From d42af68e0362ef7a43a9a3d0e216a7d83b291e20 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Thu, 23 Jun 2022 11:50:11 -0400 Subject: [PATCH 01/84] Add `cargo dev dogfood` --- clippy_dev/src/dogfood.rs | 33 +++++++++++++++++++++++++++++++++ clippy_dev/src/lib.rs | 1 + clippy_dev/src/main.rs | 21 ++++++++++++++++++++- tests/dogfood.rs | 14 ++++++++++---- 4 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 clippy_dev/src/dogfood.rs diff --git a/clippy_dev/src/dogfood.rs b/clippy_dev/src/dogfood.rs new file mode 100644 index 00000000000..b69e9f649ec --- /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 81e807cf10c..82574a8e64b 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -11,6 +11,7 @@ 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 2c27a0bcaf9..41eda842f95 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -3,8 +3,9 @@ #![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() { let matches = get_clap_config(); @@ -12,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")); }, @@ -98,6 +106,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/tests/dogfood.rs b/tests/dogfood.rs index fffc5360342..5697e8680cd 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 From c5820022d5a6e0c4d84f9ead8c8fcd6241f49dae Mon Sep 17 00:00:00 2001 From: xFrednet Date: Mon, 27 Jun 2022 23:32:35 +0200 Subject: [PATCH 02/84] Changelog for Rust 1.62 :t-rex: --- CHANGELOG.md | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcc96bc10b8..12dd7d4baf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,168 @@ 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) From 131770d22b982998c179c1407111cf24ad12c809 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Wed, 29 Jun 2022 22:42:58 -0400 Subject: [PATCH 03/84] Add `dev dogfood` to the book --- book/src/development/basics.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/book/src/development/basics.md b/book/src/development/basics.md index 78c429ea013..0acd9ce8c7f 100644 --- a/book/src/development/basics.md +++ b/book/src/development/basics.md @@ -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 @@ -95,6 +95,8 @@ cargo dev new_lint 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 From 09f5df5087c1d045db3bbf1b886702ec343a2f61 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 30 Jun 2022 10:50:09 +0200 Subject: [PATCH 04/84] Merge commit '0cb0f7636851f9fcc57085cf80197a2ef6db098f' into clippyup --- .github/workflows/remark.yml | 2 +- CHANGELOG.md | 5 + Cargo.toml | 3 +- README.md | 10 +- book/src/README.md | 2 +- clippy_dev/Cargo.toml | 2 +- clippy_dev/src/main.rs | 18 + clippy_dev/src/new_lint.rs | 2 +- clippy_dev/src/update_lints.rs | 395 +++++++- clippy_lints/Cargo.toml | 5 +- clippy_lints/src/assign_ops.rs | 235 ----- clippy_lints/src/attrs.rs | 20 +- clippy_lints/src/booleans.rs | 8 +- .../src/default_instead_of_iter_empty.rs | 68 ++ clippy_lints/src/deprecated_lints.rs | 15 +- clippy_lints/src/dereference.rs | 715 +++++++++++---- clippy_lints/src/double_comparison.rs | 96 -- clippy_lints/src/duration_subsec.rs | 75 -- clippy_lints/src/enum_variants.rs | 7 +- clippy_lints/src/eq_op.rs | 319 ------- clippy_lints/src/erasing_op.rs | 77 -- clippy_lints/src/escape.rs | 5 +- .../src/float_equality_without_abs.rs | 116 --- clippy_lints/src/implicit_return.rs | 39 +- clippy_lints/src/integer_division.rs | 61 -- clippy_lints/src/large_const_arrays.rs | 2 +- clippy_lints/src/let_underscore.rs | 7 +- clippy_lints/src/lib.register_all.rs | 39 +- clippy_lints/src/lib.register_complexity.rs | 9 +- clippy_lints/src/lib.register_correctness.rs | 16 +- clippy_lints/src/lib.register_internal.rs | 1 + clippy_lints/src/lib.register_lints.rs | 57 +- clippy_lints/src/lib.register_pedantic.rs | 6 +- clippy_lints/src/lib.register_perf.rs | 3 +- clippy_lints/src/lib.register_restriction.rs | 10 +- clippy_lints/src/lib.register_style.rs | 7 +- clippy_lints/src/lib.register_suspicious.rs | 4 +- clippy_lints/src/lib.rs | 95 +- clippy_lints/src/lifetimes.rs | 4 +- clippy_lints/src/loops/manual_find.rs | 158 ++++ clippy_lints/src/loops/mod.rs | 43 +- clippy_lints/src/loops/while_let_loop.rs | 110 ++- clippy_lints/src/macro_use.rs | 42 +- clippy_lints/src/manual_non_exhaustive.rs | 9 +- clippy_lints/src/manual_rem_euclid.rs | 123 +++ clippy_lints/src/manual_retain.rs | 228 +++++ clippy_lints/src/matches/match_same_arms.rs | 2 +- .../src/matches/match_single_binding.rs | 4 +- .../src/matches/match_str_case_mismatch.rs | 2 +- clippy_lints/src/matches/mod.rs | 4 +- .../src/matches/redundant_pattern_match.rs | 88 +- .../matches/significant_drop_in_scrutinee.rs | 162 +++- clippy_lints/src/matches/single_match.rs | 93 +- clippy_lints/src/methods/map_flatten.rs | 7 +- clippy_lints/src/misc.rs | 483 +--------- clippy_lints/src/needless_bitwise_bool.rs | 85 -- clippy_lints/src/neg_multiply.rs | 9 +- clippy_lints/src/non_copy_const.rs | 13 +- clippy_lints/src/non_expressive_names.rs | 12 +- clippy_lints/src/numeric_arithmetic.rs | 170 ---- .../absurd_extreme_comparisons.rs | 97 +- .../src/operators/assign_op_pattern.rs | 101 +++ clippy_lints/src/{ => operators}/bit_mask.rs | 168 +--- clippy_lints/src/operators/cmp_nan.rs | 30 + clippy_lints/src/operators/cmp_owned.rs | 147 +++ .../src/operators/double_comparison.rs | 54 ++ clippy_lints/src/operators/duration_subsec.rs | 44 + clippy_lints/src/operators/eq_op.rs | 45 + clippy_lints/src/operators/erasing_op.rs | 53 ++ clippy_lints/src/operators/float_cmp.rs | 139 +++ .../operators/float_equality_without_abs.rs | 71 ++ .../src/{ => operators}/identity_op.rs | 87 +- .../src/operators/integer_division.rs | 27 + .../src/operators/misrefactored_assign_op.rs | 84 ++ clippy_lints/src/operators/mod.rs | 849 ++++++++++++++++++ .../src/{ => operators}/modulo_arithmetic.rs | 74 +- clippy_lints/src/operators/modulo_one.rs | 26 + .../src/operators/needless_bitwise_bool.rs | 36 + .../src/operators/numeric_arithmetic.rs | 127 +++ clippy_lints/src/operators/op_ref.rs | 218 +++++ clippy_lints/src/operators/ptr_eq.rs | 65 ++ clippy_lints/src/operators/self_assignment.rs | 20 + .../src/operators/verbose_bit_mask.rs | 44 + clippy_lints/src/pass_by_ref_or_value.rs | 104 ++- clippy_lints/src/ptr.rs | 50 +- clippy_lints/src/ptr_eq.rs | 97 -- .../src/redundant_static_lifetimes.rs | 2 +- clippy_lints/src/returns.rs | 71 +- clippy_lints/src/self_assignment.rs | 56 -- .../src/slow_vector_initialization.rs | 39 +- clippy_lints/src/strings.rs | 6 + clippy_lints/src/transmute/mod.rs | 25 +- .../src/transmute/transmute_ptr_to_ref.rs | 56 +- clippy_lints/src/transmute/utils.rs | 28 +- clippy_lints/src/unused_async.rs | 28 +- clippy_lints/src/unwrap.rs | 8 +- clippy_lints/src/utils/internal_lints.rs | 105 ++- .../internal_lints/metadata_collector.rs | 11 +- clippy_lints/src/vec_resize_to_zero.rs | 5 + clippy_utils/Cargo.toml | 2 +- clippy_utils/src/hir_utils.rs | 44 +- clippy_utils/src/lib.rs | 69 +- clippy_utils/src/msrvs.rs | 9 +- clippy_utils/src/paths.rs | 13 +- clippy_utils/src/ty.rs | 270 ++++-- clippy_utils/src/visitors.rs | 131 ++- rust-toolchain | 2 +- src/driver.rs | 10 +- tests/compile-test.rs | 23 +- .../fail_both_diff/Cargo.toml | 8 + .../fail_both_diff/clippy.toml | 1 + .../fail_both_diff/src/main.rs | 11 + .../fail_both_diff/src/main.stderr | 16 + .../fail_both_same/Cargo.toml | 8 + .../fail_both_same/clippy.toml | 1 + .../fail_both_same/src/main.rs | 11 + .../fail_both_same/src/main.stderr | 14 + .../cargo_rust_version/fail_cargo/Cargo.toml | 8 + .../cargo_rust_version/fail_cargo/src/main.rs | 11 + .../fail_cargo/src/main.stderr | 14 + .../cargo_rust_version/fail_clippy/Cargo.toml | 7 + .../fail_clippy/clippy.toml | 1 + .../fail_clippy/src/main.rs | 11 + .../fail_clippy/src/main.stderr | 14 + .../fail_file_attr/Cargo.toml | 8 + .../fail_file_attr/clippy.toml | 1 + .../fail_file_attr/src/main.rs | 16 + .../fail_file_attr/src/main.stderr | 14 + .../pass_both_same/Cargo.toml | 8 + .../pass_both_same/clippy.toml | 1 + .../pass_both_same/src/main.rs | 11 + .../cargo_rust_version/pass_cargo/Cargo.toml | 8 + .../cargo_rust_version/pass_cargo/src/main.rs | 11 + .../cargo_rust_version/pass_clippy/Cargo.toml | 7 + .../pass_clippy/clippy.toml | 1 + .../pass_clippy/src/main.rs | 11 + .../pass_file_attr/Cargo.toml | 8 + .../pass_file_attr/src/main.rs | 13 + .../warn_both_diff/Cargo.toml | 8 + .../warn_both_diff/clippy.toml | 1 + .../warn_both_diff/src/main.rs | 11 + .../warn_both_diff/src/main.stderr | 4 + .../warn/src/main.stderr | 4 +- .../collapsible_span_lint_calls.fixed | 7 +- .../collapsible_span_lint_calls.rs | 7 +- .../ui-internal/default_deprecation_reason.rs | 30 + .../default_deprecation_reason.stderr | 22 + tests/ui/auxiliary/macro_rules.rs | 8 + tests/ui/auxiliary/proc_macro_derive.rs | 14 + .../ui/{escape_analysis.rs => boxed_local.rs} | 5 + ...ape_analysis.stderr => boxed_local.stderr} | 8 +- .../ui/checked_unwrap/simple_conditionals.rs | 16 + .../checked_unwrap/simple_conditionals.stderr | 38 +- .../declare_interior_mutable_const/others.rs | 5 + tests/ui/default_instead_of_iter_empty.fixed | 21 + tests/ui/default_instead_of_iter_empty.rs | 21 + tests/ui/default_instead_of_iter_empty.stderr | 22 + tests/ui/enum_variants.rs | 21 + tests/ui/enum_variants.stderr | 27 +- tests/ui/explicit_auto_deref.fixed | 214 +++++ tests/ui/explicit_auto_deref.rs | 214 +++++ tests/ui/explicit_auto_deref.stderr | 196 ++++ tests/ui/explicit_deref_methods.fixed | 3 +- tests/ui/explicit_deref_methods.rs | 3 +- tests/ui/explicit_deref_methods.stderr | 24 +- tests/ui/extra_unused_lifetimes.rs | 12 + tests/ui/extra_unused_lifetimes.stderr | 12 +- tests/ui/if_same_then_else.rs | 61 +- tests/ui/if_same_then_else.stderr | 20 +- tests/ui/implicit_return.fixed | 12 +- tests/ui/implicit_return.rs | 12 +- tests/ui/let_underscore_lock.rs | 9 + tests/ui/let_underscore_lock.stderr | 8 +- tests/ui/logic_bug.rs | 8 + tests/ui/logic_bug.stderr | 20 +- tests/ui/macro_use_imports.fixed | 1 + tests/ui/macro_use_imports.rs | 1 + tests/ui/macro_use_imports.stderr | 20 +- tests/ui/macro_use_imports_expect.rs | 51 ++ tests/ui/manual_find.rs | 22 + tests/ui/manual_find.stderr | 29 + tests/ui/manual_find_fixable.fixed | 182 ++++ tests/ui/manual_find_fixable.rs | 242 +++++ tests/ui/manual_find_fixable.stderr | 142 +++ tests/ui/manual_non_exhaustive_enum.rs | 9 + tests/ui/manual_non_exhaustive_enum.stderr | 8 +- tests/ui/manual_rem_euclid.fixed | 55 ++ tests/ui/manual_rem_euclid.rs | 55 ++ tests/ui/manual_rem_euclid.stderr | 57 ++ tests/ui/manual_retain.fixed | 240 +++++ tests/ui/manual_retain.rs | 246 +++++ tests/ui/manual_retain.stderr | 124 +++ tests/ui/methods.rs | 1 + tests/ui/methods.stderr | 4 +- tests/ui/min_rust_version_attr.rs | 15 + tests/ui/min_rust_version_attr.stderr | 8 +- tests/ui/needless_borrow.fixed | 43 +- tests/ui/needless_borrow.rs | 43 +- tests/ui/needless_borrow.stderr | 30 +- tests/ui/needless_borrow_pat.rs | 2 +- tests/ui/needless_return.fixed | 36 +- tests/ui/needless_return.rs | 36 +- tests/ui/needless_return.stderr | 74 +- tests/ui/neg_multiply.fixed | 3 + tests/ui/neg_multiply.rs | 3 + tests/ui/neg_multiply.stderr | 14 +- tests/ui/nonminimal_bool.rs | 7 + tests/ui/nonminimal_bool.stderr | 24 +- tests/ui/ptr_arg.rs | 6 + tests/ui/ptr_arg.stderr | 40 +- tests/ui/redundant_clone.fixed | 5 + tests/ui/redundant_clone.rs | 5 + tests/ui/redundant_clone.stderr | 60 +- tests/ui/ref_binding_to_reference.rs | 2 +- tests/ui/search_is_some_fixable_none.fixed | 2 +- tests/ui/search_is_some_fixable_none.rs | 2 +- tests/ui/search_is_some_fixable_some.fixed | 2 +- tests/ui/search_is_some_fixable_some.rs | 2 +- tests/ui/should_impl_trait/corner_cases.rs | 3 +- tests/ui/significant_drop_in_scrutinee.rs | 29 + tests/ui/significant_drop_in_scrutinee.stderr | 297 ++++-- tests/ui/single_match.stderr | 11 +- tests/ui/single_match_else.rs | 19 + tests/ui/single_match_else.stderr | 82 +- tests/ui/slow_vector_initialization.rs | 6 + tests/ui/slow_vector_initialization.stderr | 28 +- tests/ui/transmute_ptr_to_ref.fixed | 78 ++ tests/ui/transmute_ptr_to_ref.rs | 39 +- tests/ui/transmute_ptr_to_ref.stderr | 102 ++- tests/ui/trivially_copy_pass_by_ref.rs | 38 + tests/ui/trivially_copy_pass_by_ref.stderr | 8 +- tests/ui/unused_async.rs | 34 + tests/ui/unused_async.stderr | 14 +- tests/ui/useless_asref.fixed | 1 + tests/ui/useless_asref.rs | 1 + tests/ui/useless_asref.stderr | 22 +- tests/ui/while_let_loop.rs | 26 + tests/ui/while_let_on_iterator.fixed | 3 +- tests/ui/while_let_on_iterator.rs | 3 +- tests/ui/while_let_on_iterator.stderr | 46 +- tests/ui/zero_div_zero.rs | 2 +- tests/ui/zero_div_zero.stderr | 28 +- util/gh-pages/index.html | 1 + 243 files changed, 9046 insertions(+), 3487 deletions(-) delete mode 100644 clippy_lints/src/assign_ops.rs create mode 100644 clippy_lints/src/default_instead_of_iter_empty.rs delete mode 100644 clippy_lints/src/double_comparison.rs delete mode 100644 clippy_lints/src/duration_subsec.rs delete mode 100644 clippy_lints/src/eq_op.rs delete mode 100644 clippy_lints/src/erasing_op.rs delete mode 100644 clippy_lints/src/float_equality_without_abs.rs delete mode 100644 clippy_lints/src/integer_division.rs create mode 100644 clippy_lints/src/loops/manual_find.rs create mode 100644 clippy_lints/src/manual_rem_euclid.rs create mode 100644 clippy_lints/src/manual_retain.rs delete mode 100644 clippy_lints/src/needless_bitwise_bool.rs delete mode 100644 clippy_lints/src/numeric_arithmetic.rs rename clippy_lints/src/{ => operators}/absurd_extreme_comparisons.rs (53%) create mode 100644 clippy_lints/src/operators/assign_op_pattern.rs rename clippy_lints/src/{ => operators}/bit_mask.rs (51%) create mode 100644 clippy_lints/src/operators/cmp_nan.rs create mode 100644 clippy_lints/src/operators/cmp_owned.rs create mode 100644 clippy_lints/src/operators/double_comparison.rs create mode 100644 clippy_lints/src/operators/duration_subsec.rs create mode 100644 clippy_lints/src/operators/eq_op.rs create mode 100644 clippy_lints/src/operators/erasing_op.rs create mode 100644 clippy_lints/src/operators/float_cmp.rs create mode 100644 clippy_lints/src/operators/float_equality_without_abs.rs rename clippy_lints/src/{ => operators}/identity_op.rs (60%) create mode 100644 clippy_lints/src/operators/integer_division.rs create mode 100644 clippy_lints/src/operators/misrefactored_assign_op.rs create mode 100644 clippy_lints/src/operators/mod.rs rename clippy_lints/src/{ => operators}/modulo_arithmetic.rs (64%) create mode 100644 clippy_lints/src/operators/modulo_one.rs create mode 100644 clippy_lints/src/operators/needless_bitwise_bool.rs create mode 100644 clippy_lints/src/operators/numeric_arithmetic.rs create mode 100644 clippy_lints/src/operators/op_ref.rs create mode 100644 clippy_lints/src/operators/ptr_eq.rs create mode 100644 clippy_lints/src/operators/self_assignment.rs create mode 100644 clippy_lints/src/operators/verbose_bit_mask.rs delete mode 100644 clippy_lints/src/ptr_eq.rs delete mode 100644 clippy_lints/src/self_assignment.rs create mode 100644 tests/ui-cargo/cargo_rust_version/fail_both_diff/Cargo.toml create mode 100644 tests/ui-cargo/cargo_rust_version/fail_both_diff/clippy.toml create mode 100644 tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.rs create mode 100644 tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.stderr create mode 100644 tests/ui-cargo/cargo_rust_version/fail_both_same/Cargo.toml create mode 100644 tests/ui-cargo/cargo_rust_version/fail_both_same/clippy.toml create mode 100644 tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.rs create mode 100644 tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.stderr create mode 100644 tests/ui-cargo/cargo_rust_version/fail_cargo/Cargo.toml create mode 100644 tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.rs create mode 100644 tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.stderr create mode 100644 tests/ui-cargo/cargo_rust_version/fail_clippy/Cargo.toml create mode 100644 tests/ui-cargo/cargo_rust_version/fail_clippy/clippy.toml create mode 100644 tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.rs create mode 100644 tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.stderr create mode 100644 tests/ui-cargo/cargo_rust_version/fail_file_attr/Cargo.toml create mode 100644 tests/ui-cargo/cargo_rust_version/fail_file_attr/clippy.toml create mode 100644 tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.rs create mode 100644 tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.stderr create mode 100644 tests/ui-cargo/cargo_rust_version/pass_both_same/Cargo.toml create mode 100644 tests/ui-cargo/cargo_rust_version/pass_both_same/clippy.toml create mode 100644 tests/ui-cargo/cargo_rust_version/pass_both_same/src/main.rs create mode 100644 tests/ui-cargo/cargo_rust_version/pass_cargo/Cargo.toml create mode 100644 tests/ui-cargo/cargo_rust_version/pass_cargo/src/main.rs create mode 100644 tests/ui-cargo/cargo_rust_version/pass_clippy/Cargo.toml create mode 100644 tests/ui-cargo/cargo_rust_version/pass_clippy/clippy.toml create mode 100644 tests/ui-cargo/cargo_rust_version/pass_clippy/src/main.rs create mode 100644 tests/ui-cargo/cargo_rust_version/pass_file_attr/Cargo.toml create mode 100644 tests/ui-cargo/cargo_rust_version/pass_file_attr/src/main.rs create mode 100644 tests/ui-cargo/cargo_rust_version/warn_both_diff/Cargo.toml create mode 100644 tests/ui-cargo/cargo_rust_version/warn_both_diff/clippy.toml create mode 100644 tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.rs create mode 100644 tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.stderr create mode 100644 tests/ui-internal/default_deprecation_reason.rs create mode 100644 tests/ui-internal/default_deprecation_reason.stderr rename tests/ui/{escape_analysis.rs => boxed_local.rs} (97%) rename tests/ui/{escape_analysis.stderr => boxed_local.stderr} (81%) create mode 100644 tests/ui/default_instead_of_iter_empty.fixed create mode 100644 tests/ui/default_instead_of_iter_empty.rs create mode 100644 tests/ui/default_instead_of_iter_empty.stderr create mode 100644 tests/ui/explicit_auto_deref.fixed create mode 100644 tests/ui/explicit_auto_deref.rs create mode 100644 tests/ui/explicit_auto_deref.stderr create mode 100644 tests/ui/macro_use_imports_expect.rs create mode 100644 tests/ui/manual_find.rs create mode 100644 tests/ui/manual_find.stderr create mode 100644 tests/ui/manual_find_fixable.fixed create mode 100644 tests/ui/manual_find_fixable.rs create mode 100644 tests/ui/manual_find_fixable.stderr create mode 100644 tests/ui/manual_rem_euclid.fixed create mode 100644 tests/ui/manual_rem_euclid.rs create mode 100644 tests/ui/manual_rem_euclid.stderr create mode 100644 tests/ui/manual_retain.fixed create mode 100644 tests/ui/manual_retain.rs create mode 100644 tests/ui/manual_retain.stderr create mode 100644 tests/ui/transmute_ptr_to_ref.fixed diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index ff471207b65..81ef072bbb0 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -21,7 +21,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v1.4.4 with: - node-version: '12.x' + node-version: '14.x' - name: Install remark run: npm install remark-cli remark-lint remark-lint-maximum-line-length remark-preset-lint-recommended remark-gfm diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aaf12ed932..9bc93c1cb42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3348,6 +3348,7 @@ Released 2018-09-13 [`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call [`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation [`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const +[`default_instead_of_iter_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_instead_of_iter_empty [`default_numeric_fallback`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_numeric_fallback [`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access [`default_union_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_union_representation @@ -3399,6 +3400,7 @@ Released 2018-09-13 [`expect_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call [`expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_used [`expl_impl_clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#expl_impl_clone_on_copy +[`explicit_auto_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_auto_deref [`explicit_counter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_counter_loop [`explicit_deref_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods [`explicit_into_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_into_iter_loop @@ -3519,6 +3521,7 @@ Released 2018-09-13 [`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn [`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits [`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map +[`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find [`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map [`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten [`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map @@ -3526,6 +3529,8 @@ Released 2018-09-13 [`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive [`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or [`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains +[`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid +[`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain [`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic [`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once [`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat diff --git a/Cargo.toml b/Cargo.toml index e4060ce29a7..644ca6318f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.63" +version = "0.1.64" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" @@ -31,6 +31,7 @@ termize = "0.1" compiletest_rs = { version = "0.8", features = ["tmp"] } tester = "0.9" regex = "1.5" +toml = "0.5" # This is used by the `collect-metadata` alias. filetime = "0.2" diff --git a/README.md b/README.md index edbc626e354..2c3defeaa83 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are over 500 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are over 550 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category. @@ -214,6 +214,14 @@ specifying the minimum supported Rust version (MSRV) in the clippy configuration msrv = "1.30.0" ``` +Alternatively, the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) +in the `Cargo.toml` can be used. + +```toml +# Cargo.toml +rust-version = "1.30" +``` + The MSRV can also be specified as an inner attribute, like below. ```rust diff --git a/book/src/README.md b/book/src/README.md index de1f70d7e96..d941f8b65e8 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are over 500 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are over 550 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index b0d470a2124..2ac3b4fe2ed 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] aho-corasick = "0.7" -clap = "3.1" +clap = "3.2" indoc = "1.0" itertools = "0.10.1" opener = "0.5" diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 2c27a0bcaf9..243a901503f 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -5,6 +5,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command, PossibleValue}; use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints}; use indoc::indoc; + fn main() { let matches = get_clap_config(); @@ -85,6 +86,11 @@ fn main() { let uplift = matches.contains_id("uplift"); update_lints::rename(old_name, new_name, uplift); }, + Some(("deprecate", matches)) => { + let name = matches.get_one::("name").unwrap(); + let reason = matches.get_one("reason"); + update_lints::deprecate(name, reason); + }, _ => {}, } } @@ -266,6 +272,18 @@ fn get_clap_config() -> ArgMatches { .long("uplift") .help("This lint will be uplifted into rustc"), ]), + Command::new("deprecate").about("Deprecates the given lint").args([ + Arg::new("name") + .index(1) + .required(true) + .help("The name of the lint to deprecate"), + Arg::new("reason") + .long("reason") + .short('r') + .required(false) + .takes_value(true) + .help("The reason for deprecation"), + ]), ]) .get_matches() } diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 748d73c0801..7d7e760ef44 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -138,7 +138,7 @@ fn to_camel_case(name: &str) -> String { .collect() } -fn get_stabilization_version() -> String { +pub(crate) fn get_stabilization_version() -> String { fn parse_manifest(contents: &str) -> Option { let version = contents .lines() diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 1bbd9a45b61..2e0659f42d7 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,16 +1,17 @@ +use crate::clippy_project_root; use aho_corasick::AhoCorasickBuilder; -use core::fmt::Write as _; +use indoc::writedoc; use itertools::Itertools; use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; -use std::fs; -use std::io::{self, Read as _, Seek as _, Write as _}; +use std::fmt::Write; +use std::fs::{self, OpenOptions}; +use std::io::{self, Read, Seek, SeekFrom, Write as _}; +use std::ops::Range; use std::path::{Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; -use crate::clippy_project_root; - const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ // Use that command to update this file and do not edit by hand.\n\ // Manual edits will be overwritten.\n\n"; @@ -326,6 +327,200 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) { println!("note: `cargo uitest` still needs to be run to update the test results"); } +const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note"; +/// Runs the `deprecate` command +/// +/// This does the following: +/// * Adds an entry to `deprecated_lints.rs`. +/// * Removes the lint declaration (and the entire file if applicable) +/// +/// # Panics +/// +/// If a file path could not read from or written to +pub fn deprecate(name: &str, reason: Option<&String>) { + fn finish( + (lints, mut deprecated_lints, renamed_lints): (Vec, Vec, Vec), + name: &str, + reason: &str, + ) { + deprecated_lints.push(DeprecatedLint { + name: name.to_string(), + reason: reason.to_string(), + declaration_range: Range::default(), + }); + + generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + println!("info: `{}` has successfully been deprecated", name); + + if reason == DEFAULT_DEPRECATION_REASON { + println!("note: the deprecation reason must be updated in `clippy_lints/src/deprecated_lints.rs`"); + } + println!("note: you must run `cargo uitest` to update the test results"); + } + + let reason = reason.map_or(DEFAULT_DEPRECATION_REASON, String::as_str); + let name_lower = name.to_lowercase(); + let name_upper = name.to_uppercase(); + + let (mut lints, deprecated_lints, renamed_lints) = gather_all(); + let Some(lint) = lints.iter().find(|l| l.name == name_lower) else { eprintln!("error: failed to find lint `{}`", name); return; }; + + let mod_path = { + let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module)); + if mod_path.is_dir() { + mod_path = mod_path.join("mod"); + } + + mod_path.set_extension("rs"); + mod_path + }; + + let deprecated_lints_path = &*clippy_project_root().join("clippy_lints/src/deprecated_lints.rs"); + + if remove_lint_declaration(&name_lower, &mod_path, &mut lints).unwrap_or(false) { + declare_deprecated(&name_upper, deprecated_lints_path, reason).unwrap(); + finish((lints, deprecated_lints, renamed_lints), name, reason); + return; + } + + eprintln!("error: lint not found"); +} + +fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io::Result { + fn remove_lint(name: &str, lints: &mut Vec) { + lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos)); + } + + fn remove_test_assets(name: &str) { + let test_file_stem = format!("tests/ui/{}", name); + let path = Path::new(&test_file_stem); + + // Some lints have their own directories, delete them + if path.is_dir() { + fs::remove_dir_all(path).ok(); + return; + } + + // Remove all related test files + fs::remove_file(path.with_extension("rs")).ok(); + fs::remove_file(path.with_extension("stderr")).ok(); + fs::remove_file(path.with_extension("fixed")).ok(); + } + + fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) { + let impl_lint_pass_start = content.find("impl_lint_pass!").unwrap_or_else(|| { + content + .find("declare_lint_pass!") + .unwrap_or_else(|| panic!("failed to find `impl_lint_pass`")) + }); + let mut impl_lint_pass_end = content[impl_lint_pass_start..] + .find(']') + .expect("failed to find `impl_lint_pass` terminator"); + + impl_lint_pass_end += impl_lint_pass_start; + if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(&lint_name_upper) { + let mut lint_name_end = impl_lint_pass_start + (lint_name_pos + lint_name_upper.len()); + for c in content[lint_name_end..impl_lint_pass_end].chars() { + // Remove trailing whitespace + if c == ',' || c.is_whitespace() { + lint_name_end += 1; + } else { + break; + } + } + + content.replace_range(impl_lint_pass_start + lint_name_pos..lint_name_end, ""); + } + } + + if path.exists() { + if let Some(lint) = lints.iter().find(|l| l.name == name) { + if lint.module == name { + // The lint name is the same as the file, we can just delete the entire file + fs::remove_file(path)?; + } else { + // We can't delete the entire file, just remove the declaration + + if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) { + // Remove clippy_lints/src/some_mod/some_lint.rs + let mut lint_mod_path = path.to_path_buf(); + lint_mod_path.set_file_name(name); + lint_mod_path.set_extension("rs"); + + fs::remove_file(lint_mod_path).ok(); + } + + let mut content = + fs::read_to_string(&path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy())); + + eprintln!( + "warn: you will have to manually remove any code related to `{}` from `{}`", + name, + path.display() + ); + + assert!( + content[lint.declaration_range.clone()].contains(&name.to_uppercase()), + "error: `{}` does not contain lint `{}`'s declaration", + path.display(), + lint.name + ); + + // Remove lint declaration (declare_clippy_lint!) + content.replace_range(lint.declaration_range.clone(), ""); + + // Remove the module declaration (mod xyz;) + let mod_decl = format!("\nmod {};", name); + content = content.replacen(&mod_decl, "", 1); + + remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content); + fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy())); + } + + remove_test_assets(name); + remove_lint(name, lints); + return Ok(true); + } + } + + Ok(false) +} + +fn declare_deprecated(name: &str, path: &Path, reason: &str) -> io::Result<()> { + let mut file = OpenOptions::new().write(true).open(path)?; + + file.seek(SeekFrom::End(0))?; + + let version = crate::new_lint::get_stabilization_version(); + let deprecation_reason = if reason == DEFAULT_DEPRECATION_REASON { + "TODO" + } else { + reason + }; + + writedoc!( + file, + " + + declare_deprecated_lint! {{ + /// ### What it does + /// Nothing. This lint has been deprecated. + /// + /// ### Deprecation reason + /// {} + #[clippy::version = \"{}\"] + pub {}, + \"{}\" + }} + + ", + deprecation_reason, + version, + name, + reason, + ) +} + /// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there /// were no replacements. fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option { @@ -393,16 +588,18 @@ struct Lint { group: String, desc: String, module: String, + declaration_range: Range, } impl Lint { #[must_use] - fn new(name: &str, group: &str, desc: &str, module: &str) -> Self { + fn new(name: &str, group: &str, desc: &str, module: &str, declaration_range: Range) -> Self { Self { name: name.to_lowercase(), group: group.into(), desc: remove_line_splices(desc), module: module.into(), + declaration_range, } } @@ -433,12 +630,14 @@ fn by_lint_group(lints: impl Iterator) -> HashMap struct DeprecatedLint { name: String, reason: String, + declaration_range: Range, } impl DeprecatedLint { - fn new(name: &str, reason: &str) -> Self { + fn new(name: &str, reason: &str, declaration_range: Range) -> Self { Self { name: name.to_lowercase(), reason: remove_line_splices(reason), + declaration_range, } } } @@ -610,7 +809,11 @@ fn clippy_lints_src_files() -> impl Iterator { macro_rules! match_tokens { ($iter:ident, $($token:ident $({$($fields:tt)*})? $(($capture:ident))?)*) => { { - $($(let $capture =)? if let Some((TokenKind::$token $({$($fields)*})?, _x)) = $iter.next() { + $($(let $capture =)? if let Some(LintDeclSearchResult { + token_kind: TokenKind::$token $({$($fields)*})?, + content: _x, + .. + }) = $iter.next() { _x } else { continue; @@ -621,40 +824,72 @@ macro_rules! match_tokens { } } +struct LintDeclSearchResult<'a> { + token_kind: TokenKind, + content: &'a str, + range: Range, +} + /// Parse a source file looking for `declare_clippy_lint` macro invocations. fn parse_contents(contents: &str, module: &str, lints: &mut Vec) { let mut offset = 0usize; let mut iter = tokenize(contents).map(|t| { let range = offset..offset + t.len; offset = range.end; - (t.kind, &contents[range]) + + LintDeclSearchResult { + token_kind: t.kind, + content: &contents[range.clone()], + range, + } }); - while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_clippy_lint") { + while let Some(LintDeclSearchResult { range, .. }) = iter.find( + |LintDeclSearchResult { + token_kind, content, .. + }| token_kind == &TokenKind::Ident && *content == "declare_clippy_lint", + ) { + let start = range.start; + let mut iter = iter .by_ref() - .filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. })); + .filter(|t| !matches!(t.token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. })); // matches `!{` match_tokens!(iter, Bang OpenBrace); match iter.next() { // #[clippy::version = "version"] pub - Some((TokenKind::Pound, _)) => { + Some(LintDeclSearchResult { + token_kind: TokenKind::Pound, + .. + }) => { match_tokens!(iter, OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Ident); }, // pub - Some((TokenKind::Ident, _)) => (), + Some(LintDeclSearchResult { + token_kind: TokenKind::Ident, + .. + }) => (), _ => continue, } + let (name, group, desc) = match_tokens!( iter, // LINT_NAME Ident(name) Comma // group, Ident(group) Comma - // "description" } - Literal{..}(desc) CloseBrace + // "description" + Literal{..}(desc) ); - lints.push(Lint::new(name, group, desc, module)); + + if let Some(LintDeclSearchResult { + token_kind: TokenKind::CloseBrace, + range, + .. + }) = iter.next() + { + lints.push(Lint::new(name, group, desc, module, start..range.end)); + } } } @@ -664,12 +899,24 @@ fn parse_deprecated_contents(contents: &str, lints: &mut Vec) { let mut iter = tokenize(contents).map(|t| { let range = offset..offset + t.len; offset = range.end; - (t.kind, &contents[range]) + + LintDeclSearchResult { + token_kind: t.kind, + content: &contents[range.clone()], + range, + } }); - while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_deprecated_lint") { - let mut iter = iter - .by_ref() - .filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. })); + + while let Some(LintDeclSearchResult { range, .. }) = iter.find( + |LintDeclSearchResult { + token_kind, content, .. + }| token_kind == &TokenKind::Ident && *content == "declare_deprecated_lint", + ) { + let start = range.start; + + let mut iter = iter.by_ref().filter(|LintDeclSearchResult { ref token_kind, .. }| { + !matches!(token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }) + }); let (name, reason) = match_tokens!( iter, // !{ @@ -680,10 +927,16 @@ fn parse_deprecated_contents(contents: &str, lints: &mut Vec) { Ident Ident(name) Comma // "description" Literal{kind: LiteralKind::Str{..},..}(reason) - // } - CloseBrace ); - lints.push(DeprecatedLint::new(name, reason)); + + if let Some(LintDeclSearchResult { + token_kind: TokenKind::CloseBrace, + range, + .. + }) = iter.next() + { + lints.push(DeprecatedLint::new(name, reason, start..range.end)); + } } } @@ -693,8 +946,14 @@ fn parse_renamed_contents(contents: &str, lints: &mut Vec) { let mut iter = tokenize(line).map(|t| { let range = offset..offset + t.len; offset = range.end; - (t.kind, &line[range]) + + LintDeclSearchResult { + token_kind: t.kind, + content: &line[range.clone()], + range, + } }); + let (old_name, new_name) = match_tokens!( iter, // ("old_name", @@ -844,10 +1103,25 @@ fn test_parse_contents() { "#; let mut result = Vec::new(); parse_contents(CONTENTS, "module_name", &mut result); + for r in &mut result { + r.declaration_range = Range::default(); + } let expected = vec![ - Lint::new("ptr_arg", "style", "\"really long text\"", "module_name"), - Lint::new("doc_markdown", "pedantic", "\"single line\"", "module_name"), + Lint::new( + "ptr_arg", + "style", + "\"really long text\"", + "module_name", + Range::default(), + ), + Lint::new( + "doc_markdown", + "pedantic", + "\"single line\"", + "module_name", + Range::default(), + ), ]; assert_eq!(expected, result); } @@ -865,10 +1139,14 @@ fn test_parse_deprecated_contents() { let mut result = Vec::new(); parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result); + for r in &mut result { + r.declaration_range = Range::default(); + } let expected = vec![DeprecatedLint::new( "should_assert_eq", "\"`assert!()` will be more flexible with RFC 2011\"", + Range::default(), )]; assert_eq!(expected, result); } @@ -876,15 +1154,34 @@ fn test_parse_deprecated_contents() { #[test] fn test_usable_lints() { let lints = vec![ - Lint::new("should_assert_eq2", "Not Deprecated", "\"abc\"", "module_name"), - Lint::new("should_assert_eq2", "internal", "\"abc\"", "module_name"), - Lint::new("should_assert_eq2", "internal_style", "\"abc\"", "module_name"), + Lint::new( + "should_assert_eq2", + "Not Deprecated", + "\"abc\"", + "module_name", + Range::default(), + ), + Lint::new( + "should_assert_eq2", + "internal", + "\"abc\"", + "module_name", + Range::default(), + ), + Lint::new( + "should_assert_eq2", + "internal_style", + "\"abc\"", + "module_name", + Range::default(), + ), ]; let expected = vec![Lint::new( "should_assert_eq2", "Not Deprecated", "\"abc\"", "module_name", + Range::default(), )]; assert_eq!(expected, Lint::usable_lints(&lints)); } @@ -892,21 +1189,33 @@ fn test_usable_lints() { #[test] fn test_by_lint_group() { let lints = vec![ - Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"), - Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name"), - Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"), + Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()), + Lint::new( + "should_assert_eq2", + "group2", + "\"abc\"", + "module_name", + Range::default(), + ), + Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()), ]; let mut expected: HashMap> = HashMap::new(); expected.insert( "group1".to_string(), vec![ - Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"), - Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"), + Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()), + Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()), ], ); expected.insert( "group2".to_string(), - vec![Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name")], + vec![Lint::new( + "should_assert_eq2", + "group2", + "\"abc\"", + "module_name", + Range::default(), + )], ); assert_eq!(expected, Lint::by_lint_group(lints.into_iter())); } @@ -914,8 +1223,12 @@ fn test_by_lint_group() { #[test] fn test_gen_deprecated() { let lints = vec![ - DeprecatedLint::new("should_assert_eq", "\"has been superseded by should_assert_eq2\""), - DeprecatedLint::new("another_deprecated", "\"will be removed\""), + DeprecatedLint::new( + "should_assert_eq", + "\"has been superseded by should_assert_eq2\"", + Range::default(), + ), + DeprecatedLint::new("another_deprecated", "\"will be removed\"", Range::default()), ]; let expected = GENERATED_FILE_COMMENT.to_string() @@ -940,9 +1253,9 @@ fn test_gen_deprecated() { #[test] fn test_gen_lint_group_list() { let lints = vec![ - Lint::new("abc", "group1", "\"abc\"", "module_name"), - Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"), - Lint::new("internal", "internal_style", "\"abc\"", "module_name"), + Lint::new("abc", "group1", "\"abc\"", "module_name", Range::default()), + Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()), + Lint::new("internal", "internal_style", "\"abc\"", "module_name", Range::default()), ]; let expected = GENERATED_FILE_COMMENT.to_string() + &[ diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 4d5bf47833f..79a56dc405d 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_lints" -version = "0.1.63" +version = "0.1.64" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" @@ -10,7 +10,6 @@ edition = "2021" [dependencies] cargo_metadata = "0.14" -clippy_dev = { path = "../clippy_dev", optional = true } clippy_utils = { path = "../clippy_utils" } if_chain = "1.0" itertools = "0.10.1" @@ -32,7 +31,7 @@ url = { version = "2.2", features = ["serde"] } [features] deny-warnings = ["clippy_utils/deny-warnings"] # build clippy with internal lints enabled, off by default -internal = ["clippy_utils/internal", "serde_json", "tempfile", "clippy_dev"] +internal = ["clippy_utils/internal", "serde_json", "tempfile"] [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] diff --git a/clippy_lints/src/assign_ops.rs b/clippy_lints/src/assign_ops.rs deleted file mode 100644 index f81da2d4223..00000000000 --- a/clippy_lints/src/assign_ops.rs +++ /dev/null @@ -1,235 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_opt; -use clippy_utils::ty::implements_trait; -use clippy_utils::{binop_traits, sugg}; -use clippy_utils::{eq_expr_value, trait_ref_of_method}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for `a = a op b` or `a = b commutative_op a` - /// patterns. - /// - /// ### Why is this bad? - /// These can be written as the shorter `a op= b`. - /// - /// ### Known problems - /// While forbidden by the spec, `OpAssign` traits may have - /// implementations that differ from the regular `Op` impl. - /// - /// ### Example - /// ```rust - /// let mut a = 5; - /// let b = 0; - /// // ... - /// - /// a = a + b; - /// ``` - /// - /// Use instead: - /// ```rust - /// let mut a = 5; - /// let b = 0; - /// // ... - /// - /// a += b; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub ASSIGN_OP_PATTERN, - style, - "assigning the result of an operation on a variable to that same variable" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `a op= a op b` or `a op= b op a` patterns. - /// - /// ### Why is this bad? - /// Most likely these are bugs where one meant to write `a - /// op= b`. - /// - /// ### Known problems - /// Clippy cannot know for sure if `a op= a op b` should have - /// been `a = a op a op b` or `a = a op b`/`a op= b`. Therefore, it suggests both. - /// If `a op= a op b` is really the correct behavior it should be - /// written as `a = a op a op b` as it's less confusing. - /// - /// ### Example - /// ```rust - /// let mut a = 5; - /// let b = 2; - /// // ... - /// a += a + b; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MISREFACTORED_ASSIGN_OP, - suspicious, - "having a variable on both sides of an assign op" -} - -declare_lint_pass!(AssignOps => [ASSIGN_OP_PATTERN, MISREFACTORED_ASSIGN_OP]); - -impl<'tcx> LateLintPass<'tcx> for AssignOps { - #[allow(clippy::too_many_lines)] - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - match &expr.kind { - hir::ExprKind::AssignOp(op, lhs, rhs) => { - if let hir::ExprKind::Binary(binop, l, r) = &rhs.kind { - if op.node != binop.node { - return; - } - // lhs op= l op r - if eq_expr_value(cx, lhs, l) { - lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, r); - } - // lhs op= l commutative_op r - if is_commutative(op.node) && eq_expr_value(cx, lhs, r) { - lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, l); - } - } - }, - hir::ExprKind::Assign(assignee, e, _) => { - if let hir::ExprKind::Binary(op, l, r) = &e.kind { - let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| { - let ty = cx.typeck_results().expr_ty(assignee); - let rty = cx.typeck_results().expr_ty(rhs); - if_chain! { - if let Some((_, lang_item)) = binop_traits(op.node); - if let Ok(trait_id) = cx.tcx.lang_items().require(lang_item); - let parent_fn = cx.tcx.hir().get_parent_item(e.hir_id); - if trait_ref_of_method(cx, parent_fn) - .map_or(true, |t| t.path.res.def_id() != trait_id); - if implements_trait(cx, ty, trait_id, &[rty.into()]); - then { - span_lint_and_then( - cx, - ASSIGN_OP_PATTERN, - expr.span, - "manual implementation of an assign operation", - |diag| { - if let (Some(snip_a), Some(snip_r)) = - (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs.span)) - { - diag.span_suggestion( - expr.span, - "replace it with", - format!("{} {}= {}", snip_a, op.node.as_str(), snip_r), - Applicability::MachineApplicable, - ); - } - }, - ); - } - } - }; - - let mut visitor = ExprVisitor { - assignee, - counter: 0, - cx, - }; - - walk_expr(&mut visitor, e); - - if visitor.counter == 1 { - // a = a op b - if eq_expr_value(cx, assignee, l) { - lint(assignee, r); - } - // a = b commutative_op a - // Limited to primitive type as these ops are know to be commutative - if eq_expr_value(cx, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() { - match op.node { - hir::BinOpKind::Add - | hir::BinOpKind::Mul - | hir::BinOpKind::And - | hir::BinOpKind::Or - | hir::BinOpKind::BitXor - | hir::BinOpKind::BitAnd - | hir::BinOpKind::BitOr => { - lint(assignee, l); - }, - _ => {}, - } - } - } - } - }, - _ => {}, - } - } -} - -fn lint_misrefactored_assign_op( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, - op: hir::BinOp, - rhs: &hir::Expr<'_>, - assignee: &hir::Expr<'_>, - rhs_other: &hir::Expr<'_>, -) { - span_lint_and_then( - cx, - MISREFACTORED_ASSIGN_OP, - expr.span, - "variable appears on both sides of an assignment operation", - |diag| { - if let (Some(snip_a), Some(snip_r)) = (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs_other.span)) { - let a = &sugg::Sugg::hir(cx, assignee, ".."); - let r = &sugg::Sugg::hir(cx, rhs, ".."); - let long = format!("{} = {}", snip_a, sugg::make_binop(op.node.into(), a, r)); - diag.span_suggestion( - expr.span, - &format!( - "did you mean `{} = {} {} {}` or `{}`? Consider replacing it with", - snip_a, - snip_a, - op.node.as_str(), - snip_r, - long - ), - format!("{} {}= {}", snip_a, op.node.as_str(), snip_r), - Applicability::MaybeIncorrect, - ); - diag.span_suggestion( - expr.span, - "or", - long, - Applicability::MaybeIncorrect, // snippet - ); - } - }, - ); -} - -#[must_use] -fn is_commutative(op: hir::BinOpKind) -> bool { - use rustc_hir::BinOpKind::{ - Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub, - }; - match op { - Add | Mul | And | Or | BitXor | BitAnd | BitOr | Eq | Ne => true, - Sub | Div | Rem | Shl | Shr | Lt | Le | Ge | Gt => false, - } -} - -struct ExprVisitor<'a, 'tcx> { - assignee: &'a hir::Expr<'a>, - counter: u8, - cx: &'a LateContext<'tcx>, -} - -impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> { - fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { - if eq_expr_value(self.cx, self.assignee, expr) { - self.counter += 1; - } - - walk_expr(self, expr); - } -} diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index ed12ad9c367..4bcbeacf9fe 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -78,10 +78,17 @@ /// Checks for `extern crate` and `use` items annotated with /// lint attributes. /// - /// This lint permits `#[allow(unused_imports)]`, `#[allow(deprecated)]`, - /// `#[allow(unreachable_pub)]`, `#[allow(clippy::wildcard_imports)]` and - /// `#[allow(clippy::enum_glob_use)]` on `use` items and `#[allow(unused_imports)]` on - /// `extern crate` items with a `#[macro_use]` attribute. + /// This lint permits lint attributes for lints emitted on the items themself. + /// For `use` items these lints are: + /// * deprecated + /// * unreachable_pub + /// * unused_imports + /// * clippy::enum_glob_use + /// * clippy::macro_use_imports + /// * clippy::wildcard_imports + /// + /// For `extern crate` items these lints are: + /// * `unused_imports` on items with `#[macro_use]` /// /// ### Why is this bad? /// Lint attributes have no effect on crate imports. Most @@ -347,7 +354,10 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { || extract_clippy_lint(lint).map_or(false, |s| { matches!( s.as_str(), - "wildcard_imports" | "enum_glob_use" | "redundant_pub_crate", + "wildcard_imports" + | "enum_glob_use" + | "redundant_pub_crate" + | "macro_use_imports", ) }) { diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index e4e122ba6eb..526ee2f891a 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::snippet_opt; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::{eq_expr_value, get_trait_def_id, paths}; @@ -394,9 +394,10 @@ fn bool_expr(&self, e: &'tcx Expr<'_>) { continue 'simplified; } if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 { - span_lint_and_then( + span_lint_hir_and_then( self.cx, LOGIC_BUG, + e.hir_id, e.span, "this boolean expression contains a logic bug", |diag| { @@ -429,9 +430,10 @@ fn bool_expr(&self, e: &'tcx Expr<'_>) { } } let nonminimal_bool_lint = |suggestions: Vec<_>| { - span_lint_and_then( + span_lint_hir_and_then( self.cx, NONMINIMAL_BOOL, + e.hir_id, e.span, "this boolean expression can be simplified", |diag| { diff --git a/clippy_lints/src/default_instead_of_iter_empty.rs b/clippy_lints/src/default_instead_of_iter_empty.rs new file mode 100644 index 00000000000..3c996d3d2ae --- /dev/null +++ b/clippy_lints/src/default_instead_of_iter_empty.rs @@ -0,0 +1,68 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::last_path_segment; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{match_def_path, paths}; +use rustc_errors::Applicability; +use rustc_hir::{def, Expr, ExprKind, GenericArg, QPath, TyKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// It checks for `std::iter::Empty::default()` and suggests replacing it with + /// `std::iter::empty()`. + /// ### Why is this bad? + /// `std::iter::empty()` is the more idiomatic way. + /// ### Example + /// ```rust + /// let _ = std::iter::Empty::::default(); + /// let iter: std::iter::Empty = std::iter::Empty::default(); + /// ``` + /// Use instead: + /// ```rust + /// let _ = std::iter::empty::(); + /// let iter: std::iter::Empty = std::iter::empty(); + /// ``` + #[clippy::version = "1.63.0"] + pub DEFAULT_INSTEAD_OF_ITER_EMPTY, + style, + "check `std::iter::Empty::default()` and replace with `std::iter::empty()`" +} +declare_lint_pass!(DefaultIterEmpty => [DEFAULT_INSTEAD_OF_ITER_EMPTY]); + +impl<'tcx> LateLintPass<'tcx> for DefaultIterEmpty { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Call(iter_expr, []) = &expr.kind + && let ExprKind::Path(QPath::TypeRelative(ty, _)) = &iter_expr.kind + && let TyKind::Path(ty_path) = &ty.kind + && let QPath::Resolved(None, path) = ty_path + && let def::Res::Def(_, def_id) = &path.res + && match_def_path(cx, *def_id, &paths::ITER_EMPTY) + { + let mut applicability = Applicability::MachineApplicable; + let sugg = make_sugg(cx, ty_path, &mut applicability); + span_lint_and_sugg( + cx, + DEFAULT_INSTEAD_OF_ITER_EMPTY, + expr.span, + "`std::iter::empty()` is the more idiomatic way", + "try", + sugg, + applicability, + ); + } + } +} + +fn make_sugg(cx: &LateContext<'_>, ty_path: &rustc_hir::QPath<'_>, applicability: &mut Applicability) -> String { + if let Some(last) = last_path_segment(ty_path).args + && let Some(iter_ty) = last.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }) + { + format!("std::iter::empty::<{}>()", snippet_with_applicability(cx, iter_ty.span, "..", applicability)) + } else { + "std::iter::empty()".to_owned() + } +} diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index 5d5ea0f49c8..9aa5af3190f 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -1,16 +1,21 @@ -// NOTE: if you add a deprecated lint in this file, please add a corresponding test in -// tests/ui/deprecated.rs +// NOTE: Entries should be created with `cargo dev deprecate` /// This struct fakes the `Lint` declaration that is usually created by `declare_lint!`. This /// enables the simple extraction of the metadata without changing the current deprecation /// declaration. -pub struct ClippyDeprecatedLint; +pub struct ClippyDeprecatedLint { + #[allow(dead_code)] + pub desc: &'static str, +} +#[macro_export] macro_rules! declare_deprecated_lint { - { $(#[$attr:meta])* pub $name: ident, $_reason: expr} => { + { $(#[$attr:meta])* pub $name: ident, $reason: literal} => { $(#[$attr])* #[allow(dead_code)] - pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint {}; + pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint { + desc: $reason + }; } } diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index b47441eff37..59dcc1ebf19 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1,20 +1,24 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::ty::peel_mid_ty_refs; -use clippy_utils::{get_parent_expr, get_parent_node, is_lint_allowed, path_to_local}; +use clippy_utils::ty::{expr_sig, peel_mid_ty_refs, variant_of_res}; +use clippy_utils::{get_parent_expr, is_lint_allowed, path_to_local, walk_to_expr_usage}; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_ty, Visitor}; use rustc_hir::{ - BindingAnnotation, Body, BodyId, BorrowKind, Destination, Expr, ExprKind, HirId, MatchSource, Mutability, Node, - Pat, PatKind, UnOp, + self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, GenericArg, HirId, ImplItem, + ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem, + TraitItemKind, TyKind, UnOp, }; +use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; -use rustc_middle::ty::{self, Ty, TyCtxt, TypeckResults}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeckResults}; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::{symbol::sym, Span}; +use rustc_span::{symbol::sym, Span, Symbol}; +use rustc_trait_selection::infer::InferCtxtExt; declare_clippy_lint! { /// ### What it does @@ -104,10 +108,34 @@ "`ref` binding to a reference" } +declare_clippy_lint! { + /// ### What it does + /// Checks for dereferencing expressions which would be covered by auto-deref. + /// + /// ### Why is this bad? + /// This unnecessarily complicates the code. + /// + /// ### Example + /// ```rust + /// let x = String::new(); + /// let y: &str = &*x; + /// ``` + /// Use instead: + /// ```rust + /// let x = String::new(); + /// let y: &str = &x; + /// ``` + #[clippy::version = "1.60.0"] + pub EXPLICIT_AUTO_DEREF, + complexity, + "dereferencing when the compiler would automatically dereference" +} + impl_lint_pass!(Dereferencing => [ EXPLICIT_DEREF_METHODS, NEEDLESS_BORROW, REF_BINDING_TO_REFERENCE, + EXPLICIT_AUTO_DEREF, ]); #[derive(Default)] @@ -136,6 +164,12 @@ struct StateData { /// Span of the top level expression span: Span, hir_id: HirId, + position: Position, +} + +struct DerefedBorrow { + count: usize, + msg: &'static str, } enum State { @@ -147,11 +181,19 @@ enum State { /// The required mutability target_mut: Mutability, }, - DerefedBorrow { - count: usize, - required_precedence: i8, - msg: &'static str, + DerefedBorrow(DerefedBorrow), + ExplicitDeref { + // Span and id of the top-level deref expression if the parent expression is a borrow. + deref_span_id: Option<(Span, HirId)>, }, + ExplicitDerefField { + name: Symbol, + }, + Reborrow { + deref_span: Span, + deref_hir_id: HirId, + }, + Borrow, } // A reference operation considered by this lint pass @@ -207,13 +249,28 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { match (self.state.take(), kind) { (None, kind) => { - let parent = get_parent_node(cx.tcx, expr.hir_id); let expr_ty = typeck.expr_ty(expr); + let (position, adjustments) = walk_parents(cx, expr); match kind { + RefOp::Deref => { + if let Position::FieldAccess(name) = position + && !ty_contains_field(typeck.expr_ty(sub_expr), name) + { + self.state = Some(( + State::ExplicitDerefField { name }, + StateData { span: expr.span, hir_id: expr.hir_id, position }, + )); + } else if position.is_deref_stable() { + self.state = Some(( + State::ExplicitDeref { deref_span_id: None }, + StateData { span: expr.span, hir_id: expr.hir_id, position }, + )); + } + } RefOp::Method(target_mut) if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id) - && is_linted_explicit_deref_position(parent, expr.hir_id, expr.span) => + && position.lint_explicit_deref() => { self.state = Some(( State::DerefMethod { @@ -228,12 +285,13 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { StateData { span: expr.span, hir_id: expr.hir_id, + position }, )); }, RefOp::AddrOf => { // Find the number of times the borrow is auto-derefed. - let mut iter = find_adjustments(cx.tcx, typeck, expr).iter(); + let mut iter = adjustments.iter(); let mut deref_count = 0usize; let next_adjust = loop { match iter.next() { @@ -274,40 +332,43 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { "this expression creates a reference which is immediately dereferenced by the compiler"; let borrow_msg = "this expression borrows a value the compiler would automatically borrow"; - let (required_refs, required_precedence, msg) = if is_auto_borrow_position(parent, expr.hir_id) - { - (1, PREC_POSTFIX, if deref_count == 1 { borrow_msg } else { deref_msg }) + let (required_refs, msg) = if position.can_auto_borrow() { + (1, if deref_count == 1 { borrow_msg } else { deref_msg }) } else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) = next_adjust.map(|a| &a.kind) { - if matches!(mutability, AutoBorrowMutability::Mut { .. }) - && !is_auto_reborrow_position(parent) + if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !position.is_reborrow_stable() { - (3, 0, deref_msg) + (3, deref_msg) } else { - (2, 0, deref_msg) + (2, deref_msg) } } else { - (2, 0, deref_msg) + (2, deref_msg) }; if deref_count >= required_refs { self.state = Some(( - State::DerefedBorrow { + State::DerefedBorrow(DerefedBorrow { // One of the required refs is for the current borrow expression, the remaining ones // can't be removed without breaking the code. See earlier comment. count: deref_count - required_refs, - required_precedence, msg, - }, + }), + StateData { span: expr.span, hir_id: expr.hir_id, position }, + )); + } else if position.is_deref_stable() { + self.state = Some(( + State::Borrow, StateData { span: expr.span, hir_id: expr.hir_id, + position }, )); } }, - _ => (), + RefOp::Method(..) => (), } }, ( @@ -334,26 +395,90 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { data, )); }, + (Some((State::DerefedBorrow(state), data)), RefOp::AddrOf) if state.count != 0 => { + self.state = Some(( + State::DerefedBorrow(DerefedBorrow { + count: state.count - 1, + ..state + }), + data, + )); + }, + (Some((State::DerefedBorrow(state), data)), RefOp::AddrOf) => { + let position = data.position; + report(cx, expr, State::DerefedBorrow(state), data); + if position.is_deref_stable() { + self.state = Some(( + State::Borrow, + StateData { + span: expr.span, + hir_id: expr.hir_id, + position, + }, + )); + } + }, + (Some((State::DerefedBorrow(state), data)), RefOp::Deref) => { + let position = data.position; + report(cx, expr, State::DerefedBorrow(state), data); + if let Position::FieldAccess(name) = position + && !ty_contains_field(typeck.expr_ty(sub_expr), name) + { + self.state = Some(( + State::ExplicitDerefField { name }, + StateData { span: expr.span, hir_id: expr.hir_id, position }, + )); + } else if position.is_deref_stable() { + self.state = Some(( + State::ExplicitDeref { deref_span_id: None }, + StateData { span: expr.span, hir_id: expr.hir_id, position }, + )); + } + }, + + (Some((State::Borrow, data)), RefOp::Deref) => { + if typeck.expr_ty(sub_expr).is_ref() { + self.state = Some(( + State::Reborrow { + deref_span: expr.span, + deref_hir_id: expr.hir_id, + }, + data, + )); + } else { + self.state = Some(( + State::ExplicitDeref { + deref_span_id: Some((expr.span, expr.hir_id)), + }, + data, + )); + } + }, ( Some(( - State::DerefedBorrow { - count, - required_precedence, - msg, + State::Reborrow { + deref_span, + deref_hir_id, }, data, )), - RefOp::AddrOf, - ) if count != 0 => { + RefOp::Deref, + ) => { self.state = Some(( - State::DerefedBorrow { - count: count - 1, - required_precedence, - msg, + State::ExplicitDeref { + deref_span_id: Some((deref_span, deref_hir_id)), }, data, )); }, + (state @ Some((State::ExplicitDeref { .. }, _)), RefOp::Deref) => { + self.state = state; + }, + (Some((State::ExplicitDerefField { name }, data)), RefOp::Deref) + if !ty_contains_field(typeck.expr_ty(sub_expr), name) => + { + self.state = Some((State::ExplicitDerefField { name }, data)); + }, (Some((state, data)), _) => report(cx, expr, state, data), } @@ -473,131 +598,362 @@ fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool { } } -// Checks whether the parent node is a suitable context for switching from a deref method to the -// deref operator. -fn is_linted_explicit_deref_position(parent: Option>, child_id: HirId, child_span: Span) -> bool { - let parent = match parent { - Some(Node::Expr(e)) if e.span.ctxt() == child_span.ctxt() => e, - _ => return true, - }; - match parent.kind { - // Leave deref calls in the middle of a method chain. - // e.g. x.deref().foo() - ExprKind::MethodCall(_, [self_arg, ..], _) if self_arg.hir_id == child_id => false, - - // Leave deref calls resulting in a called function - // e.g. (x.deref())() - ExprKind::Call(func_expr, _) if func_expr.hir_id == child_id => false, - - // Makes an ugly suggestion - // e.g. *x.deref() => *&*x - ExprKind::Unary(UnOp::Deref, _) - // Postfix expressions would require parens - | ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) - | ExprKind::Field(..) - | ExprKind::Index(..) - | ExprKind::Err => false, - - ExprKind::Box(..) - | ExprKind::ConstBlock(..) - | ExprKind::Array(_) - | ExprKind::Call(..) - | ExprKind::MethodCall(..) - | ExprKind::Tup(..) - | ExprKind::Binary(..) - | ExprKind::Unary(..) - | ExprKind::Lit(..) - | ExprKind::Cast(..) - | ExprKind::Type(..) - | ExprKind::DropTemps(..) - | ExprKind::If(..) - | ExprKind::Loop(..) - | ExprKind::Match(..) - | ExprKind::Let(..) - | ExprKind::Closure{..} - | ExprKind::Block(..) - | ExprKind::Assign(..) - | ExprKind::AssignOp(..) - | ExprKind::Path(..) - | ExprKind::AddrOf(..) - | ExprKind::Break(..) - | ExprKind::Continue(..) - | ExprKind::Ret(..) - | ExprKind::InlineAsm(..) - | ExprKind::Struct(..) - | ExprKind::Repeat(..) - | ExprKind::Yield(..) => true, - } +/// The position of an expression relative to it's parent. +#[derive(Clone, Copy)] +enum Position { + MethodReceiver, + /// The method is defined on a reference type. e.g. `impl Foo for &T` + MethodReceiverRefImpl, + Callee, + FieldAccess(Symbol), + Postfix, + Deref, + /// Any other location which will trigger auto-deref to a specific time. + DerefStable(i8), + /// Any other location which will trigger auto-reborrowing. + ReborrowStable(i8), + Other(i8), } - -/// Checks if the given expression is in a position which can be auto-reborrowed. -/// Note: This is only correct assuming auto-deref is already occurring. -fn is_auto_reborrow_position(parent: Option>) -> bool { - match parent { - Some(Node::Expr(parent)) => matches!(parent.kind, ExprKind::MethodCall(..) | ExprKind::Call(..)), - Some(Node::Local(_)) => true, - _ => false, +impl Position { + fn is_deref_stable(self) -> bool { + matches!(self, Self::DerefStable(_)) } -} -/// Checks if the given expression is a position which can auto-borrow. -fn is_auto_borrow_position(parent: Option>, child_id: HirId) -> bool { - if let Some(Node::Expr(parent)) = parent { - match parent.kind { - // ExprKind::MethodCall(_, [self_arg, ..], _) => self_arg.hir_id == child_id, - ExprKind::Field(..) => true, - ExprKind::Call(f, _) => f.hir_id == child_id, - _ => false, + fn is_reborrow_stable(self) -> bool { + matches!(self, Self::DerefStable(_) | Self::ReborrowStable(_)) + } + + fn can_auto_borrow(self) -> bool { + matches!(self, Self::MethodReceiver | Self::FieldAccess(_) | Self::Callee) + } + + fn lint_explicit_deref(self) -> bool { + matches!(self, Self::Other(_) | Self::DerefStable(_) | Self::ReborrowStable(_)) + } + + fn precedence(self) -> i8 { + match self { + Self::MethodReceiver + | Self::MethodReceiverRefImpl + | Self::Callee + | Self::FieldAccess(_) + | Self::Postfix => PREC_POSTFIX, + Self::Deref => PREC_PREFIX, + Self::DerefStable(p) | Self::ReborrowStable(p) | Self::Other(p) => p, } + } +} + +/// Walks up the parent expressions attempting to determine both how stable the auto-deref result +/// is, and which adjustments will be applied to it. Note this will not consider auto-borrow +/// locations as those follow different rules. +#[allow(clippy::too_many_lines)] +fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &'tcx [Adjustment<'tcx>]) { + let mut adjustments = [].as_slice(); + let mut precedence = 0i8; + let ctxt = e.span.ctxt(); + let position = walk_to_expr_usage(cx, e, &mut |parent, child_id| { + // LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead. + if adjustments.is_empty() && let Node::Expr(e) = cx.tcx.hir().get(child_id) { + adjustments = cx.typeck_results().expr_adjustments(e); + } + match parent { + Node::Local(Local { ty: Some(ty), span, .. }) if span.ctxt() == ctxt => { + Some(binding_ty_auto_deref_stability(ty, precedence)) + }, + Node::Item(&Item { + kind: ItemKind::Static(..) | ItemKind::Const(..), + def_id, + span, + .. + }) + | Node::TraitItem(&TraitItem { + kind: TraitItemKind::Const(..), + def_id, + span, + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Const(..), + def_id, + span, + .. + }) if span.ctxt() == ctxt => { + let ty = cx.tcx.type_of(def_id); + Some(if ty.is_ref() { + Position::DerefStable(precedence) + } else { + Position::Other(precedence) + }) + }, + + Node::Item(&Item { + kind: ItemKind::Fn(..), + def_id, + span, + .. + }) + | Node::TraitItem(&TraitItem { + kind: TraitItemKind::Fn(..), + def_id, + span, + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Fn(..), + def_id, + span, + .. + }) if span.ctxt() == ctxt => { + let output = cx.tcx.fn_sig(def_id.to_def_id()).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) + }) + }, + + 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) + }) + }, + 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) + .zip(expr_sig(cx, func)) + .and_then(|(i, sig)| sig.input_with_hir(i)) + .map(|(hir_ty, ty)| match hir_ty { + // Type inference for closures can depend on how they're called. Only go by the explicit + // types here. + Some(ty) => binding_ty_auto_deref_stability(ty, precedence), + None => param_auto_deref_stability(ty.skip_binder(), precedence), + }), + ExprKind::MethodCall(_, args, _) => { + let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(); + args.iter().position(|arg| arg.hir_id == child_id).map(|i| { + if i == 0 { + // Check for calls to trait methods where the trait is implemented on a reference. + // Two cases need to be handled: + // * `self` methods on `&T` will never have auto-borrow + // * `&self` methods on `&T` can have auto-borrow, but `&self` methods on `T` will take + // priority. + if e.hir_id != child_id { + Position::ReborrowStable(precedence) + } 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() { + // Trait methods taking `&self` + sub_ty + } else { + // Trait methods taking `self` + arg_ty + } && impl_ty.is_ref() + && cx.tcx.infer_ctxt().enter(|infcx| + infcx + .type_implements_trait(trait_id, impl_ty, subs, cx.param_env) + .must_apply_modulo_regions() + ) + { + Position::MethodReceiverRefImpl + } else { + Position::MethodReceiver + } + } else { + param_auto_deref_stability(cx.tcx.fn_sig(id).skip_binder().inputs()[i], precedence) + } + }) + }, + ExprKind::Struct(path, fields, _) => { + let variant = variant_of_res(cx, cx.qpath_res(path, parent.hir_id)); + fields + .iter() + .find(|f| f.expr.hir_id == child_id) + .zip(variant) + .and_then(|(field, variant)| variant.fields.iter().find(|f| f.name == field.ident.name)) + .map(|field| param_auto_deref_stability(cx.tcx.type_of(field.did), precedence)) + }, + ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(Position::FieldAccess(name.name)), + ExprKind::Unary(UnOp::Deref, child) if child.hir_id == e.hir_id => Some(Position::Deref), + ExprKind::Match(child, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) + | ExprKind::Index(child, _) + if child.hir_id == e.hir_id => + { + Some(Position::Postfix) + }, + _ if child_id == e.hir_id => { + precedence = parent.precedence().order(); + None + }, + _ => None, + }, + _ => None, + } + }) + .unwrap_or(Position::Other(precedence)); + (position, adjustments) +} + +// Checks the stability of auto-deref when assigned to a binding with the given explicit type. +// +// e.g. +// let x = Box::new(Box::new(0u32)); +// let y1: &Box<_> = x.deref(); +// let y2: &Box<_> = &x; +// +// Here `y1` and `y2` would resolve to different types, so the type `&Box<_>` is not stable when +// switching to auto-dereferencing. +fn binding_ty_auto_deref_stability(ty: &hir::Ty<'_>, precedence: i8) -> Position { + let TyKind::Rptr(_, ty) = &ty.kind else { + return Position::Other(precedence); + }; + let mut ty = ty; + + loop { + break match ty.ty.kind { + TyKind::Rptr(_, ref ref_ty) => { + ty = ref_ty; + continue; + }, + TyKind::Path( + QPath::TypeRelative(_, path) + | QPath::Resolved( + _, + Path { + segments: [.., path], .. + }, + ), + ) => { + if let Some(args) = path.args + && args.args.iter().any(|arg| match arg { + GenericArg::Infer(_) => true, + GenericArg::Type(ty) => ty_contains_infer(ty), + _ => false, + }) + { + Position::ReborrowStable(precedence) + } else { + Position::DerefStable(precedence) + } + }, + TyKind::Slice(_) + | TyKind::Array(..) + | TyKind::BareFn(_) + | TyKind::Never + | TyKind::Tup(_) + | TyKind::Ptr(_) + | TyKind::TraitObject(..) + | TyKind::Path(_) => Position::DerefStable(precedence), + TyKind::OpaqueDef(..) + | TyKind::Infer + | TyKind::Typeof(..) + | TyKind::Err => Position::ReborrowStable(precedence), + }; + } +} + +// Checks whether a type is inferred at some point. +// e.g. `_`, `Box<_>`, `[_]` +fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool { + struct V(bool); + impl Visitor<'_> for V { + fn visit_ty(&mut self, ty: &hir::Ty<'_>) { + if self.0 + || matches!( + ty.kind, + TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(_) | TyKind::Err + ) + { + self.0 = true; + } else { + walk_ty(self, ty); + } + } + + fn visit_generic_arg(&mut self, arg: &GenericArg<'_>) { + if self.0 || matches!(arg, GenericArg::Infer(_)) { + self.0 = true; + } else if let GenericArg::Type(ty) = arg { + self.visit_ty(ty); + } + } + } + let mut v = V(false); + v.visit_ty(ty); + v.0 +} + +// Checks whether a type is stable when switching to auto dereferencing, +fn param_auto_deref_stability(ty: Ty<'_>, precedence: i8) -> Position { + let ty::Ref(_, mut ty, _) = *ty.kind() else { + return Position::Other(precedence); + }; + + loop { + break match *ty.kind() { + ty::Ref(_, ref_ty, _) => { + ty = ref_ty; + continue; + }, + ty::Infer(_) + | ty::Error(_) + | ty::Param(_) + | ty::Bound(..) + | ty::Opaque(..) + | ty::Placeholder(_) + | ty::Dynamic(..) => Position::ReborrowStable(precedence), + ty::Adt(..) if ty.has_placeholders() || ty.has_param_types_or_consts() => { + Position::ReborrowStable(precedence) + }, + ty::Adt(..) + | ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Foreign(_) + | ty::Str + | ty::Array(..) + | ty::Slice(..) + | ty::RawPtr(..) + | ty::FnDef(..) + | ty::FnPtr(_) + | ty::Closure(..) + | ty::Generator(..) + | ty::GeneratorWitness(..) + | ty::Never + | ty::Tuple(_) + | ty::Projection(_) => Position::DerefStable(precedence), + }; + } +} + +fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool { + if let ty::Adt(adt, _) = *ty.kind() { + adt.is_struct() && adt.all_fields().any(|f| f.name == name) } else { false } } -/// Adjustments are sometimes made in the parent block rather than the expression itself. -fn find_adjustments<'tcx>( - tcx: TyCtxt<'tcx>, - typeck: &'tcx TypeckResults<'tcx>, - expr: &'tcx Expr<'tcx>, -) -> &'tcx [Adjustment<'tcx>] { - let map = tcx.hir(); - let mut iter = map.parent_iter(expr.hir_id); - let mut prev = expr; - - loop { - match typeck.expr_adjustments(prev) { - [] => (), - a => break a, - }; - - match iter.next().map(|(_, x)| x) { - Some(Node::Block(_)) => { - if let Some((_, Node::Expr(e))) = iter.next() { - prev = e; - } else { - // This shouldn't happen. Blocks are always contained in an expression. - break &[]; - } - }, - Some(Node::Expr(&Expr { - kind: ExprKind::Break(Destination { target_id: Ok(id), .. }, _), - .. - })) => { - if let Some(Node::Expr(e)) = map.find(id) { - prev = e; - iter = map.parent_iter(id); - } else { - // This shouldn't happen. The destination should exist. - break &[]; - } - }, - _ => break &[], - } - } -} - -#[expect(clippy::needless_pass_by_value)] -fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: StateData) { +#[expect(clippy::needless_pass_by_value, clippy::too_many_lines)] +fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData) { match state { State::DerefMethod { ty_changed_count, @@ -647,15 +1003,14 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: S app, ); }, - State::DerefedBorrow { - required_precedence, - msg, - .. - } => { + State::DerefedBorrow(state) => { let mut app = Applicability::MachineApplicable; - let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0; - span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, msg, |diag| { - let sugg = if required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) { + 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 sugg = if !snip_is_macro + && expr.precedence().order() < data.position.precedence() + && !has_enclosing_paren(&snip) + { format!("({})", snip) } else { snip.into() @@ -663,6 +1018,48 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: S diag.span_suggestion(data.span, "change this to", sugg, app); }); }, + State::ExplicitDeref { deref_span_id } => { + let (span, hir_id, precedence) = if let Some((span, hir_id)) = deref_span_id + && !cx.typeck_results().expr_ty(expr).is_ref() + { + (span, hir_id, PREC_PREFIX) + } else { + (data.span, data.hir_id, data.position.precedence()) + }; + span_lint_hir_and_then( + cx, + EXPLICIT_AUTO_DEREF, + hir_id, + span, + "deref which would be done by auto-deref", + |diag| { + let mut app = Applicability::MachineApplicable; + let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, span.ctxt(), "..", &mut app); + let sugg = + if !snip_is_macro && expr.precedence().order() < precedence && !has_enclosing_paren(&snip) { + format!("({})", snip) + } else { + snip.into() + }; + diag.span_suggestion(span, "try this", sugg, app); + }, + ); + }, + State::ExplicitDerefField { .. } => { + span_lint_hir_and_then( + cx, + EXPLICIT_AUTO_DEREF, + data.hir_id, + data.span, + "deref which would be done by auto-deref", + |diag| { + let mut app = Applicability::MachineApplicable; + let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0; + diag.span_suggestion(data.span, "try this", snip.into_owned(), app); + }, + ); + }, + State::Borrow | State::Reborrow { .. } => (), } } diff --git a/clippy_lints/src/double_comparison.rs b/clippy_lints/src/double_comparison.rs deleted file mode 100644 index ee0440e52ff..00000000000 --- a/clippy_lints/src/double_comparison.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! Lint on unnecessary double comparisons. Some examples: - -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::eq_expr_value; -use clippy_utils::source::snippet_with_applicability; -use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::Span; - -declare_clippy_lint! { - /// ### What it does - /// Checks for double comparisons that could be simplified to a single expression. - /// - /// - /// ### Why is this bad? - /// Readability. - /// - /// ### Example - /// ```rust - /// # let x = 1; - /// # let y = 2; - /// if x == y || x < y {} - /// ``` - /// - /// Use instead: - /// - /// ```rust - /// # let x = 1; - /// # let y = 2; - /// if x <= y {} - /// ``` - #[clippy::version = "pre 1.29.0"] - pub DOUBLE_COMPARISONS, - complexity, - "unnecessary double comparisons that can be simplified" -} - -declare_lint_pass!(DoubleComparisons => [DOUBLE_COMPARISONS]); - -impl<'tcx> DoubleComparisons { - #[expect(clippy::similar_names)] - fn check_binop(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) { - let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) { - (ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => { - (lb.node, llhs, lrhs, rb.node, rlhs, rrhs) - }, - _ => return, - }; - if !(eq_expr_value(cx, llhs, rlhs) && eq_expr_value(cx, lrhs, rrhs)) { - return; - } - macro_rules! lint_double_comparison { - ($op:tt) => {{ - let mut applicability = Applicability::MachineApplicable; - let lhs_str = snippet_with_applicability(cx, llhs.span, "", &mut applicability); - let rhs_str = snippet_with_applicability(cx, lrhs.span, "", &mut applicability); - let sugg = format!("{} {} {}", lhs_str, stringify!($op), rhs_str); - span_lint_and_sugg( - cx, - DOUBLE_COMPARISONS, - span, - "this binary expression can be simplified", - "try", - sugg, - applicability, - ); - }}; - } - #[rustfmt::skip] - match (op, lkind, rkind) { - (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Lt) | (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Eq) => { - lint_double_comparison!(<=); - }, - (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Eq) => { - lint_double_comparison!(>=); - }, - (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Lt) => { - lint_double_comparison!(!=); - }, - (BinOpKind::And, BinOpKind::Le, BinOpKind::Ge) | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => { - lint_double_comparison!(==); - }, - _ => (), - }; - } -} - -impl<'tcx> LateLintPass<'tcx> for DoubleComparisons { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::Binary(ref kind, lhs, rhs) = expr.kind { - Self::check_binop(cx, kind.node, lhs, rhs, expr.span); - } - } -} diff --git a/clippy_lints/src/duration_subsec.rs b/clippy_lints/src/duration_subsec.rs deleted file mode 100644 index d85ace3a279..00000000000 --- a/clippy_lints/src/duration_subsec.rs +++ /dev/null @@ -1,75 +0,0 @@ -use clippy_utils::consts::{constant, Constant}; -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, 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::sym; - -declare_clippy_lint! { - /// ### What it does - /// Checks for calculation of subsecond microseconds or milliseconds - /// from other `Duration` methods. - /// - /// ### Why is this bad? - /// It's more concise to call `Duration::subsec_micros()` or - /// `Duration::subsec_millis()` than to calculate them. - /// - /// ### Example - /// ```rust - /// # use std::time::Duration; - /// # let duration = Duration::new(5, 0); - /// let micros = duration.subsec_nanos() / 1_000; - /// let millis = duration.subsec_nanos() / 1_000_000; - /// ``` - /// - /// Use instead: - /// ```rust - /// # use std::time::Duration; - /// # let duration = Duration::new(5, 0); - /// let micros = duration.subsec_micros(); - /// let millis = duration.subsec_millis(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub DURATION_SUBSEC, - complexity, - "checks for calculation of subsecond microseconds or milliseconds" -} - -declare_lint_pass!(DurationSubsec => [DURATION_SUBSEC]); - -impl<'tcx> LateLintPass<'tcx> for DurationSubsec { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if_chain! { - if let ExprKind::Binary(Spanned { node: BinOpKind::Div, .. }, left, right) = expr.kind; - if let ExprKind::MethodCall(method_path, args, _) = left.kind; - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), sym::Duration); - if let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right); - then { - let suggested_fn = match (method_path.ident.as_str(), divisor) { - ("subsec_micros", 1_000) | ("subsec_nanos", 1_000_000) => "subsec_millis", - ("subsec_nanos", 1_000) => "subsec_micros", - _ => return, - }; - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - DURATION_SUBSEC, - expr.span, - &format!("calling `{}()` is more concise than this calculation", suggested_fn), - "try", - format!( - "{}.{}()", - snippet_with_applicability(cx, args[0].span, "_", &mut applicability), - suggested_fn - ), - applicability, - ); - } - } - } -} diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs index 23b75104570..cd36f9fcd72 100644 --- a/clippy_lints/src/enum_variants.rs +++ b/clippy_lints/src/enum_variants.rs @@ -190,7 +190,7 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n .map(|e| *e.0) .collect(); } - let (what, value) = match (pre.is_empty(), post.is_empty()) { + let (what, value) = match (have_no_extra_prefix(&pre), post.is_empty()) { (true, true) => return, (false, _) => ("pre", pre.join("")), (true, false) => { @@ -212,6 +212,11 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n ); } +#[must_use] +fn have_no_extra_prefix(prefixes: &[&str]) -> bool { + prefixes.iter().all(|p| p == &"" || p == &"_") +} + #[must_use] fn to_camel_case(item_name: &str) -> String { let mut s = String::new(); diff --git a/clippy_lints/src/eq_op.rs b/clippy_lints/src/eq_op.rs deleted file mode 100644 index 2f4c90d07cf..00000000000 --- a/clippy_lints/src/eq_op.rs +++ /dev/null @@ -1,319 +0,0 @@ -use clippy_utils::diagnostics::{multispan_sugg, span_lint, span_lint_and_then}; -use clippy_utils::get_enclosing_block; -use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace}; -use clippy_utils::source::snippet; -use clippy_utils::ty::{implements_trait, is_copy}; -use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, is_in_test_function}; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::{def::Res, def_id::DefId, BinOpKind, BorrowKind, Expr, ExprKind, GenericArg, ItemKind, QPath, TyKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, Ty}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for equal operands to comparison, logical and - /// bitwise, difference and division binary operators (`==`, `>`, etc., `&&`, - /// `||`, `&`, `|`, `^`, `-` and `/`). - /// - /// ### Why is this bad? - /// This is usually just a typo or a copy and paste error. - /// - /// ### Known problems - /// False negatives: We had some false positives regarding - /// calls (notably [racer](https://github.com/phildawes/racer) had one instance - /// of `x.pop() && x.pop()`), so we removed matching any function or method - /// calls. We may introduce a list of known pure functions in the future. - /// - /// ### Example - /// ```rust - /// # let x = 1; - /// if x + 1 == x + 1 {} - /// - /// // or - /// - /// # let a = 3; - /// # let b = 4; - /// assert_eq!(a, a); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub EQ_OP, - correctness, - "equal operands on both sides of a comparison or bitwise combination (e.g., `x == x`)" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for arguments to `==` which have their address - /// taken to satisfy a bound - /// and suggests to dereference the other argument instead - /// - /// ### Why is this bad? - /// It is more idiomatic to dereference the other argument. - /// - /// ### Example - /// ```rust,ignore - /// &x == y - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// x == *y - /// ``` - #[clippy::version = "pre 1.29.0"] - pub OP_REF, - style, - "taking a reference to satisfy the type constraints on `==`" -} - -declare_lint_pass!(EqOp => [EQ_OP, OP_REF]); - -impl<'tcx> LateLintPass<'tcx> for EqOp { - #[expect(clippy::similar_names, clippy::too_many_lines)] - fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if_chain! { - if let Some((macro_call, macro_name)) = first_node_macro_backtrace(cx, e).find_map(|macro_call| { - let name = cx.tcx.item_name(macro_call.def_id); - matches!(name.as_str(), "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne") - .then(|| (macro_call, name)) - }); - if let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn); - if eq_expr_value(cx, lhs, rhs); - if macro_call.is_local(); - if !is_in_test_function(cx.tcx, e.hir_id); - then { - span_lint( - cx, - EQ_OP, - lhs.span.to(rhs.span), - &format!("identical args used in this `{}!` macro call", macro_name), - ); - } - } - if let ExprKind::Binary(op, left, right) = e.kind { - if e.span.from_expansion() { - return; - } - let macro_with_not_op = |expr_kind: &ExprKind<'_>| { - if let ExprKind::Unary(_, expr) = *expr_kind { - expr.span.from_expansion() - } else { - false - } - }; - if macro_with_not_op(&left.kind) || macro_with_not_op(&right.kind) { - return; - } - if is_useless_with_eq_exprs(op.node.into()) - && eq_expr_value(cx, left, right) - && !is_in_test_function(cx.tcx, e.hir_id) - { - span_lint( - cx, - EQ_OP, - e.span, - &format!("equal expressions as operands to `{}`", op.node.as_str()), - ); - return; - } - let (trait_id, requires_ref) = match op.node { - BinOpKind::Add => (cx.tcx.lang_items().add_trait(), false), - BinOpKind::Sub => (cx.tcx.lang_items().sub_trait(), false), - BinOpKind::Mul => (cx.tcx.lang_items().mul_trait(), false), - BinOpKind::Div => (cx.tcx.lang_items().div_trait(), false), - BinOpKind::Rem => (cx.tcx.lang_items().rem_trait(), false), - // don't lint short circuiting ops - BinOpKind::And | BinOpKind::Or => return, - BinOpKind::BitXor => (cx.tcx.lang_items().bitxor_trait(), false), - BinOpKind::BitAnd => (cx.tcx.lang_items().bitand_trait(), false), - BinOpKind::BitOr => (cx.tcx.lang_items().bitor_trait(), false), - BinOpKind::Shl => (cx.tcx.lang_items().shl_trait(), false), - BinOpKind::Shr => (cx.tcx.lang_items().shr_trait(), false), - BinOpKind::Ne | BinOpKind::Eq => (cx.tcx.lang_items().eq_trait(), true), - BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ge | BinOpKind::Gt => { - (cx.tcx.lang_items().partial_ord_trait(), true) - }, - }; - if let Some(trait_id) = trait_id { - match (&left.kind, &right.kind) { - // do not suggest to dereference literals - (&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {}, - // &foo == &bar - (&ExprKind::AddrOf(BorrowKind::Ref, _, l), &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => { - let lty = cx.typeck_results().expr_ty(l); - let rty = cx.typeck_results().expr_ty(r); - let lcpy = is_copy(cx, lty); - let rcpy = is_copy(cx, rty); - if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) { - if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) - || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty)) - { - return; // Don't lint - } - } - // either operator autorefs or both args are copyable - if (requires_ref || (lcpy && rcpy)) && implements_trait(cx, lty, trait_id, &[rty.into()]) { - span_lint_and_then( - cx, - OP_REF, - e.span, - "needlessly taken reference of both operands", - |diag| { - let lsnip = snippet(cx, l.span, "...").to_string(); - let rsnip = snippet(cx, r.span, "...").to_string(); - multispan_sugg( - diag, - "use the values directly", - vec![(left.span, lsnip), (right.span, rsnip)], - ); - }, - ); - } else if lcpy - && !rcpy - && implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()]) - { - span_lint_and_then( - cx, - OP_REF, - e.span, - "needlessly taken reference of left operand", - |diag| { - let lsnip = snippet(cx, l.span, "...").to_string(); - diag.span_suggestion( - left.span, - "use the left value directly", - lsnip, - Applicability::MaybeIncorrect, // FIXME #2597 - ); - }, - ); - } else if !lcpy - && rcpy - && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()]) - { - span_lint_and_then( - cx, - OP_REF, - e.span, - "needlessly taken reference of right operand", - |diag| { - let rsnip = snippet(cx, r.span, "...").to_string(); - diag.span_suggestion( - right.span, - "use the right value directly", - rsnip, - Applicability::MaybeIncorrect, // FIXME #2597 - ); - }, - ); - } - }, - // &foo == bar - (&ExprKind::AddrOf(BorrowKind::Ref, _, l), _) => { - let lty = cx.typeck_results().expr_ty(l); - if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) { - let rty = cx.typeck_results().expr_ty(right); - if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) - || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty)) - { - return; // Don't lint - } - } - let lcpy = is_copy(cx, lty); - if (requires_ref || lcpy) - && implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()]) - { - span_lint_and_then( - cx, - OP_REF, - e.span, - "needlessly taken reference of left operand", - |diag| { - let lsnip = snippet(cx, l.span, "...").to_string(); - diag.span_suggestion( - left.span, - "use the left value directly", - lsnip, - Applicability::MaybeIncorrect, // FIXME #2597 - ); - }, - ); - } - }, - // foo == &bar - (_, &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => { - let rty = cx.typeck_results().expr_ty(r); - if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) { - let lty = cx.typeck_results().expr_ty(left); - if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) - || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty)) - { - return; // Don't lint - } - } - let rcpy = is_copy(cx, rty); - if (requires_ref || rcpy) - && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()]) - { - span_lint_and_then(cx, OP_REF, e.span, "taken reference of right operand", |diag| { - let rsnip = snippet(cx, r.span, "...").to_string(); - diag.span_suggestion( - right.span, - "use the right value directly", - rsnip, - Applicability::MaybeIncorrect, // FIXME #2597 - ); - }); - } - }, - _ => {}, - } - } - } - } -} - -fn in_impl<'tcx>( - cx: &LateContext<'tcx>, - e: &'tcx Expr<'_>, - bin_op: DefId, -) -> Option<(&'tcx rustc_hir::Ty<'tcx>, &'tcx rustc_hir::Ty<'tcx>)> { - if_chain! { - if let Some(block) = get_enclosing_block(cx, e.hir_id); - if let Some(impl_def_id) = cx.tcx.impl_of_method(block.hir_id.owner.to_def_id()); - let item = cx.tcx.hir().expect_item(impl_def_id.expect_local()); - if let ItemKind::Impl(item) = &item.kind; - if let Some(of_trait) = &item.of_trait; - if let Some(seg) = of_trait.path.segments.last(); - if let Some(Res::Def(_, trait_id)) = seg.res; - if trait_id == bin_op; - if let Some(generic_args) = seg.args; - if let Some(GenericArg::Type(other_ty)) = generic_args.args.last(); - - then { - Some((item.self_ty, other_ty)) - } - else { - None - } - } -} - -fn are_equal<'tcx>(cx: &LateContext<'tcx>, middle_ty: Ty<'_>, hir_ty: &rustc_hir::Ty<'_>) -> bool { - if_chain! { - if let ty::Adt(adt_def, _) = middle_ty.kind(); - if let Some(local_did) = adt_def.did().as_local(); - let item = cx.tcx.hir().expect_item(local_did); - let middle_ty_id = item.def_id.to_def_id(); - if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind; - if let Res::Def(_, hir_ty_id) = path.res; - - then { - hir_ty_id == middle_ty_id - } - else { - false - } - } -} diff --git a/clippy_lints/src/erasing_op.rs b/clippy_lints/src/erasing_op.rs deleted file mode 100644 index c1a84973c42..00000000000 --- a/clippy_lints/src/erasing_op.rs +++ /dev/null @@ -1,77 +0,0 @@ -use clippy_utils::consts::{constant_simple, Constant}; -use clippy_utils::diagnostics::span_lint; -use clippy_utils::ty::same_type_and_consts; - -use rustc_hir::{BinOpKind, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TypeckResults; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for erasing operations, e.g., `x * 0`. - /// - /// ### Why is this bad? - /// The whole expression can be replaced by zero. - /// This is most likely not the intended outcome and should probably be - /// corrected - /// - /// ### Example - /// ```rust - /// let x = 1; - /// 0 / x; - /// 0 * x; - /// x & 0; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub ERASING_OP, - correctness, - "using erasing operations, e.g., `x * 0` or `y & 0`" -} - -declare_lint_pass!(ErasingOp => [ERASING_OP]); - -impl<'tcx> LateLintPass<'tcx> for ErasingOp { - fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if e.span.from_expansion() { - return; - } - if let ExprKind::Binary(ref cmp, left, right) = e.kind { - let tck = cx.typeck_results(); - match cmp.node { - BinOpKind::Mul | BinOpKind::BitAnd => { - check(cx, tck, left, right, e); - check(cx, tck, right, left, e); - }, - BinOpKind::Div => check(cx, tck, left, right, e), - _ => (), - } - } - } -} - -fn different_types(tck: &TypeckResults<'_>, input: &Expr<'_>, output: &Expr<'_>) -> bool { - let input_ty = tck.expr_ty(input).peel_refs(); - let output_ty = tck.expr_ty(output).peel_refs(); - !same_type_and_consts(input_ty, output_ty) -} - -fn check<'tcx>( - cx: &LateContext<'tcx>, - tck: &TypeckResults<'tcx>, - op: &Expr<'tcx>, - other: &Expr<'tcx>, - parent: &Expr<'tcx>, -) { - if constant_simple(cx, tck, op) == Some(Constant::Int(0)) { - if different_types(tck, other, parent) { - return; - } - span_lint( - cx, - ERASING_OP, - parent.span, - "this operation will always return zero. This is likely not the intended outcome", - ); - } -} diff --git a/clippy_lints/src/escape.rs b/clippy_lints/src/escape.rs index 9d21dd71e0e..7a65b849a66 100644 --- a/clippy_lints/src/escape.rs +++ b/clippy_lints/src/escape.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::span_lint_hir; use clippy_utils::ty::contains_ty; use rustc_hir::intravisit; use rustc_hir::{self, AssocItemKind, Body, FnDecl, HirId, HirIdSet, Impl, ItemKind, Node}; @@ -118,9 +118,10 @@ fn check_fn( }); for node in v.set { - span_lint( + span_lint_hir( cx, BOXED_LOCAL, + node, cx.tcx.hir().span(node), "local variable doesn't need to be boxed here", ); diff --git a/clippy_lints/src/float_equality_without_abs.rs b/clippy_lints/src/float_equality_without_abs.rs deleted file mode 100644 index 98aee7592ae..00000000000 --- a/clippy_lints/src/float_equality_without_abs.rs +++ /dev/null @@ -1,116 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{match_def_path, paths, sugg}; -use if_chain::if_chain; -use rustc_ast::util::parser::AssocOp; -use rustc_errors::Applicability; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{BinOpKind, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::Spanned; - -declare_clippy_lint! { - /// ### What it does - /// Checks for statements of the form `(a - b) < f32::EPSILON` or - /// `(a - b) < f64::EPSILON`. Notes the missing `.abs()`. - /// - /// ### Why is this bad? - /// The code without `.abs()` is more likely to have a bug. - /// - /// ### Known problems - /// If the user can ensure that b is larger than a, the `.abs()` is - /// technically unnecessary. However, it will make the code more robust and doesn't have any - /// large performance implications. If the abs call was deliberately left out for performance - /// reasons, it is probably better to state this explicitly in the code, which then can be done - /// with an allow. - /// - /// ### Example - /// ```rust - /// pub fn is_roughly_equal(a: f32, b: f32) -> bool { - /// (a - b) < f32::EPSILON - /// } - /// ``` - /// Use instead: - /// ```rust - /// pub fn is_roughly_equal(a: f32, b: f32) -> bool { - /// (a - b).abs() < f32::EPSILON - /// } - /// ``` - #[clippy::version = "1.48.0"] - pub FLOAT_EQUALITY_WITHOUT_ABS, - suspicious, - "float equality check without `.abs()`" -} - -declare_lint_pass!(FloatEqualityWithoutAbs => [FLOAT_EQUALITY_WITHOUT_ABS]); - -impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let lhs; - let rhs; - - // check if expr is a binary expression with a lt or gt operator - if let ExprKind::Binary(op, left, right) = expr.kind { - match op.node { - BinOpKind::Lt => { - lhs = left; - rhs = right; - }, - BinOpKind::Gt => { - lhs = right; - rhs = left; - }, - _ => return, - }; - } else { - return; - } - - if_chain! { - - // left hand side is a subtraction - if let ExprKind::Binary( - Spanned { - node: BinOpKind::Sub, - .. - }, - val_l, - val_r, - ) = lhs.kind; - - // right hand side matches either f32::EPSILON or f64::EPSILON - if let ExprKind::Path(ref epsilon_path) = rhs.kind; - if let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id); - if match_def_path(cx, def_id, &paths::F32_EPSILON) || match_def_path(cx, def_id, &paths::F64_EPSILON); - - // values of the subtractions on the left hand side are of the type float - let t_val_l = cx.typeck_results().expr_ty(val_l); - let t_val_r = cx.typeck_results().expr_ty(val_r); - if let ty::Float(_) = t_val_l.kind(); - if let ty::Float(_) = t_val_r.kind(); - - then { - let sug_l = sugg::Sugg::hir(cx, val_l, ".."); - let sug_r = sugg::Sugg::hir(cx, val_r, ".."); - // format the suggestion - let suggestion = format!("{}.abs()", sugg::make_assoc(AssocOp::Subtract, &sug_l, &sug_r).maybe_par()); - // spans the lint - span_lint_and_then( - cx, - FLOAT_EQUALITY_WITHOUT_ABS, - expr.span, - "float equality check without `.abs()`", - | diag | { - diag.span_suggestion( - lhs.span, - "add `.abs()`", - suggestion, - Applicability::MaybeIncorrect, - ); - } - ); - } - } - } -} diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index 647947d5d30..a6610ade37e 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -1,5 +1,5 @@ use clippy_utils::{ - diagnostics::span_lint_and_sugg, + diagnostics::span_lint_hir_and_then, get_async_fn_body, is_async_fn, source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}, visitors::expr_visitor_no_bodies, @@ -43,31 +43,38 @@ declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]); -fn lint_return(cx: &LateContext<'_>, span: Span) { +fn lint_return(cx: &LateContext<'_>, emission_place: HirId, span: Span) { let mut app = Applicability::MachineApplicable; let snip = snippet_with_applicability(cx, span, "..", &mut app); - span_lint_and_sugg( + span_lint_hir_and_then( cx, IMPLICIT_RETURN, + emission_place, span, "missing `return` statement", - "add `return` as shown", - format!("return {}", snip), - app, + |diag| { + diag.span_suggestion(span, "add `return` as shown", format!("return {}", snip), app); + }, ); } -fn lint_break(cx: &LateContext<'_>, break_span: Span, expr_span: Span) { +fn lint_break(cx: &LateContext<'_>, emission_place: HirId, break_span: Span, expr_span: Span) { let mut app = Applicability::MachineApplicable; let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0; - span_lint_and_sugg( + span_lint_hir_and_then( cx, IMPLICIT_RETURN, + emission_place, break_span, "missing `return` statement", - "change `break` to `return` as shown", - format!("return {}", snip), - app, + |diag| { + diag.span_suggestion( + break_span, + "change `break` to `return` as shown", + format!("return {}", snip), + app, + ); + }, ); } @@ -152,7 +159,7 @@ fn lint_implicit_returns( // At this point sub_expr can be `None` in async functions which either diverge, or return // the unit type. if let Some(sub_expr) = sub_expr { - lint_break(cx, e.span, sub_expr.span); + lint_break(cx, e.hir_id, e.span, sub_expr.span); } } else { // the break expression is from a macro call, add a return to the loop @@ -166,10 +173,10 @@ fn lint_implicit_returns( if add_return { #[expect(clippy::option_if_let_else)] if let Some(span) = call_site_span { - lint_return(cx, span); + lint_return(cx, expr.hir_id, span); LintLocation::Parent } else { - lint_return(cx, expr.span); + lint_return(cx, expr.hir_id, expr.span); LintLocation::Inner } } else { @@ -198,10 +205,10 @@ fn lint_implicit_returns( { #[expect(clippy::option_if_let_else)] if let Some(span) = call_site_span { - lint_return(cx, span); + lint_return(cx, expr.hir_id, span); LintLocation::Parent } else { - lint_return(cx, expr.span); + lint_return(cx, expr.hir_id, expr.span); LintLocation::Inner } }, diff --git a/clippy_lints/src/integer_division.rs b/clippy_lints/src/integer_division.rs deleted file mode 100644 index 3effba56826..00000000000 --- a/clippy_lints/src/integer_division.rs +++ /dev/null @@ -1,61 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use if_chain::if_chain; -use rustc_hir as hir; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for division of integers - /// - /// ### Why is this bad? - /// When outside of some very specific algorithms, - /// integer division is very often a mistake because it discards the - /// remainder. - /// - /// ### Example - /// ```rust - /// let x = 3 / 2; - /// println!("{}", x); - /// ``` - /// - /// Use instead: - /// ```rust - /// let x = 3f32 / 2f32; - /// println!("{}", x); - /// ``` - #[clippy::version = "1.37.0"] - pub INTEGER_DIVISION, - restriction, - "integer division may cause loss of precision" -} - -declare_lint_pass!(IntegerDivision => [INTEGER_DIVISION]); - -impl<'tcx> LateLintPass<'tcx> for IntegerDivision { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - if is_integer_division(cx, expr) { - span_lint_and_help( - cx, - INTEGER_DIVISION, - expr.span, - "integer division", - None, - "division of integers may cause loss of precision. consider using floats", - ); - } - } -} - -fn is_integer_division<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) -> bool { - if_chain! { - if let hir::ExprKind::Binary(binop, left, right) = &expr.kind; - if binop.node == hir::BinOpKind::Div; - then { - let (left_ty, right_ty) = (cx.typeck_results().expr_ty(left), cx.typeck_results().expr_ty(right)); - return left_ty.is_integral() && right_ty.is_integral(); - } - } - - false -} diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index 289755bfec6..984c5cd4e37 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -24,7 +24,7 @@ /// ``` /// /// Use instead: - /// ```rust.ignore + /// ```rust,ignore /// pub static a = [0u32; 1_000_000]; /// ``` #[clippy::version = "1.44.0"] diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index 26c540e2223..176787497eb 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -99,12 +99,13 @@ declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_DROP]); -const SYNC_GUARD_PATHS: [&[&str]; 5] = [ +const SYNC_GUARD_PATHS: [&[&str]; 6] = [ &paths::MUTEX_GUARD, &paths::RWLOCK_READ_GUARD, &paths::RWLOCK_WRITE_GUARD, - &paths::PARKING_LOT_RAWMUTEX, - &paths::PARKING_LOT_RAWRWLOCK, + &paths::PARKING_LOT_MUTEX_GUARD, + &paths::PARKING_LOT_RWLOCK_READ_GUARD, + &paths::PARKING_LOT_RWLOCK_WRITE_GUARD, ]; impl<'tcx> LateLintPass<'tcx> for LetUnderscore { diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 8a2cfbff953..563ad891603 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -3,12 +3,9 @@ // Manual edits will be overwritten. store.register_group(true, "clippy::all", Some("clippy_all"), vec![ - LintId::of(absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS), 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(assign_ops::ASSIGN_OP_PATTERN), - LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP), LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC), LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS), LintId::of(attrs::DEPRECATED_CFG_ATTR), @@ -18,8 +15,6 @@ LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE), LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), - LintId::of(bit_mask::BAD_BIT_MASK), - LintId::of(bit_mask::INEFFECTIVE_BIT_MASK), LintId::of(blacklisted_name::BLACKLISTED_NAME), LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS), LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON), @@ -43,6 +38,8 @@ LintId::of(copies::IF_SAME_THEN_ELSE), LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF), LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT), + LintId::of(default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY), + LintId::of(dereference::EXPLICIT_AUTO_DEREF), LintId::of(dereference::NEEDLESS_BORROW), LintId::of(derivable_impls::DERIVABLE_IMPLS), LintId::of(derive::DERIVE_HASH_XOR_EQ), @@ -52,7 +49,6 @@ LintId::of(disallowed_types::DISALLOWED_TYPES), LintId::of(doc::MISSING_SAFETY_DOC), LintId::of(doc::NEEDLESS_DOCTEST_MAIN), - LintId::of(double_comparison::DOUBLE_COMPARISONS), LintId::of(double_parens::DOUBLE_PARENS), LintId::of(drop_forget_ref::DROP_COPY), LintId::of(drop_forget_ref::DROP_NON_DROP), @@ -62,18 +58,13 @@ LintId::of(drop_forget_ref::FORGET_REF), LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS), LintId::of(duplicate_mod::DUPLICATE_MOD), - LintId::of(duration_subsec::DURATION_SUBSEC), LintId::of(entry::MAP_ENTRY), LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT), LintId::of(enum_variants::ENUM_VARIANT_NAMES), LintId::of(enum_variants::MODULE_INCEPTION), - LintId::of(eq_op::EQ_OP), - LintId::of(eq_op::OP_REF), - LintId::of(erasing_op::ERASING_OP), LintId::of(escape::BOXED_LOCAL), LintId::of(eta_reduction::REDUNDANT_CLOSURE), LintId::of(explicit_write::EXPLICIT_WRITE), - LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS), LintId::of(float_literal::EXCESSIVE_PRECISION), LintId::of(format::USELESS_FORMAT), LintId::of(format_args::FORMAT_IN_FORMAT_ARGS), @@ -93,7 +84,6 @@ LintId::of(functions::RESULT_UNIT_ERR), LintId::of(functions::TOO_MANY_ARGUMENTS), LintId::of(get_first::GET_FIRST), - LintId::of(identity_op::IDENTITY_OP), LintId::of(if_let_mutex::IF_LET_MUTEX), LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING), LintId::of(infinite_iter::INFINITE_ITER), @@ -118,6 +108,7 @@ LintId::of(loops::FOR_KV_MAP), LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES), LintId::of(loops::ITER_NEXT_LOOP), + LintId::of(loops::MANUAL_FIND), LintId::of(loops::MANUAL_FLATTEN), LintId::of(loops::MANUAL_MEMCPY), LintId::of(loops::MISSING_SPIN_LOOP), @@ -134,6 +125,8 @@ LintId::of(manual_async_fn::MANUAL_ASYNC_FN), LintId::of(manual_bits::MANUAL_BITS), LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE), + LintId::of(manual_rem_euclid::MANUAL_REM_EUCLID), + LintId::of(manual_retain::MANUAL_RETAIN), LintId::of(manual_strip::MANUAL_STRIP), LintId::of(map_clone::MAP_CLONE), LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN), @@ -220,9 +213,6 @@ LintId::of(methods::WRONG_SELF_CONVENTION), LintId::of(methods::ZST_OFFSET), LintId::of(minmax::MIN_MAX), - LintId::of(misc::CMP_NAN), - LintId::of(misc::CMP_OWNED), - LintId::of(misc::MODULO_ONE), LintId::of(misc::SHORT_CIRCUIT_STATEMENT), LintId::of(misc::TOPLEVEL_REF_ARG), LintId::of(misc::ZERO_PTR), @@ -256,6 +246,23 @@ LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS), LintId::of(octal_escapes::OCTAL_ESCAPES), LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), + LintId::of(operators::ABSURD_EXTREME_COMPARISONS), + LintId::of(operators::ASSIGN_OP_PATTERN), + LintId::of(operators::BAD_BIT_MASK), + LintId::of(operators::CMP_NAN), + LintId::of(operators::CMP_OWNED), + LintId::of(operators::DOUBLE_COMPARISONS), + LintId::of(operators::DURATION_SUBSEC), + LintId::of(operators::EQ_OP), + LintId::of(operators::ERASING_OP), + LintId::of(operators::FLOAT_EQUALITY_WITHOUT_ABS), + LintId::of(operators::IDENTITY_OP), + LintId::of(operators::INEFFECTIVE_BIT_MASK), + LintId::of(operators::MISREFACTORED_ASSIGN_OP), + LintId::of(operators::MODULO_ONE), + LintId::of(operators::OP_REF), + LintId::of(operators::PTR_EQ), + LintId::of(operators::SELF_ASSIGNMENT), LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP), LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL), @@ -264,7 +271,6 @@ LintId::of(ptr::INVALID_NULL_PTR_USAGE), LintId::of(ptr::MUT_FROM_REF), LintId::of(ptr::PTR_ARG), - LintId::of(ptr_eq::PTR_EQ), LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST), LintId::of(question_mark::QUESTION_MARK), LintId::of(ranges::MANUAL_RANGE_CONTAINS), @@ -282,7 +288,6 @@ LintId::of(repeat_once::REPEAT_ONCE), LintId::of(returns::LET_AND_RETURN), LintId::of(returns::NEEDLESS_RETURN), - LintId::of(self_assignment::SELF_ASSIGNMENT), LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS), LintId::of(serde_api::SERDE_API_MISUSE), LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index 4f1c3673f85..3784d3c68dc 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -9,21 +9,21 @@ LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN), LintId::of(casts::CHAR_LIT_AS_U8), LintId::of(casts::UNNECESSARY_CAST), + LintId::of(dereference::EXPLICIT_AUTO_DEREF), LintId::of(derivable_impls::DERIVABLE_IMPLS), - LintId::of(double_comparison::DOUBLE_COMPARISONS), LintId::of(double_parens::DOUBLE_PARENS), - LintId::of(duration_subsec::DURATION_SUBSEC), LintId::of(explicit_write::EXPLICIT_WRITE), LintId::of(format::USELESS_FORMAT), LintId::of(functions::TOO_MANY_ARGUMENTS), - LintId::of(identity_op::IDENTITY_OP), LintId::of(int_plus_one::INT_PLUS_ONE), LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES), LintId::of(lifetimes::NEEDLESS_LIFETIMES), LintId::of(loops::EXPLICIT_COUNTER_LOOP), + LintId::of(loops::MANUAL_FIND), LintId::of(loops::MANUAL_FLATTEN), LintId::of(loops::SINGLE_ELEMENT_LOOP), LintId::of(loops::WHILE_LET_LOOP), + LintId::of(manual_rem_euclid::MANUAL_REM_EUCLID), LintId::of(manual_strip::MANUAL_STRIP), LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN), LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN), @@ -69,6 +69,9 @@ LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), LintId::of(no_effect::NO_EFFECT), LintId::of(no_effect::UNNECESSARY_OPERATION), + LintId::of(operators::DOUBLE_COMPARISONS), + LintId::of(operators::DURATION_SUBSEC), + LintId::of(operators::IDENTITY_OP), LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL), LintId::of(precedence::PRECEDENCE), diff --git a/clippy_lints/src/lib.register_correctness.rs b/clippy_lints/src/lib.register_correctness.rs index 92a3a0aabf1..7d5e65cb27a 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -3,14 +3,11 @@ // Manual edits will be overwritten. store.register_group(true, "clippy::correctness", Some("clippy_correctness"), vec![ - LintId::of(absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS), LintId::of(approx_const::APPROX_CONSTANT), LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC), LintId::of(attrs::DEPRECATED_SEMVER), LintId::of(attrs::MISMATCHED_TARGET_OS), LintId::of(attrs::USELESS_ATTRIBUTE), - LintId::of(bit_mask::BAD_BIT_MASK), - LintId::of(bit_mask::INEFFECTIVE_BIT_MASK), LintId::of(booleans::LOGIC_BUG), LintId::of(casts::CAST_REF_TO_MUT), LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES), @@ -24,8 +21,6 @@ LintId::of(drop_forget_ref::FORGET_REF), LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS), LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT), - LintId::of(eq_op::EQ_OP), - LintId::of(erasing_op::ERASING_OP), LintId::of(format_impl::RECURSIVE_FORMAT_IMPL), LintId::of(formatting::POSSIBLE_MISSING_COMMA), LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF), @@ -47,17 +42,22 @@ LintId::of(methods::UNINIT_ASSUMED_INIT), LintId::of(methods::ZST_OFFSET), LintId::of(minmax::MIN_MAX), - LintId::of(misc::CMP_NAN), - LintId::of(misc::MODULO_ONE), LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS), LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), + LintId::of(operators::ABSURD_EXTREME_COMPARISONS), + LintId::of(operators::BAD_BIT_MASK), + LintId::of(operators::CMP_NAN), + LintId::of(operators::EQ_OP), + LintId::of(operators::ERASING_OP), + LintId::of(operators::INEFFECTIVE_BIT_MASK), + LintId::of(operators::MODULO_ONE), + LintId::of(operators::SELF_ASSIGNMENT), LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP), LintId::of(ptr::INVALID_NULL_PTR_USAGE), LintId::of(ptr::MUT_FROM_REF), LintId::of(ranges::REVERSED_EMPTY_RANGES), LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC), LintId::of(regex::INVALID_REGEX), - LintId::of(self_assignment::SELF_ASSIGNMENT), LintId::of(serde_api::SERDE_API_MISUSE), LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT), LintId::of(swap::ALMOST_SWAPPED), diff --git a/clippy_lints/src/lib.register_internal.rs b/clippy_lints/src/lib.register_internal.rs index 4778f4fdfa7..be63646a12f 100644 --- a/clippy_lints/src/lib.register_internal.rs +++ b/clippy_lints/src/lib.register_internal.rs @@ -6,6 +6,7 @@ LintId::of(utils::internal_lints::CLIPPY_LINTS_INTERNAL), LintId::of(utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS), LintId::of(utils::internal_lints::COMPILER_LINT_FUNCTIONS), + LintId::of(utils::internal_lints::DEFAULT_DEPRECATION_REASON), LintId::of(utils::internal_lints::DEFAULT_LINT), LintId::of(utils::internal_lints::IF_CHAIN_STYLE), LintId::of(utils::internal_lints::INTERNING_DEFINED_SYMBOL), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 8ad984c68b8..d3c75f8b519 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -10,6 +10,8 @@ #[cfg(feature = "internal")] utils::internal_lints::COMPILER_LINT_FUNCTIONS, #[cfg(feature = "internal")] + utils::internal_lints::DEFAULT_DEPRECATION_REASON, + #[cfg(feature = "internal")] utils::internal_lints::DEFAULT_LINT, #[cfg(feature = "internal")] utils::internal_lints::IF_CHAIN_STYLE, @@ -33,7 +35,6 @@ utils::internal_lints::PRODUCE_ICE, #[cfg(feature = "internal")] utils::internal_lints::UNNECESSARY_SYMBOL_STR, - absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS, almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE, approx_const::APPROX_CONSTANT, as_conversions::AS_CONVERSIONS, @@ -41,8 +42,6 @@ asm_syntax::INLINE_ASM_X86_ATT_SYNTAX, asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX, assertions_on_constants::ASSERTIONS_ON_CONSTANTS, - assign_ops::ASSIGN_OP_PATTERN, - assign_ops::MISREFACTORED_ASSIGN_OP, async_yields_async::ASYNC_YIELDS_ASYNC, attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON, attrs::BLANKET_CLIPPY_RESTRICTION_LINTS, @@ -55,9 +54,6 @@ await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE, await_holding_invalid::AWAIT_HOLDING_LOCK, await_holding_invalid::AWAIT_HOLDING_REFCELL_REF, - bit_mask::BAD_BIT_MASK, - bit_mask::INEFFECTIVE_BIT_MASK, - bit_mask::VERBOSE_BIT_MASK, blacklisted_name::BLACKLISTED_NAME, blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS, bool_assert_comparison::BOOL_ASSERT_COMPARISON, @@ -105,8 +101,10 @@ dbg_macro::DBG_MACRO, default::DEFAULT_TRAIT_ACCESS, default::FIELD_REASSIGN_WITH_DEFAULT, + default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY, default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK, default_union_representation::DEFAULT_UNION_REPRESENTATION, + dereference::EXPLICIT_AUTO_DEREF, dereference::EXPLICIT_DEREF_METHODS, dereference::NEEDLESS_BORROW, dereference::REF_BINDING_TO_REFERENCE, @@ -125,7 +123,6 @@ doc::MISSING_SAFETY_DOC, doc::NEEDLESS_DOCTEST_MAIN, doc_link_with_quotes::DOC_LINK_WITH_QUOTES, - double_comparison::DOUBLE_COMPARISONS, double_parens::DOUBLE_PARENS, drop_forget_ref::DROP_COPY, drop_forget_ref::DROP_NON_DROP, @@ -135,7 +132,6 @@ drop_forget_ref::FORGET_REF, drop_forget_ref::UNDROPPED_MANUALLY_DROPS, duplicate_mod::DUPLICATE_MOD, - duration_subsec::DURATION_SUBSEC, else_if_without_else::ELSE_IF_WITHOUT_ELSE, empty_drop::EMPTY_DROP, empty_enum::EMPTY_ENUM, @@ -145,10 +141,7 @@ enum_variants::ENUM_VARIANT_NAMES, enum_variants::MODULE_INCEPTION, enum_variants::MODULE_NAME_REPETITIONS, - eq_op::EQ_OP, - eq_op::OP_REF, equatable_if_let::EQUATABLE_IF_LET, - erasing_op::ERASING_OP, escape::BOXED_LOCAL, eta_reduction::REDUNDANT_CLOSURE, eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS, @@ -159,7 +152,6 @@ exit::EXIT, explicit_write::EXPLICIT_WRITE, fallible_impl_from::FALLIBLE_IMPL_FROM, - float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS, float_literal::EXCESSIVE_PRECISION, float_literal::LOSSY_FLOAT_LITERAL, floating_point_arithmetic::IMPRECISE_FLOPS, @@ -185,7 +177,6 @@ functions::TOO_MANY_LINES, future_not_send::FUTURE_NOT_SEND, get_first::GET_FIRST, - identity_op::IDENTITY_OP, if_let_mutex::IF_LET_MUTEX, if_not_else::IF_NOT_ELSE, if_then_some_else_none::IF_THEN_SOME_ELSE_NONE, @@ -204,7 +195,6 @@ init_numbered_fields::INIT_NUMBERED_FIELDS, inline_fn_without_body::INLINE_FN_WITHOUT_BODY, int_plus_one::INT_PLUS_ONE, - integer_division::INTEGER_DIVISION, invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS, items_after_statements::ITEMS_AFTER_STATEMENTS, iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR, @@ -234,6 +224,7 @@ loops::FOR_KV_MAP, loops::FOR_LOOPS_OVER_FALLIBLES, loops::ITER_NEXT_LOOP, + loops::MANUAL_FIND, loops::MANUAL_FLATTEN, loops::MANUAL_MEMCPY, loops::MISSING_SPIN_LOOP, @@ -253,6 +244,8 @@ manual_bits::MANUAL_BITS, manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE, manual_ok_or::MANUAL_OK_OR, + manual_rem_euclid::MANUAL_REM_EUCLID, + manual_retain::MANUAL_RETAIN, manual_strip::MANUAL_STRIP, map_clone::MAP_CLONE, map_err_ignore::MAP_ERR_IGNORE, @@ -364,11 +357,6 @@ methods::WRONG_SELF_CONVENTION, methods::ZST_OFFSET, minmax::MIN_MAX, - misc::CMP_NAN, - misc::CMP_OWNED, - misc::FLOAT_CMP, - misc::FLOAT_CMP_CONST, - misc::MODULO_ONE, misc::SHORT_CIRCUIT_STATEMENT, misc::TOPLEVEL_REF_ARG, misc::USED_UNDERSCORE_BINDING, @@ -392,7 +380,6 @@ mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION, module_style::MOD_MODULE_FILES, module_style::SELF_NAMED_MODULE_FILES, - modulo_arithmetic::MODULO_ARITHMETIC, mut_key::MUTABLE_KEY_TYPE, mut_mut::MUT_MUT, mut_mutex_lock::MUT_MUTEX_LOCK, @@ -401,7 +388,6 @@ mutex_atomic::MUTEX_ATOMIC, mutex_atomic::MUTEX_INTEGER, needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE, - needless_bitwise_bool::NEEDLESS_BITWISE_BOOL, needless_bool::BOOL_COMPARISON, needless_bool::NEEDLESS_BOOL, needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE, @@ -426,11 +412,34 @@ non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS, non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY, nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES, - numeric_arithmetic::FLOAT_ARITHMETIC, - numeric_arithmetic::INTEGER_ARITHMETIC, octal_escapes::OCTAL_ESCAPES, only_used_in_recursion::ONLY_USED_IN_RECURSION, open_options::NONSENSICAL_OPEN_OPTIONS, + operators::ABSURD_EXTREME_COMPARISONS, + operators::ASSIGN_OP_PATTERN, + operators::BAD_BIT_MASK, + operators::CMP_NAN, + operators::CMP_OWNED, + operators::DOUBLE_COMPARISONS, + operators::DURATION_SUBSEC, + operators::EQ_OP, + operators::ERASING_OP, + operators::FLOAT_ARITHMETIC, + operators::FLOAT_CMP, + operators::FLOAT_CMP_CONST, + operators::FLOAT_EQUALITY_WITHOUT_ABS, + operators::IDENTITY_OP, + operators::INEFFECTIVE_BIT_MASK, + operators::INTEGER_ARITHMETIC, + operators::INTEGER_DIVISION, + operators::MISREFACTORED_ASSIGN_OP, + operators::MODULO_ARITHMETIC, + operators::MODULO_ONE, + operators::NEEDLESS_BITWISE_BOOL, + operators::OP_REF, + operators::PTR_EQ, + operators::SELF_ASSIGNMENT, + operators::VERBOSE_BIT_MASK, option_env_unwrap::OPTION_ENV_UNWRAP, option_if_let_else::OPTION_IF_LET_ELSE, overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL, @@ -449,7 +458,6 @@ ptr::INVALID_NULL_PTR_USAGE, ptr::MUT_FROM_REF, ptr::PTR_ARG, - ptr_eq::PTR_EQ, ptr_offset_with_cast::PTR_OFFSET_WITH_CAST, pub_use::PUB_USE, question_mark::QUESTION_MARK, @@ -477,7 +485,6 @@ returns::LET_AND_RETURN, returns::NEEDLESS_RETURN, same_name_method::SAME_NAME_METHOD, - self_assignment::SELF_ASSIGNMENT, self_named_constructors::SELF_NAMED_CONSTRUCTORS, semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED, serde_api::SERDE_API_MISUSE, diff --git a/clippy_lints/src/lib.register_pedantic.rs b/clippy_lints/src/lib.register_pedantic.rs index 48de92ae945..a1b54665814 100644 --- a/clippy_lints/src/lib.register_pedantic.rs +++ b/clippy_lints/src/lib.register_pedantic.rs @@ -4,7 +4,6 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(attrs::INLINE_ALWAYS), - LintId::of(bit_mask::VERBOSE_BIT_MASK), LintId::of(borrow_as_ptr::BORROW_AS_PTR), LintId::of(bytecount::NAIVE_BYTECOUNT), LintId::of(case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS), @@ -65,17 +64,18 @@ LintId::of(methods::INEFFICIENT_TO_STRING), LintId::of(methods::MAP_UNWRAP_OR), LintId::of(methods::UNNECESSARY_JOIN), - LintId::of(misc::FLOAT_CMP), LintId::of(misc::USED_UNDERSCORE_BINDING), LintId::of(mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER), LintId::of(mut_mut::MUT_MUT), - LintId::of(needless_bitwise_bool::NEEDLESS_BITWISE_BOOL), LintId::of(needless_continue::NEEDLESS_CONTINUE), LintId::of(needless_for_each::NEEDLESS_FOR_EACH), LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE), LintId::of(no_effect::NO_EFFECT_UNDERSCORE_BINDING), LintId::of(non_expressive_names::MANY_SINGLE_CHAR_NAMES), LintId::of(non_expressive_names::SIMILAR_NAMES), + LintId::of(operators::FLOAT_CMP), + LintId::of(operators::NEEDLESS_BITWISE_BOOL), + LintId::of(operators::VERBOSE_BIT_MASK), LintId::of(pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE), LintId::of(pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF), LintId::of(ranges::RANGE_MINUS_ONE), diff --git a/clippy_lints/src/lib.register_perf.rs b/clippy_lints/src/lib.register_perf.rs index 82431863e6c..6bf519c24e8 100644 --- a/clippy_lints/src/lib.register_perf.rs +++ b/clippy_lints/src/lib.register_perf.rs @@ -13,6 +13,7 @@ LintId::of(loops::MANUAL_MEMCPY), LintId::of(loops::MISSING_SPIN_LOOP), LintId::of(loops::NEEDLESS_COLLECT), + LintId::of(manual_retain::MANUAL_RETAIN), LintId::of(methods::EXPECT_FUN_CALL), LintId::of(methods::EXTEND_WITH_DRAIN), LintId::of(methods::ITER_NTH), @@ -21,7 +22,7 @@ LintId::of(methods::OR_FUN_CALL), LintId::of(methods::SINGLE_CHAR_PATTERN), LintId::of(methods::UNNECESSARY_TO_OWNED), - LintId::of(misc::CMP_OWNED), + LintId::of(operators::CMP_OWNED), LintId::of(redundant_clone::REDUNDANT_CLONE), LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), LintId::of(types::BOX_COLLECTION), diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index 3695012f552..970e9db4772 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -25,7 +25,6 @@ LintId::of(implicit_return::IMPLICIT_RETURN), LintId::of(indexing_slicing::INDEXING_SLICING), LintId::of(inherent_impl::MULTIPLE_INHERENT_IMPL), - LintId::of(integer_division::INTEGER_DIVISION), LintId::of(large_include_file::LARGE_INCLUDE_FILE), LintId::of(let_underscore::LET_UNDERSCORE_MUST_USE), LintId::of(literal_representation::DECIMAL_LITERAL_REPRESENTATION), @@ -39,7 +38,6 @@ LintId::of(methods::FILETYPE_IS_FILE), LintId::of(methods::GET_UNWRAP), LintId::of(methods::UNWRAP_USED), - LintId::of(misc::FLOAT_CMP_CONST), LintId::of(misc_early::SEPARATED_LITERAL_SUFFIX), LintId::of(misc_early::UNNEEDED_FIELD_PATTERN), LintId::of(misc_early::UNSEPARATED_LITERAL_SUFFIX), @@ -49,9 +47,11 @@ 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(modulo_arithmetic::MODULO_ARITHMETIC), - LintId::of(numeric_arithmetic::FLOAT_ARITHMETIC), - LintId::of(numeric_arithmetic::INTEGER_ARITHMETIC), + LintId::of(operators::FLOAT_ARITHMETIC), + LintId::of(operators::FLOAT_CMP_CONST), + LintId::of(operators::INTEGER_ARITHMETIC), + LintId::of(operators::INTEGER_DIVISION), + LintId::of(operators::MODULO_ARITHMETIC), LintId::of(panic_in_result_fn::PANIC_IN_RESULT_FN), LintId::of(panic_unimplemented::PANIC), LintId::of(panic_unimplemented::TODO), diff --git a/clippy_lints/src/lib.register_style.rs b/clippy_lints/src/lib.register_style.rs index b6992ae0ad2..15a1bc569af 100644 --- a/clippy_lints/src/lib.register_style.rs +++ b/clippy_lints/src/lib.register_style.rs @@ -4,7 +4,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS), - LintId::of(assign_ops::ASSIGN_OP_PATTERN), LintId::of(blacklisted_name::BLACKLISTED_NAME), LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS), LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON), @@ -14,6 +13,7 @@ LintId::of(collapsible_if::COLLAPSIBLE_IF), LintId::of(comparison_chain::COMPARISON_CHAIN), LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT), + LintId::of(default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY), LintId::of(dereference::NEEDLESS_BORROW), LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ), LintId::of(disallowed_methods::DISALLOWED_METHODS), @@ -22,7 +22,6 @@ LintId::of(doc::NEEDLESS_DOCTEST_MAIN), LintId::of(enum_variants::ENUM_VARIANT_NAMES), LintId::of(enum_variants::MODULE_INCEPTION), - LintId::of(eq_op::OP_REF), LintId::of(eta_reduction::REDUNDANT_CLOSURE), LintId::of(float_literal::EXCESSIVE_PRECISION), LintId::of(from_over_into::FROM_OVER_INTO), @@ -97,9 +96,11 @@ LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST), LintId::of(non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST), LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS), + LintId::of(operators::ASSIGN_OP_PATTERN), + LintId::of(operators::OP_REF), + LintId::of(operators::PTR_EQ), LintId::of(ptr::CMP_NULL), LintId::of(ptr::PTR_ARG), - LintId::of(ptr_eq::PTR_EQ), LintId::of(question_mark::QUESTION_MARK), LintId::of(ranges::MANUAL_RANGE_CONTAINS), LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES), diff --git a/clippy_lints/src/lib.register_suspicious.rs b/clippy_lints/src/lib.register_suspicious.rs index 7b13713c36e..f7558f87098 100644 --- a/clippy_lints/src/lib.register_suspicious.rs +++ b/clippy_lints/src/lib.register_suspicious.rs @@ -4,7 +4,6 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec![ LintId::of(almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE), - LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP), LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS), LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE), LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), @@ -16,7 +15,6 @@ LintId::of(drop_forget_ref::DROP_NON_DROP), LintId::of(drop_forget_ref::FORGET_NON_DROP), LintId::of(duplicate_mod::DUPLICATE_MOD), - LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS), LintId::of(format_impl::PRINT_IN_FORMAT_IMPL), LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING), @@ -29,6 +27,8 @@ LintId::of(methods::SUSPICIOUS_MAP), LintId::of(mut_key::MUTABLE_KEY_TYPE), LintId::of(octal_escapes::OCTAL_ESCAPES), + LintId::of(operators::FLOAT_EQUALITY_WITHOUT_ABS), + LintId::of(operators::MISREFACTORED_ASSIGN_OP), LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT), LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 84898eae05a..172fdf8c852 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -7,6 +7,7 @@ #![feature(let_chains)] #![feature(let_else)] #![feature(lint_reasons)] +#![feature(never_type)] #![feature(once_cell)] #![feature(rustc_private)] #![feature(stmt_expr_attributes)] @@ -52,6 +53,7 @@ use clippy_utils::parse_msrv; use rustc_data_structures::fx::FxHashSet; use rustc_lint::LintId; +use rustc_semver::RustcVersion; use rustc_session::Session; /// Macro used to declare a Clippy lint. @@ -159,25 +161,22 @@ macro_rules! declare_clippy_lint { } #[cfg(feature = "internal")] -mod deprecated_lints; +pub mod deprecated_lints; #[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))] mod utils; mod renamed_lints; // begin lints modules, do not remove this comment, it’s used in `update_lints` -mod absurd_extreme_comparisons; mod almost_complete_letter_range; mod approx_const; mod as_conversions; mod as_underscore; mod asm_syntax; mod assertions_on_constants; -mod assign_ops; mod async_yields_async; mod attrs; mod await_holding_invalid; -mod bit_mask; mod blacklisted_name; mod blocks_in_if_conditions; mod bool_assert_comparison; @@ -199,6 +198,7 @@ macro_rules! declare_clippy_lint { mod create_dir; mod dbg_macro; mod default; +mod default_instead_of_iter_empty; mod default_numeric_fallback; mod default_union_representation; mod dereference; @@ -209,11 +209,9 @@ macro_rules! declare_clippy_lint { mod disallowed_types; mod doc; mod doc_link_with_quotes; -mod double_comparison; mod double_parens; mod drop_forget_ref; mod duplicate_mod; -mod duration_subsec; mod else_if_without_else; mod empty_drop; mod empty_enum; @@ -221,9 +219,7 @@ macro_rules! declare_clippy_lint { mod entry; mod enum_clike; mod enum_variants; -mod eq_op; mod equatable_if_let; -mod erasing_op; mod escape; mod eta_reduction; mod excessive_bools; @@ -231,7 +227,6 @@ macro_rules! declare_clippy_lint { mod exit; mod explicit_write; mod fallible_impl_from; -mod float_equality_without_abs; mod float_literal; mod floating_point_arithmetic; mod format; @@ -244,7 +239,6 @@ macro_rules! declare_clippy_lint { mod functions; mod future_not_send; mod get_first; -mod identity_op; mod if_let_mutex; mod if_not_else; mod if_then_some_else_none; @@ -260,7 +254,6 @@ macro_rules! declare_clippy_lint { mod init_numbered_fields; mod inline_fn_without_body; mod int_plus_one; -mod integer_division; mod invalid_upcast_comparisons; mod items_after_statements; mod iter_not_returning_iterator; @@ -281,6 +274,8 @@ macro_rules! declare_clippy_lint { mod manual_bits; mod manual_non_exhaustive; mod manual_ok_or; +mod manual_rem_euclid; +mod manual_retain; mod manual_strip; mod map_clone; mod map_err_ignore; @@ -300,7 +295,6 @@ macro_rules! declare_clippy_lint { mod missing_inline; mod mixed_read_write_in_expression; mod module_style; -mod modulo_arithmetic; mod mut_key; mod mut_mut; mod mut_mutex_lock; @@ -308,7 +302,6 @@ macro_rules! declare_clippy_lint { mod mutable_debug_assertion; mod mutex_atomic; mod needless_arbitrary_self_type; -mod needless_bitwise_bool; mod needless_bool; mod needless_borrowed_ref; mod needless_continue; @@ -327,10 +320,10 @@ macro_rules! declare_clippy_lint { mod non_octal_unix_permissions; mod non_send_fields_in_send_ty; mod nonstandard_macro_braces; -mod numeric_arithmetic; mod octal_escapes; mod only_used_in_recursion; mod open_options; +mod operators; mod option_env_unwrap; mod option_if_let_else; mod overflow_check_conditional; @@ -342,7 +335,6 @@ macro_rules! declare_clippy_lint { mod pattern_type_mismatch; mod precedence; mod ptr; -mod ptr_eq; mod ptr_offset_with_cast; mod pub_use; mod question_mark; @@ -363,7 +355,6 @@ macro_rules! declare_clippy_lint { mod return_self_not_must_use; mod returns; mod same_name_method; -mod self_assignment; mod self_named_constructors; mod semicolon_if_nothing_returned; mod serde_api; @@ -448,6 +439,39 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Se store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv })); } +fn read_msrv(conf: &Conf, sess: &Session) -> Option { + let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION") + .ok() + .and_then(|v| parse_msrv(&v, None, None)); + let clippy_msrv = conf.msrv.as_ref().and_then(|s| { + parse_msrv(s, None, None).or_else(|| { + sess.err(&format!( + "error reading Clippy's configuration file. `{}` is not a valid Rust version", + s + )); + None + }) + }); + + if let Some(cargo_msrv) = cargo_msrv { + if let Some(clippy_msrv) = clippy_msrv { + // if both files have an msrv, let's compare them and emit a warning if they differ + if clippy_msrv != cargo_msrv { + sess.warn(&format!( + "the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{}` from `clippy.toml`", + clippy_msrv + )); + } + + Some(clippy_msrv) + } else { + Some(cargo_msrv) + } + } else { + clippy_msrv + } +} + #[doc(hidden)] pub fn read_conf(sess: &Session) -> Conf { let file_name = match utils::conf::lookup_conf_file() { @@ -463,12 +487,11 @@ pub fn read_conf(sess: &Session) -> Conf { let TryConf { conf, errors } = utils::conf::read(&file_name); // all conf errors are non-fatal, we just use the default conf in case of error for error in errors { - sess.struct_err(&format!( + sess.err(&format!( "error reading Clippy's configuration file `{}`: {}", file_name.display(), format_error(error) - )) - .emit(); + )); } conf @@ -543,21 +566,14 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: )) }); store.register_late_pass(|| Box::new(booleans::NonminimalBool)); - store.register_late_pass(|| Box::new(needless_bitwise_bool::NeedlessBitwiseBool)); - store.register_late_pass(|| Box::new(eq_op::EqOp)); store.register_late_pass(|| Box::new(enum_clike::UnportableVariant)); store.register_late_pass(|| Box::new(float_literal::FloatLiteral)); - let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold; - store.register_late_pass(move || Box::new(bit_mask::BitMask::new(verbose_bit_mask_threshold))); store.register_late_pass(|| Box::new(ptr::Ptr)); - store.register_late_pass(|| Box::new(ptr_eq::PtrEq)); store.register_late_pass(|| Box::new(needless_bool::NeedlessBool)); store.register_late_pass(|| Box::new(needless_bool::BoolComparison)); store.register_late_pass(|| Box::new(needless_for_each::NeedlessForEach)); store.register_late_pass(|| Box::new(misc::MiscLints)); store.register_late_pass(|| Box::new(eta_reduction::EtaReduction)); - store.register_late_pass(|| Box::new(identity_op::IdentityOp)); - store.register_late_pass(|| Box::new(erasing_op::ErasingOp)); store.register_late_pass(|| Box::new(mut_mut::MutMut)); store.register_late_pass(|| Box::new(mut_reference::UnnecessaryMutPassed)); store.register_late_pass(|| Box::new(len_zero::LenZero)); @@ -575,16 +591,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions)); store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports)); - let msrv = conf.msrv.as_ref().and_then(|s| { - parse_msrv(s, None, None).or_else(|| { - sess.err(&format!( - "error reading Clippy's configuration file. `{}` is not a valid Rust version", - s - )); - None - }) - }); - + let msrv = read_msrv(conf, sess); let avoid_breaking_exported_api = conf.avoid_breaking_exported_api; let allow_expect_in_tests = conf.allow_expect_in_tests; let allow_unwrap_in_tests = conf.allow_unwrap_in_tests; @@ -639,7 +646,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(borrow_deref_ref::BorrowDerefRef)); store.register_late_pass(|| Box::new(no_effect::NoEffect)); store.register_late_pass(|| Box::new(temporary_assignment::TemporaryAssignment)); - store.register_late_pass(|| Box::new(transmute::Transmute)); + store.register_late_pass(move || Box::new(transmute::Transmute::new(msrv))); let cognitive_complexity_threshold = conf.cognitive_complexity_threshold; store.register_late_pass(move || { Box::new(cognitive_complexity::CognitiveComplexity::new( @@ -655,7 +662,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(derivable_impls::DerivableImpls)); store.register_late_pass(|| Box::new(drop_forget_ref::DropForgetRef)); store.register_late_pass(|| Box::new(empty_enum::EmptyEnum)); - store.register_late_pass(|| Box::new(absurd_extreme_comparisons::AbsurdExtremeComparisons)); store.register_late_pass(|| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons)); store.register_late_pass(|| Box::new(regex::Regex)); store.register_late_pass(|| Box::new(copies::CopyAndPaste)); @@ -678,8 +684,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(doc::DocMarkdown::new(doc_valid_idents.clone()))); store.register_late_pass(|| Box::new(neg_multiply::NegMultiply)); store.register_late_pass(|| Box::new(mem_forget::MemForget)); - store.register_late_pass(|| Box::new(numeric_arithmetic::NumericArithmetic::default())); - store.register_late_pass(|| Box::new(assign_ops::AssignOps)); store.register_late_pass(|| Box::new(let_if_seq::LetIfSeq)); store.register_late_pass(|| Box::new(mixed_read_write_in_expression::EvalOrderDependence)); store.register_late_pass(|| Box::new(missing_doc::MissingDoc::new())); @@ -706,7 +710,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(useless_conversion::UselessConversion::default())); store.register_late_pass(|| Box::new(implicit_hasher::ImplicitHasher)); store.register_late_pass(|| Box::new(fallible_impl_from::FallibleImplFrom)); - store.register_late_pass(|| Box::new(double_comparison::DoubleComparisons)); store.register_late_pass(|| Box::new(question_mark::QuestionMark)); store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings)); store.register_late_pass(|| Box::new(suspicious_trait_impl::SuspiciousImpl)); @@ -714,7 +717,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(inherent_impl::MultipleInherentImpl)); store.register_late_pass(|| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd)); store.register_late_pass(|| Box::new(unwrap::Unwrap)); - store.register_late_pass(|| Box::new(duration_subsec::DurationSubsec)); store.register_late_pass(|| Box::new(indexing_slicing::IndexingSlicing)); store.register_late_pass(|| Box::new(non_copy_const::NonCopyConst)); store.register_late_pass(|| Box::new(ptr_offset_with_cast::PtrOffsetWithCast)); @@ -725,13 +727,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(assertions_on_constants::AssertionsOnConstants)); 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(integer_division::IntegerDivision)); store.register_late_pass(|| Box::new(inherent_to_string::InherentToString)); let max_trait_bounds = conf.max_trait_bounds; store.register_late_pass(move || Box::new(trait_bounds::TraitBounds::new(max_trait_bounds))); store.register_late_pass(|| Box::new(comparison_chain::ComparisonChain)); store.register_late_pass(|| Box::new(mut_key::MutableKeyType)); - store.register_late_pass(|| Box::new(modulo_arithmetic::ModuloArithmetic)); store.register_early_pass(|| Box::new(reference::DerefAddrOf)); store.register_early_pass(|| Box::new(double_parens::DoubleParens)); store.register_late_pass(|| Box::new(format_impl::FormatImpl::new())); @@ -828,9 +828,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(stable_sort_primitive::StableSortPrimitive)); store.register_late_pass(|| Box::new(repeat_once::RepeatOnce)); store.register_late_pass(|| Box::new(unwrap_in_result::UnwrapInResult)); - store.register_late_pass(|| Box::new(self_assignment::SelfAssignment)); store.register_late_pass(|| Box::new(manual_ok_or::ManualOkOr)); - store.register_late_pass(|| Box::new(float_equality_without_abs::FloatEqualityWithoutAbs)); store.register_late_pass(|| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)); store.register_late_pass(|| Box::new(async_yields_async::AsyncYieldsAsync)); let disallowed_methods = conf.disallowed_methods.clone(); @@ -910,6 +908,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(mismatching_type_param_order::TypeParamMismatch)); store.register_late_pass(|| Box::new(as_underscore::AsUnderscore)); store.register_late_pass(|| Box::new(read_zero_byte_vec::ReadZeroByteVec)); + store.register_late_pass(|| Box::new(default_instead_of_iter_empty::DefaultIterEmpty)); + store.register_late_pass(move || Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv))); + store.register_late_pass(move || Box::new(manual_retain::ManualRetain::new(msrv))); + let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold; + store.register_late_pass(move || Box::new(operators::Operators::new(verbose_bit_mask_threshold))); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 93f5663312f..5c0bd57ac50 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -92,7 +92,9 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Fn(ref sig, generics, id) = item.kind { check_fn_inner(cx, sig.decl, Some(id), None, generics, item.span, true); } else if let ItemKind::Impl(impl_) = item.kind { - report_extra_impl_lifetimes(cx, impl_); + if !item.span.from_expansion() { + report_extra_impl_lifetimes(cx, impl_); + } } } diff --git a/clippy_lints/src/loops/manual_find.rs b/clippy_lints/src/loops/manual_find.rs new file mode 100644 index 00000000000..33736d6d4e6 --- /dev/null +++ b/clippy_lints/src/loops/manual_find.rs @@ -0,0 +1,158 @@ +use super::utils::make_iterator_snippet; +use super::MANUAL_FIND; +use clippy_utils::{ + diagnostics::span_lint_and_then, higher, is_lang_ctor, path_res, peel_blocks_with_stmt, + source::snippet_with_applicability, ty::implements_trait, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{ + def::Res, lang_items::LangItem, BindingAnnotation, Block, Expr, ExprKind, HirId, Node, Pat, PatKind, Stmt, StmtKind, +}; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + span: Span, + expr: &'tcx Expr<'_>, +) { + let inner_expr = peel_blocks_with_stmt(body); + // Check for the specific case that the result is returned and optimize suggestion for that (more + // cases can be added later) + if_chain! { + if let Some(higher::If { cond, then, r#else: None, }) = higher::If::hir(inner_expr); + if let Some(binding_id) = get_binding(pat); + if let ExprKind::Block(block, _) = then.kind; + if let [stmt] = block.stmts; + if let StmtKind::Semi(semi) = stmt.kind; + if let ExprKind::Ret(Some(ret_value)) = semi.kind; + if let ExprKind::Call(Expr { kind: ExprKind::Path(ctor), .. }, [inner_ret]) = ret_value.kind; + if is_lang_ctor(cx, ctor, LangItem::OptionSome); + if path_res(cx, inner_ret) == Res::Local(binding_id); + if let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr); + then { + let mut applicability = Applicability::MachineApplicable; + let mut snippet = make_iterator_snippet(cx, arg, &mut applicability); + // Checks if `pat` is a single reference to a binding (`&x`) + let is_ref_to_binding = + matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..))); + // If `pat` is not a binding or a reference to a binding (`x` or `&x`) + // we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`) + if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) { + snippet.push_str( + &format!( + ".map(|{}| {})", + snippet_with_applicability(cx, pat.span, "..", &mut applicability), + snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability), + )[..], + ); + } + let ty = cx.typeck_results().expr_ty(inner_ret); + if cx.tcx.lang_items().copy_trait().map_or(false, |id| implements_trait(cx, ty, id, &[])) { + snippet.push_str( + &format!( + ".find(|{}{}| {})", + "&".repeat(1 + usize::from(is_ref_to_binding)), + snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability), + snippet_with_applicability(cx, cond.span, "..", &mut applicability), + )[..], + ); + if is_ref_to_binding { + snippet.push_str(".copied()"); + } + } else { + applicability = Applicability::MaybeIncorrect; + snippet.push_str( + &format!( + ".find(|{}| {})", + snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability), + snippet_with_applicability(cx, cond.span, "..", &mut applicability), + )[..], + ); + } + // Extends to `last_stmt` to include semicolon in case of `return None;` + let lint_span = span.to(last_stmt.span).to(last_ret.span); + span_lint_and_then( + cx, + MANUAL_FIND, + lint_span, + "manual implementation of `Iterator::find`", + |diag| { + if applicability == Applicability::MaybeIncorrect { + diag.note("you may need to dereference some variables"); + } + diag.span_suggestion( + lint_span, + "replace with an iterator", + snippet, + applicability, + ); + }, + ); + } + } +} + +fn get_binding(pat: &Pat<'_>) -> Option { + let mut hir_id = None; + let mut count = 0; + pat.each_binding(|annotation, id, _, _| { + count += 1; + if count > 1 { + hir_id = None; + return; + } + if let BindingAnnotation::Unannotated = annotation { + hir_id = Some(id); + } + }); + hir_id +} + +// Returns the last statement and last return if function fits format for lint +fn last_stmt_and_ret<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, +) -> Option<(&'tcx Stmt<'tcx>, &'tcx Expr<'tcx>)> { + // Returns last non-return statement and the last return + fn extract<'tcx>(block: &Block<'tcx>) -> Option<(&'tcx Stmt<'tcx>, &'tcx Expr<'tcx>)> { + if let [.., last_stmt] = block.stmts { + if let Some(ret) = block.expr { + return Some((last_stmt, ret)); + } + if_chain! { + if let [.., snd_last, _] = block.stmts; + if let StmtKind::Semi(last_expr) = last_stmt.kind; + if let ExprKind::Ret(Some(ret)) = last_expr.kind; + then { + return Some((snd_last, ret)); + } + } + } + None + } + let mut parent_iter = cx.tcx.hir().parent_iter(expr.hir_id); + if_chain! { + // This should be the loop + if let Some((node_hir, Node::Stmt(..))) = parent_iter.next(); + // This should be the funciton 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; + if let ExprKind::Path(path) = &last_ret.kind; + if is_lang_ctor(cx, path, LangItem::OptionNone); + if let Some((_, Node::Expr(_block))) = parent_iter.next(); + // This includes the function header + if let Some((_, func)) = parent_iter.next(); + if func.fn_kind().is_some(); + then { + Some((block.stmts.last().unwrap(), last_ret)) + } else { + None + } + } +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 391de922e1e..ed270bd490d 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -5,6 +5,7 @@ mod for_kv_map; mod for_loops_over_fallibles; mod iter_next_loop; +mod manual_find; mod manual_flatten; mod manual_memcpy; mod missing_spin_loop; @@ -346,7 +347,14 @@ /// /// ### Example /// ```ignore - /// while let Some(val) = iter() { + /// while let Some(val) = iter.next() { + /// .. + /// } + /// ``` + /// + /// Use instead: + /// ```ignore + /// for val in &mut iter { /// .. /// } /// ``` @@ -602,6 +610,37 @@ "An empty busy waiting loop" } +declare_clippy_lint! { + /// ### What it does + /// Check for manual implementations of Iterator::find + /// + /// ### Why is this bad? + /// It doesn't affect performance, but using `find` is shorter and easier to read. + /// + /// ### Example + /// + /// ```rust + /// fn example(arr: Vec) -> Option { + /// for el in arr { + /// if el == 1 { + /// return Some(el); + /// } + /// } + /// None + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn example(arr: Vec) -> Option { + /// arr.into_iter().find(|&el| el == 1) + /// } + /// ``` + #[clippy::version = "1.61.0"] + pub MANUAL_FIND, + complexity, + "manual implementation of `Iterator::find`" +} + declare_lint_pass!(Loops => [ MANUAL_MEMCPY, MANUAL_FLATTEN, @@ -622,6 +661,7 @@ SAME_ITEM_PUSH, SINGLE_ELEMENT_LOOP, MISSING_SPIN_LOOP, + MANUAL_FIND, ]); impl<'tcx> LateLintPass<'tcx> for Loops { @@ -696,6 +736,7 @@ fn check_for_loop<'tcx>( single_element_loop::check(cx, pat, arg, body, expr); same_item_push::check(cx, pat, arg, body, expr); manual_flatten::check(cx, pat, arg, body, span); + manual_find::check(cx, pat, arg, body, span, expr); } fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { diff --git a/clippy_lints/src/loops/while_let_loop.rs b/clippy_lints/src/loops/while_let_loop.rs index 8f57df0be6b..45af6be2653 100644 --- a/clippy_lints/src/loops/while_let_loop.rs +++ b/clippy_lints/src/loops/while_let_loop.rs @@ -2,71 +2,60 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher; use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::needs_ordered_drop; +use clippy_utils::visitors::any_temporaries_need_ordered_drop; use rustc_errors::Applicability; -use rustc_hir::{Block, Expr, ExprKind, MatchSource, Pat, StmtKind}; -use rustc_lint::{LateContext, LintContext}; -use rustc_middle::lint::in_external_macro; +use rustc_hir::{Block, Expr, ExprKind, Local, MatchSource, Pat, StmtKind}; +use rustc_lint::LateContext; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { - // extract the expression from the first statement (if any) in a block - let inner_stmt_expr = extract_expr_from_first_stmt(loop_block); - // or extract the first expression (if any) from the block - if let Some(inner) = inner_stmt_expr.or_else(|| extract_first_expr(loop_block)) { - if let Some(higher::IfLet { - let_pat, - let_expr, - if_else: Some(if_else), - .. - }) = higher::IfLet::hir(cx, inner) - { - if is_simple_break_expr(if_else) { - could_be_while_let(cx, expr, let_pat, let_expr); + let (init, has_trailing_exprs) = match (loop_block.stmts, loop_block.expr) { + ([stmt, stmts @ ..], expr) => { + if let StmtKind::Local(&Local { init: Some(e), .. }) | StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind { + (e, !stmts.is_empty() || expr.is_some()) + } else { + return; } - } - - if let ExprKind::Match(matchexpr, arms, MatchSource::Normal) = inner.kind { - if arms.len() == 2 - && arms[0].guard.is_none() - && arms[1].guard.is_none() - && is_simple_break_expr(arms[1].body) - { - could_be_while_let(cx, expr, arms[0].pat, matchexpr); - } - } - } -} - -/// If a block begins with a statement (possibly a `let` binding) and has an -/// expression, return it. -fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { - if let Some(first_stmt) = block.stmts.get(0) { - if let StmtKind::Local(local) = first_stmt.kind { - return local.init; - } - } - None -} - -/// If a block begins with an expression (with or without semicolon), return it. -fn extract_first_expr<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { - match block.expr { - Some(expr) if block.stmts.is_empty() => Some(expr), - None if !block.stmts.is_empty() => match block.stmts[0].kind { - StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some(expr), - StmtKind::Local(..) | StmtKind::Item(..) => None, }, - _ => None, + ([], Some(e)) => (e, false), + _ => return, + }; + + if let Some(if_let) = higher::IfLet::hir(cx, init) + && let Some(else_expr) = if_let.if_else + && is_simple_break_expr(else_expr) + { + could_be_while_let(cx, expr, if_let.let_pat, if_let.let_expr, has_trailing_exprs); + } else if let ExprKind::Match(scrutinee, [arm1, arm2], MatchSource::Normal) = init.kind + && arm1.guard.is_none() + && arm2.guard.is_none() + && is_simple_break_expr(arm2.body) + { + could_be_while_let(cx, expr, arm1.pat, scrutinee, has_trailing_exprs); } } -/// Returns `true` if expr contains a single break expr without destination label -/// and -/// passed expression. The expression may be within a block. -fn is_simple_break_expr(expr: &Expr<'_>) -> bool { - match expr.kind { - ExprKind::Break(dest, ref passed_expr) if dest.label.is_none() && passed_expr.is_none() => true, - ExprKind::Block(b, _) => extract_first_expr(b).map_or(false, is_simple_break_expr), - _ => false, +/// Returns `true` if expr contains a single break expression without a label or eub-expression. +fn is_simple_break_expr(e: &Expr<'_>) -> bool { + matches!(peel_blocks(e).kind, ExprKind::Break(dest, None) if dest.label.is_none()) +} + +/// Removes any blocks containing only a single expression. +fn peel_blocks<'tcx>(e: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { + if let ExprKind::Block(b, _) = e.kind { + match (b.stmts, b.expr) { + ([s], None) => { + if let StmtKind::Expr(e) | StmtKind::Semi(e) = s.kind { + peel_blocks(e) + } else { + e + } + }, + ([], Some(e)) => peel_blocks(e), + _ => e, + } + } else { + e } } @@ -75,8 +64,13 @@ fn could_be_while_let<'tcx>( expr: &'tcx Expr<'_>, let_pat: &'tcx Pat<'_>, let_expr: &'tcx Expr<'_>, + has_trailing_exprs: bool, ) { - if in_external_macro(cx.sess(), expr.span) { + if has_trailing_exprs + && (needs_ordered_drop(cx, cx.typeck_results().expr_ty(let_expr)) + || any_temporaries_need_ordered_drop(cx, let_expr)) + { + // Switching to a `while let` loop will extend the lifetime of some values. return; } diff --git a/clippy_lints/src/macro_use.rs b/clippy_lints/src/macro_use.rs index 753469c603d..d573a1b4fbb 100644 --- a/clippy_lints/src/macro_use.rs +++ b/clippy_lints/src/macro_use.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::snippet; use hir::def::{DefKind, Res}; use if_chain::if_chain; @@ -50,8 +50,9 @@ pub fn new(name: String) -> Self { #[derive(Default)] #[expect(clippy::module_name_repetitions)] pub struct MacroUseImports { - /// the actual import path used and the span of the attribute above it. - imports: Vec<(String, Span)>, + /// the actual import path used and the span of the attribute above it. The value is + /// the location, where the lint should be emitted. + imports: Vec<(String, Span, hir::HirId)>, /// the span of the macro reference, kept to ensure only one reference is used per macro call. collected: FxHashSet, mac_refs: Vec, @@ -90,7 +91,8 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { if_chain! { if cx.sess().opts.edition >= Edition::Edition2018; if let hir::ItemKind::Use(path, _kind) = &item.kind; - let attrs = cx.tcx.hir().attrs(item.hir_id()); + let hir_id = item.hir_id(); + let attrs = cx.tcx.hir().attrs(hir_id); if let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use)); if let Res::Def(DefKind::Mod, id) = path.res; if !id.is_local(); @@ -99,7 +101,7 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res { let span = mac_attr.span; let def_path = cx.tcx.def_path_str(mac_id); - self.imports.push((def_path, span)); + self.imports.push((def_path, span, hir_id)); } } } else { @@ -137,7 +139,7 @@ fn check_ty(&mut self, cx: &LateContext<'_>, ty: &hir::Ty<'_>) { fn check_crate_post(&mut self, cx: &LateContext<'_>) { let mut used = FxHashMap::default(); let mut check_dup = vec![]; - for (import, span) in &self.imports { + for (import, span, hir_id) in &self.imports { let found_idx = self.mac_refs.iter().position(|mac| import.ends_with(&mac.name)); if let Some(idx) = found_idx { @@ -150,7 +152,7 @@ fn check_crate_post(&mut self, cx: &LateContext<'_>) { [] | [_] => return, [root, item] => { if !check_dup.contains(&(*item).to_string()) { - used.entry(((*root).to_string(), span)) + used.entry(((*root).to_string(), span, hir_id)) .or_insert_with(Vec::new) .push((*item).to_string()); check_dup.push((*item).to_string()); @@ -168,13 +170,13 @@ fn check_crate_post(&mut self, cx: &LateContext<'_>) { } }) .collect::>(); - used.entry(((*root).to_string(), span)) + used.entry(((*root).to_string(), span, hir_id)) .or_insert_with(Vec::new) .push(filtered.join("::")); check_dup.extend(filtered); } else { let rest = rest.to_vec(); - used.entry(((*root).to_string(), span)) + used.entry(((*root).to_string(), span, hir_id)) .or_insert_with(Vec::new) .push(rest.join("::")); check_dup.extend(rest.iter().map(ToString::to_string)); @@ -185,27 +187,33 @@ fn check_crate_post(&mut self, cx: &LateContext<'_>) { } let mut suggestions = vec![]; - for ((root, span), path) in used { + for ((root, span, hir_id), path) in used { if path.len() == 1 { - suggestions.push((span, format!("{}::{}", root, path[0]))); + suggestions.push((span, format!("{}::{}", root, path[0]), hir_id)); } else { - suggestions.push((span, format!("{}::{{{}}}", root, path.join(", ")))); + suggestions.push((span, format!("{}::{{{}}}", root, path.join(", ")), hir_id)); } } // If mac_refs is not empty we have encountered an import we could not handle // such as `std::prelude::v1::foo` or some other macro that expands to an import. if self.mac_refs.is_empty() { - for (span, import) in suggestions { + for (span, import, hir_id) in suggestions { let help = format!("use {};", import); - span_lint_and_sugg( + span_lint_hir_and_then( cx, MACRO_USE_IMPORTS, + *hir_id, *span, "`macro_use` attributes are no longer needed in the Rust 2018 edition", - "remove the attribute and import the macro directly, try", - help, - Applicability::MaybeIncorrect, + |diag| { + diag.span_suggestion( + *span, + "remove the attribute and import the macro directly, try", + help, + Applicability::MaybeIncorrect, + ); + }, ); } } diff --git a/clippy_lints/src/manual_non_exhaustive.rs b/clippy_lints/src/manual_non_exhaustive.rs index 14f5faafd7c..4278e98dc91 100644 --- a/clippy_lints/src/manual_non_exhaustive.rs +++ b/clippy_lints/src/manual_non_exhaustive.rs @@ -1,6 +1,6 @@ -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::source::snippet_opt; -use clippy_utils::{is_doc_hidden, is_lint_allowed, meets_msrv, msrvs}; +use clippy_utils::{is_doc_hidden, meets_msrv, msrvs}; use rustc_ast::ast::{self, VisibilityKind}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; @@ -190,12 +190,13 @@ fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { !self .constructed_enum_variants .contains(&(enum_id.to_def_id(), variant_id.to_def_id())) - && !is_lint_allowed(cx, MANUAL_NON_EXHAUSTIVE, cx.tcx.hir().local_def_id_to_hir_id(enum_id)) }) { - span_lint_and_then( + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(enum_id); + span_lint_hir_and_then( cx, MANUAL_NON_EXHAUSTIVE, + hir_id, enum_span, "this seems like a manual implementation of the non-exhaustive pattern", |diag| { diff --git a/clippy_lints/src/manual_rem_euclid.rs b/clippy_lints/src/manual_rem_euclid.rs new file mode 100644 index 00000000000..b5698965fc3 --- /dev/null +++ b/clippy_lints/src/manual_rem_euclid.rs @@ -0,0 +1,123 @@ +use clippy_utils::consts::{constant_full_int, FullInt}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{in_constant, meets_msrv, msrvs, path_to_local}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind}; +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}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for an expression like `((x % 4) + 4) % 4` which is a common manual reimplementation + /// of `x.rem_euclid(4)`. + /// + /// ### Why is this bad? + /// It's simpler and more readable. + /// + /// ### Example + /// ```rust + /// let x: i32 = 24; + /// let rem = ((x % 4) + 4) % 4; + /// ``` + /// Use instead: + /// ```rust + /// let x: i32 = 24; + /// let rem = x.rem_euclid(4); + /// ``` + #[clippy::version = "1.63.0"] + pub MANUAL_REM_EUCLID, + complexity, + "manually reimplementing `rem_euclid`" +} + +pub struct ManualRemEuclid { + msrv: Option, +} + +impl ManualRemEuclid { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(ManualRemEuclid => [MANUAL_REM_EUCLID]); + +impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if !meets_msrv(self.msrv, msrvs::REM_EUCLID) { + return; + } + + if in_constant(cx, expr.hir_id) && !meets_msrv(self.msrv, msrvs::REM_EUCLID_CONST) { + return; + } + + if in_external_macro(cx.sess(), expr.span) { + return; + } + + if let ExprKind::Binary(op1, expr1, right) = expr.kind + && op1.node == BinOpKind::Rem + && let Some(const1) = check_for_unsigned_int_constant(cx, right) + && let ExprKind::Binary(op2, left, right) = expr1.kind + && op2.node == BinOpKind::Add + && let Some((const2, expr2)) = check_for_either_unsigned_int_constant(cx, left, right) + && let ExprKind::Binary(op3, expr3, right) = expr2.kind + && op3.node == BinOpKind::Rem + && 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(Node::Binding(_)) = 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(..)) => (), + Some(Node::Local(local)) => { + let Some(ty) = local.ty else { return }; + if matches!(ty.kind, TyKind::Infer) { + return; + } + } + _ => return, + }; + + let mut app = Applicability::MachineApplicable; + let rem_of = snippet_with_applicability(cx, expr3.span, "_", &mut app); + span_lint_and_sugg( + cx, + MANUAL_REM_EUCLID, + expr.span, + "manual `rem_euclid` implementation", + "consider using", + format!("{rem_of}.rem_euclid({const1})"), + app, + ); + } + } + + extract_msrv_attr!(LateContext); +} + +// Checks if either the left or right expressions can be an unsigned int constant and returns that +// constant along with the other expression unchanged if so +fn check_for_either_unsigned_int_constant<'a>( + cx: &'a LateContext<'_>, + left: &'a Expr<'_>, + right: &'a Expr<'_>, +) -> Option<(u128, &'a Expr<'a>)> { + check_for_unsigned_int_constant(cx, left) + .map(|int_const| (int_const, right)) + .or_else(|| check_for_unsigned_int_constant(cx, right).map(|int_const| (int_const, left))) +} + +fn check_for_unsigned_int_constant<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option { + let Some(int_const) = constant_full_int(cx, cx.typeck_results(), expr) else { return None }; + match int_const { + FullInt::S(s) => s.try_into().ok(), + FullInt::U(u) => Some(u), + } +} diff --git a/clippy_lints/src/manual_retain.rs b/clippy_lints/src/manual_retain.rs new file mode 100644 index 00000000000..c35e1e021ef --- /dev/null +++ b/clippy_lints/src/manual_retain.rs @@ -0,0 +1,228 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{get_parent_expr, match_def_path, paths, SpanlessEq}; +use clippy_utils::{meets_msrv, msrvs}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def_id::DefId; +use rustc_hir::ExprKind::Assign; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::sym; + +const ACCEPTABLE_METHODS: [&[&str]; 4] = [ + &paths::HASHSET_ITER, + &paths::BTREESET_ITER, + &paths::SLICE_INTO, + &paths::VEC_DEQUE_ITER, +]; +const ACCEPTABLE_TYPES: [(rustc_span::Symbol, Option); 6] = [ + (sym::BTreeSet, Some(msrvs::BTREE_SET_RETAIN)), + (sym::BTreeMap, Some(msrvs::BTREE_MAP_RETAIN)), + (sym::HashSet, Some(msrvs::HASH_SET_RETAIN)), + (sym::HashMap, Some(msrvs::HASH_MAP_RETAIN)), + (sym::Vec, None), + (sym::VecDeque, None), +]; + +declare_clippy_lint! { + /// ### What it does + /// Checks for code to be replaced by `.retain()`. + /// ### Why is this bad? + /// `.retain()` is simpler and avoids needless allocation. + /// ### Example + /// ```rust + /// let mut vec = vec![0, 1, 2]; + /// vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect(); + /// vec = vec.into_iter().filter(|x| x % 2 == 0).collect(); + /// ``` + /// Use instead: + /// ```rust + /// let mut vec = vec![0, 1, 2]; + /// vec.retain(|x| x % 2 == 0); + /// ``` + #[clippy::version = "1.63.0"] + pub MANUAL_RETAIN, + perf, + "`retain()` is simpler and the same functionalitys" +} + +pub struct ManualRetain { + msrv: Option, +} + +impl ManualRetain { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(ManualRetain => [MANUAL_RETAIN]); + +impl<'tcx> LateLintPass<'tcx> for ManualRetain { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if let Some(parent_expr) = get_parent_expr(cx, expr) + && let Assign(left_expr, collect_expr, _) = &parent_expr.kind + && let hir::ExprKind::MethodCall(seg, _, _) = &collect_expr.kind + && seg.args.is_none() + && let hir::ExprKind::MethodCall(_, [target_expr], _) = &collect_expr.kind + && let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id) + && match_def_path(cx, collect_def_id, &paths::CORE_ITER_COLLECT) { + check_into_iter(cx, parent_expr, left_expr, target_expr, self.msrv); + check_iter(cx, parent_expr, left_expr, target_expr, self.msrv); + check_to_owned(cx, parent_expr, left_expr, target_expr, self.msrv); + } + } + + extract_msrv_attr!(LateContext); +} + +fn check_into_iter( + cx: &LateContext<'_>, + parent_expr: &hir::Expr<'_>, + left_expr: &hir::Expr<'_>, + target_expr: &hir::Expr<'_>, + msrv: Option, +) { + if let hir::ExprKind::MethodCall(_, [into_iter_expr, _], _) = &target_expr.kind + && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id) + && match_def_path(cx, filter_def_id, &paths::CORE_ITER_FILTER) + && let hir::ExprKind::MethodCall(_, [struct_expr], _) = &into_iter_expr.kind + && let Some(into_iter_def_id) = cx.typeck_results().type_dependent_def_id(into_iter_expr.hir_id) + && match_def_path(cx, into_iter_def_id, &paths::CORE_ITER_INTO_ITER) + && match_acceptable_type(cx, left_expr, msrv) + && SpanlessEq::new(cx).eq_expr(left_expr, struct_expr) { + suggest(cx, parent_expr, left_expr, target_expr); + } +} + +fn check_iter( + cx: &LateContext<'_>, + parent_expr: &hir::Expr<'_>, + left_expr: &hir::Expr<'_>, + target_expr: &hir::Expr<'_>, + msrv: Option, +) { + if let hir::ExprKind::MethodCall(_, [filter_expr], _) = &target_expr.kind + && let Some(copied_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id) + && (match_def_path(cx, copied_def_id, &paths::CORE_ITER_COPIED) + || match_def_path(cx, copied_def_id, &paths::CORE_ITER_CLONED)) + && let hir::ExprKind::MethodCall(_, [iter_expr, _], _) = &filter_expr.kind + && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(filter_expr.hir_id) + && match_def_path(cx, filter_def_id, &paths::CORE_ITER_FILTER) + && let hir::ExprKind::MethodCall(_, [struct_expr], _) = &iter_expr.kind + && let Some(iter_expr_def_id) = cx.typeck_results().type_dependent_def_id(iter_expr.hir_id) + && match_acceptable_def_path(cx, iter_expr_def_id) + && match_acceptable_type(cx, left_expr, msrv) + && SpanlessEq::new(cx).eq_expr(left_expr, struct_expr) { + suggest(cx, parent_expr, left_expr, filter_expr); + } +} + +fn check_to_owned( + cx: &LateContext<'_>, + parent_expr: &hir::Expr<'_>, + left_expr: &hir::Expr<'_>, + target_expr: &hir::Expr<'_>, + msrv: Option, +) { + if meets_msrv(msrv, msrvs::STRING_RETAIN) + && let hir::ExprKind::MethodCall(_, [filter_expr], _) = &target_expr.kind + && let Some(to_owned_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id) + && match_def_path(cx, to_owned_def_id, &paths::TO_OWNED_METHOD) + && let hir::ExprKind::MethodCall(_, [chars_expr, _], _) = &filter_expr.kind + && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(filter_expr.hir_id) + && match_def_path(cx, filter_def_id, &paths::CORE_ITER_FILTER) + && let hir::ExprKind::MethodCall(_, [str_expr], _) = &chars_expr.kind + && let Some(chars_expr_def_id) = cx.typeck_results().type_dependent_def_id(chars_expr.hir_id) + && match_def_path(cx, chars_expr_def_id, &paths::STR_CHARS) + && let ty = cx.typeck_results().expr_ty(str_expr).peel_refs() + && is_type_diagnostic_item(cx, ty, sym::String) + && SpanlessEq::new(cx).eq_expr(left_expr, str_expr) { + suggest(cx, parent_expr, left_expr, filter_expr); + } +} + +fn suggest(cx: &LateContext<'_>, parent_expr: &hir::Expr<'_>, left_expr: &hir::Expr<'_>, filter_expr: &hir::Expr<'_>) { + if let hir::ExprKind::MethodCall(_, [_, closure], _) = filter_expr.kind + && let hir::ExprKind::Closure{ body, ..} = closure.kind + && let filter_body = cx.tcx.hir().body(body) + && let [filter_params] = filter_body.params + && let Some(sugg) = match filter_params.pat.kind { + hir::PatKind::Binding(_, _, filter_param_ident, None) => { + Some(format!("{}.retain(|{}| {})", snippet(cx, left_expr.span, ".."), filter_param_ident, snippet(cx, filter_body.value.span, ".."))) + }, + hir::PatKind::Tuple([key_pat, value_pat], _) => { + make_sugg(cx, key_pat, value_pat, left_expr, filter_body) + }, + hir::PatKind::Ref(pat, _) => { + match pat.kind { + hir::PatKind::Binding(_, _, filter_param_ident, None) => { + Some(format!("{}.retain(|{}| {})", snippet(cx, left_expr.span, ".."), filter_param_ident, snippet(cx, filter_body.value.span, ".."))) + }, + _ => None + } + }, + _ => None + } { + span_lint_and_sugg( + cx, + MANUAL_RETAIN, + parent_expr.span, + "this expression can be written more simply using `.retain()`", + "consider calling `.retain()` instead", + sugg, + Applicability::MachineApplicable + ); + } +} + +fn make_sugg( + cx: &LateContext<'_>, + key_pat: &rustc_hir::Pat<'_>, + value_pat: &rustc_hir::Pat<'_>, + left_expr: &hir::Expr<'_>, + filter_body: &hir::Body<'_>, +) -> Option { + match (&key_pat.kind, &value_pat.kind) { + (hir::PatKind::Binding(_, _, key_param_ident, None), hir::PatKind::Binding(_, _, value_param_ident, None)) => { + Some(format!( + "{}.retain(|{}, &mut {}| {})", + snippet(cx, left_expr.span, ".."), + key_param_ident, + value_param_ident, + snippet(cx, filter_body.value.span, "..") + )) + }, + (hir::PatKind::Binding(_, _, key_param_ident, None), hir::PatKind::Wild) => Some(format!( + "{}.retain(|{}, _| {})", + snippet(cx, left_expr.span, ".."), + key_param_ident, + snippet(cx, filter_body.value.span, "..") + )), + (hir::PatKind::Wild, hir::PatKind::Binding(_, _, value_param_ident, None)) => Some(format!( + "{}.retain(|_, &mut {}| {})", + snippet(cx, left_expr.span, ".."), + value_param_ident, + snippet(cx, filter_body.value.span, "..") + )), + _ => None, + } +} + +fn match_acceptable_def_path(cx: &LateContext<'_>, collect_def_id: DefId) -> bool { + ACCEPTABLE_METHODS + .iter() + .any(|&method| match_def_path(cx, collect_def_id, method)) +} + +fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: Option) -> bool { + let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs(); + ACCEPTABLE_TYPES.iter().any(|(ty, acceptable_msrv)| { + is_type_diagnostic_item(cx, expr_ty, *ty) + && acceptable_msrv.map_or(true, |acceptable_msrv| meets_msrv(msrv, acceptable_msrv)) + }) +} diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index 16fefea5520..15513de7d86 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -285,7 +285,7 @@ fn from_pat(cx: &LateContext<'_>, arena: &'a DroplessArena, pat: &'a Pat<'_>) -> // TODO: Handle negative integers. They're currently treated as a wild match. ExprKind::Lit(lit) => match lit.node { LitKind::Str(sym, _) => Self::LitStr(sym), - LitKind::ByteStr(ref bytes) => Self::LitBytes(&**bytes), + LitKind::ByteStr(ref bytes) => Self::LitBytes(bytes), LitKind::Byte(val) => Self::LitInt(val.into()), LitKind::Char(val) => Self::LitInt(val.into()), LitKind::Int(val, _) => Self::LitInt(val), diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs index 9df2db45dcf..5ae4a65acaf 100644 --- a/clippy_lints/src/matches/match_single_binding.rs +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -55,7 +55,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e cx, (ex, expr), (bind_names, matched_vars), - &*snippet_body, + &snippet_body, &mut applicability, Some(span), ); @@ -88,7 +88,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e cx, (ex, expr), (bind_names, matched_vars), - &*snippet_body, + &snippet_body, &mut applicability, None, ); diff --git a/clippy_lints/src/matches/match_str_case_mismatch.rs b/clippy_lints/src/matches/match_str_case_mismatch.rs index 8302ce426e5..fa3b8d1fcea 100644 --- a/clippy_lints/src/matches/match_str_case_mismatch.rs +++ b/clippy_lints/src/matches/match_str_case_mismatch.rs @@ -118,7 +118,7 @@ fn lint(cx: &LateContext<'_>, case_method: &CaseMethod, bad_case_span: Span, bad MATCH_STR_CASE_MISMATCH, bad_case_span, "this `match` arm has a differing case than its expression", - &*format!("consider changing the case of this arm to respect `{}`", method_str), + &format!("consider changing the case of this arm to respect `{}`", method_str), format!("\"{}\"", suggestion), Applicability::MachineApplicable, ); diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 3e765173fb9..b2a873ef582 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -791,7 +791,7 @@ /// the match block and thus will not unlock. /// /// ### Example - /// ```rust.ignore + /// ```rust,ignore /// # use std::sync::Mutex; /// /// # struct State {} @@ -963,7 +963,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { return; } if matches!(source, MatchSource::Normal | MatchSource::ForLoopDesugar) { - significant_drop_in_scrutinee::check(cx, expr, ex, source); + significant_drop_in_scrutinee::check(cx, expr, ex, arms, source); } collapsible_match::check_match(cx, arms); diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index 0ea3f3b673b..8499e050af2 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -3,16 +3,13 @@ use clippy_utils::source::snippet; use clippy_utils::sugg::Sugg; use clippy_utils::ty::needs_ordered_drop; -use clippy_utils::{higher, match_def_path}; -use clippy_utils::{is_lang_ctor, is_trait_method, paths}; +use clippy_utils::visitors::any_temporaries_need_ordered_drop; +use clippy_utils::{higher, is_lang_ctor, is_trait_method, match_def_path, paths}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, PollPending}; -use rustc_hir::{ - intravisit::{walk_expr, Visitor}, - Arm, Block, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp, -}; +use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty}; use rustc_span::sym; @@ -47,79 +44,6 @@ fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option> { } } -// Checks if there are any temporaries created in the given expression for which drop order -// matters. -fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - struct V<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - res: bool, - } - impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> { - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - match expr.kind { - // Taking the reference of a value leaves a temporary - // e.g. In `&String::new()` the string is a temporary value. - // Remaining fields are temporary values - // e.g. In `(String::new(), 0).1` the string is a temporary value. - ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => { - if !matches!(expr.kind, ExprKind::Path(_)) { - if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) { - self.res = true; - } else { - self.visit_expr(expr); - } - } - }, - // the base type is always taken by reference. - // e.g. In `(vec![0])[0]` the vector is a temporary value. - ExprKind::Index(base, index) => { - if !matches!(base.kind, ExprKind::Path(_)) { - if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) { - self.res = true; - } else { - self.visit_expr(base); - } - } - self.visit_expr(index); - }, - // Method calls can take self by reference. - // e.g. In `String::new().len()` the string is a temporary value. - ExprKind::MethodCall(_, [self_arg, args @ ..], _) => { - if !matches!(self_arg.kind, ExprKind::Path(_)) { - let self_by_ref = self - .cx - .typeck_results() - .type_dependent_def_id(expr.hir_id) - .map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref()); - if self_by_ref && needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg)) { - self.res = true; - } else { - self.visit_expr(self_arg); - } - } - args.iter().for_each(|arg| self.visit_expr(arg)); - }, - // Either explicitly drops values, or changes control flow. - ExprKind::DropTemps(_) - | ExprKind::Ret(_) - | ExprKind::Break(..) - | ExprKind::Yield(..) - | ExprKind::Block(Block { expr: None, .. }, _) - | ExprKind::Loop(..) => (), - - // Only consider the final expression. - ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr), - - _ => walk_expr(self, expr), - } - } - } - - let mut v = V { cx, res: false }; - v.visit_expr(expr); - v.res -} - fn find_sugg_for_if_let<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, @@ -191,7 +115,7 @@ fn find_sugg_for_if_let<'tcx>( // scrutinee would be, so they have to be considered as well. // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held // for the duration if body. - let needs_drop = needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr); + let needs_drop = needs_ordered_drop(cx, check_ty) || any_temporaries_need_ordered_drop(cx, let_expr); // check that `while_let_on_iterator` lint does not trigger if_chain! { @@ -362,9 +286,9 @@ fn find_good_method_for_match<'a>( .qpath_res(path_right, arms[1].pat.hir_id) .opt_def_id()?; let body_node_pair = if match_def_path(cx, left_id, expected_left) && match_def_path(cx, right_id, expected_right) { - (&(*arms[0].body).kind, &(*arms[1].body).kind) + (&arms[0].body.kind, &arms[1].body.kind) } else if match_def_path(cx, right_id, expected_left) && match_def_path(cx, right_id, expected_right) { - (&(*arms[1].body).kind, &(*arms[0].body).kind) + (&arms[1].body.kind, &arms[0].body.kind) } else { return None; }; diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index dcaf6f865de..0704a5af525 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -1,10 +1,10 @@ use crate::FxHashSet; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::get_attr; use clippy_utils::source::{indent_of, snippet}; +use clippy_utils::{get_attr, is_lint_allowed}; use rustc_errors::{Applicability, Diagnostic}; use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{Expr, ExprKind, MatchSource}; +use rustc_hir::{Arm, Expr, ExprKind, MatchSource}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty::subst::GenericArgKind; use rustc_middle::ty::{Ty, TypeAndMut}; @@ -16,12 +16,23 @@ pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, scrutinee: &'tcx Expr<'_>, + arms: &'tcx [Arm<'_>], source: MatchSource, ) { + if is_lint_allowed(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, expr.hir_id) { + return; + } + if let Some((suggestions, message)) = has_significant_drop_in_scrutinee(cx, scrutinee, source) { for found in suggestions { span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| { set_diagnostic(diag, cx, expr, found); + let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None); + diag.span_label(s, "temporary lives until here"); + for span in has_significant_drop_in_arms(cx, arms) { + diag.span_label(span, "another value with significant `Drop` created here"); + } + diag.note("this might lead to deadlocks or other unexpected behavior"); }); } } @@ -80,22 +91,77 @@ fn has_significant_drop_in_scrutinee<'tcx, 'a>( let mut helper = SigDropHelper::new(cx); helper.find_sig_drop(scrutinee).map(|drops| { let message = if source == MatchSource::Normal { - "temporary with significant drop in match scrutinee" + "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression" } else { - "temporary with significant drop in for loop" + "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression" }; (drops, message) }) } +struct SigDropChecker<'a, 'tcx> { + seen_types: FxHashSet>, + cx: &'a LateContext<'tcx>, +} + +impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { + fn new(cx: &'a LateContext<'tcx>) -> SigDropChecker<'a, 'tcx> { + SigDropChecker { + seen_types: FxHashSet::default(), + cx, + } + } + + fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> { + self.cx.typeck_results().expr_ty(ex) + } + + fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool { + !self.seen_types.insert(ty) + } + + fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + if let Some(adt) = ty.ty_adt_def() { + if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 { + return true; + } + } + + match ty.kind() { + rustc_middle::ty::Adt(a, b) => { + for f in a.all_fields() { + let ty = f.ty(cx.tcx, b); + if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) { + return true; + } + } + + for generic_arg in b.iter() { + if let GenericArgKind::Type(ty) = generic_arg.unpack() { + if self.has_sig_drop_attr(cx, ty) { + return true; + } + } + } + false + }, + rustc_middle::ty::Array(ty, _) + | rustc_middle::ty::RawPtr(TypeAndMut { ty, .. }) + | rustc_middle::ty::Ref(_, ty, _) + | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty), + _ => false, + } + } +} + struct SigDropHelper<'a, 'tcx> { cx: &'a LateContext<'tcx>, is_chain_end: bool, - seen_types: FxHashSet>, has_significant_drop: bool, current_sig_drop: Option, sig_drop_spans: Option>, special_handling_for_binary_op: bool, + sig_drop_checker: SigDropChecker<'a, 'tcx>, } #[expect(clippy::enum_variant_names)] @@ -118,11 +184,11 @@ fn new(cx: &'a LateContext<'tcx>) -> SigDropHelper<'a, 'tcx> { SigDropHelper { cx, is_chain_end: true, - seen_types: FxHashSet::default(), has_significant_drop: false, current_sig_drop: None, sig_drop_spans: None, special_handling_for_binary_op: false, + sig_drop_checker: SigDropChecker::new(cx), } } @@ -163,7 +229,7 @@ fn try_setting_current_suggestion(&mut self, expr: &'tcx Expr<'_>, allow_move_an if self.current_sig_drop.is_some() { return; } - let ty = self.get_type(expr); + let ty = self.sig_drop_checker.get_type(expr); if ty.is_ref() { // We checked that the type was ref, so builtin_deref will return Some TypeAndMut, // but let's avoid any chance of an ICE @@ -187,14 +253,6 @@ fn move_current_suggestion(&mut self) { } } - fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> { - self.cx.typeck_results().expr_ty(ex) - } - - fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool { - !self.seen_types.insert(ty) - } - fn visit_exprs_for_binary_ops( &mut self, left: &'tcx Expr<'_>, @@ -214,44 +272,15 @@ fn visit_exprs_for_binary_ops( self.special_handling_for_binary_op = false; } - - fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - if let Some(adt) = ty.ty_adt_def() { - if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 { - return true; - } - } - - match ty.kind() { - rustc_middle::ty::Adt(a, b) => { - for f in a.all_fields() { - let ty = f.ty(cx.tcx, b); - if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) { - return true; - } - } - - for generic_arg in b.iter() { - if let GenericArgKind::Type(ty) = generic_arg.unpack() { - if self.has_sig_drop_attr(cx, ty) { - return true; - } - } - } - false - }, - rustc_middle::ty::Array(ty, _) - | rustc_middle::ty::RawPtr(TypeAndMut { ty, .. }) - | rustc_middle::ty::Ref(_, ty, _) - | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty), - _ => false, - } - } } impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { - if !self.is_chain_end && self.has_sig_drop_attr(self.cx, self.get_type(ex)) { + if !self.is_chain_end + && self + .sig_drop_checker + .has_sig_drop_attr(self.cx, self.sig_drop_checker.get_type(ex)) + { self.has_significant_drop = true; return; } @@ -330,3 +359,38 @@ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { } } } + +struct ArmSigDropHelper<'a, 'tcx> { + sig_drop_checker: SigDropChecker<'a, 'tcx>, + found_sig_drop_spans: FxHashSet, +} + +impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> { + fn new(cx: &'a LateContext<'tcx>) -> ArmSigDropHelper<'a, 'tcx> { + ArmSigDropHelper { + sig_drop_checker: SigDropChecker::new(cx), + found_sig_drop_spans: FxHashSet::::default(), + } + } +} + +fn has_significant_drop_in_arms<'tcx, 'a>(cx: &'a LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> FxHashSet { + let mut helper = ArmSigDropHelper::new(cx); + for arm in arms { + helper.visit_expr(arm.body); + } + helper.found_sig_drop_spans +} + +impl<'a, 'tcx> Visitor<'tcx> for ArmSigDropHelper<'a, 'tcx> { + fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { + if self + .sig_drop_checker + .has_sig_drop_attr(self.sig_drop_checker.cx, self.sig_drop_checker.get_type(ex)) + { + self.found_sig_drop_spans.insert(ex.span); + return; + } + walk_expr(self, ex); + } +} diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 0c4cb45d147..92091a0c339 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -140,70 +140,45 @@ fn check_opt_like<'a>( ty: Ty<'a>, els: Option<&Expr<'_>>, ) { - // list of candidate `Enum`s we know will never get any more members - let candidates = &[ - (&paths::COW, "Borrowed"), - (&paths::COW, "Cow::Borrowed"), - (&paths::COW, "Cow::Owned"), - (&paths::COW, "Owned"), - (&paths::OPTION, "None"), - (&paths::RESULT, "Err"), - (&paths::RESULT, "Ok"), - ]; - - // We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive - // match with the second branch, without enum variants in matches. - if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(arms[0].pat, arms[1].pat) { - return; - } - - let mut paths_and_types = Vec::new(); - if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) { - return; - } - - let in_candidate_enum = |path_info: &(String, Ty<'_>)| -> bool { - let (path, ty) = path_info; - for &(ty_path, pat_path) in candidates { - if path == pat_path && match_type(cx, *ty, ty_path) { - return true; - } - } - false - }; - if paths_and_types.iter().all(in_candidate_enum) { + // We don't want to lint if the second arm contains an enum which could + // have more variants in the future. + if form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat) { report_single_pattern(cx, ex, arms, expr, els); } } -/// Collects paths and their types from the given patterns. Returns true if the given pattern could -/// be simplified, false otherwise. -fn collect_pat_paths<'a>(acc: &mut Vec<(String, Ty<'a>)>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool { +/// Returns `true` if all of the types in the pattern are enums which we know +/// won't be expanded in the future +fn pat_in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'a>, pat: &Pat<'_>) -> bool { + let mut paths_and_types = Vec::new(); + collect_pat_paths(&mut paths_and_types, cx, pat, ty); + paths_and_types.iter().all(|ty| in_candidate_enum(cx, *ty)) +} + +/// Returns `true` if the given type is an enum we know won't be expanded in the future +fn in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'_>) -> bool { + // list of candidate `Enum`s we know will never get any more members + let candidates = [&paths::COW, &paths::OPTION, &paths::RESULT]; + + for candidate_ty in candidates { + if match_type(cx, ty, candidate_ty) { + return true; + } + } + false +} + +/// Collects types from the given pattern +fn collect_pat_paths<'a>(acc: &mut Vec>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) { match pat.kind { - PatKind::Wild => true, - PatKind::Tuple(inner, _) => inner.iter().all(|p| { + PatKind::Tuple(inner, _) => inner.iter().for_each(|p| { let p_ty = cx.typeck_results().pat_ty(p); - collect_pat_paths(acc, cx, p, p_ty) + collect_pat_paths(acc, cx, p, p_ty); }), - PatKind::TupleStruct(ref path, ..) => { - let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { - s.print_qpath(path, false); - }); - acc.push((path, ty)); - true + PatKind::TupleStruct(..) | PatKind::Binding(BindingAnnotation::Unannotated, .., None) | PatKind::Path(_) => { + acc.push(ty); }, - PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => { - acc.push((ident.to_string(), ty)); - true - }, - PatKind::Path(ref path) => { - let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { - s.print_qpath(path, false); - }); - acc.push((path, ty)); - true - }, - _ => false, + _ => {}, } } @@ -218,7 +193,7 @@ fn contains_only_wilds(pat: &Pat<'_>) -> bool { /// Returns true if the given patterns forms only exhaustive matches that don't contain enum /// patterns without a wildcard. -fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool { +fn form_exhaustive_matches<'a>(cx: &LateContext<'a>, ty: Ty<'a>, left: &Pat<'_>, right: &Pat<'_>) -> bool { match (&left.kind, &right.kind) { (PatKind::Wild, _) | (_, PatKind::Wild) => true, (PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => { @@ -264,6 +239,10 @@ fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool { } true }, + (PatKind::TupleStruct(..), PatKind::Path(_)) => pat_in_candidate_enum(cx, ty, right), + (PatKind::TupleStruct(..), PatKind::TupleStruct(_, inner, _)) => { + pat_in_candidate_enum(cx, ty, right) && inner.iter().all(contains_only_wilds) + }, _ => false, } } diff --git a/clippy_lints/src/methods/map_flatten.rs b/clippy_lints/src/methods/map_flatten.rs index 8ae84dbb3dc..13853dec99d 100644 --- a/clippy_lints/src/methods/map_flatten.rs +++ b/clippy_lints/src/methods/map_flatten.rs @@ -14,14 +14,17 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) { if let Some((caller_ty_name, method_to_use)) = try_get_caller_ty_name_and_method_name(cx, expr, recv, map_arg) { let mut applicability = Applicability::MachineApplicable; - + let closure_snippet = snippet_with_applicability(cx, map_arg.span, "..", &mut applicability); span_lint_and_sugg( cx, MAP_FLATTEN, expr.span.with_lo(map_span.lo()), &format!("called `map(..).flatten()` on `{}`", caller_ty_name), - &format!("try replacing `map` with `{}` and remove the `.flatten()`", method_to_use), + &format!( + "try replacing `map` with `{}` and remove the `.flatten()`", + method_to_use + ), format!("{}({})", method_to_use, closure_snippet), applicability, ); diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index 01bf871198a..df2430ced6b 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -1,28 +1,21 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; +use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet, snippet_opt}; -use clippy_utils::ty::{implements_trait, is_copy}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{ self as hir, def, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind, Stmt, - StmtKind, TyKind, UnOp, + StmtKind, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::hygiene::DesugaringKind; use rustc_span::source_map::{ExpnKind, Span}; -use rustc_span::symbol::sym; -use clippy_utils::consts::{constant, Constant}; use clippy_utils::sugg::Sugg; -use clippy_utils::{ - get_item_name, get_parent_expr, in_constant, is_integer_const, iter_input_pats, last_path_segment, - match_any_def_paths, path_def_id, paths, unsext, SpanlessEq, -}; +use clippy_utils::{get_parent_expr, in_constant, iter_input_pats, last_path_segment, SpanlessEq}; declare_clippy_lint! { /// ### What it does @@ -58,122 +51,6 @@ style, "an entire binding declared as `ref`, in a function argument or a `let` statement" } - -declare_clippy_lint! { - /// ### What it does - /// Checks for comparisons to NaN. - /// - /// ### Why is this bad? - /// NaN does not compare meaningfully to anything – not - /// even itself – so those comparisons are simply wrong. - /// - /// ### Example - /// ```rust - /// # let x = 1.0; - /// if x == f32::NAN { } - /// ``` - /// - /// Use instead: - /// ```rust - /// # let x = 1.0f32; - /// if x.is_nan() { } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CMP_NAN, - correctness, - "comparisons to `NAN`, which will always return false, probably not intended" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for (in-)equality comparisons on floating-point - /// values (apart from zero), except in functions called `*eq*` (which probably - /// implement equality for a type involving floats). - /// - /// ### Why is this bad? - /// Floating point calculations are usually imprecise, so - /// asking if two values are *exactly* equal is asking for trouble. For a good - /// guide on what to do, see [the floating point - /// guide](http://www.floating-point-gui.de/errors/comparison). - /// - /// ### Example - /// ```rust - /// let x = 1.2331f64; - /// let y = 1.2332f64; - /// - /// if y == 1.23f64 { } - /// if y != x {} // where both are floats - /// ``` - /// - /// Use instead: - /// ```rust - /// # let x = 1.2331f64; - /// # let y = 1.2332f64; - /// let error_margin = f64::EPSILON; // Use an epsilon for comparison - /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. - /// // let error_margin = std::f64::EPSILON; - /// if (y - 1.23f64).abs() < error_margin { } - /// if (y - x).abs() > error_margin { } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub FLOAT_CMP, - pedantic, - "using `==` or `!=` on float values instead of comparing difference with an epsilon" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for conversions to owned values just for the sake - /// of a comparison. - /// - /// ### Why is this bad? - /// The comparison can operate on a reference, so creating - /// an owned value effectively throws it away directly afterwards, which is - /// needlessly consuming code and heap space. - /// - /// ### Example - /// ```rust - /// # let x = "foo"; - /// # let y = String::from("foo"); - /// if x.to_owned() == y {} - /// ``` - /// - /// Use instead: - /// ```rust - /// # let x = "foo"; - /// # let y = String::from("foo"); - /// if x == y {} - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CMP_OWNED, - perf, - "creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for getting the remainder of a division by one or minus - /// one. - /// - /// ### Why is this bad? - /// The result for a divisor of one can only ever be zero; for - /// minus one it can cause panic/overflow (if the left operand is the minimal value of - /// the respective integer type) or results in zero. No one will write such code - /// deliberately, unless trying to win an Underhanded Rust Contest. Even for that - /// contest, it's probably a bad idea. Use something more underhanded. - /// - /// ### Example - /// ```rust - /// # let x = 1; - /// let a = x % 1; - /// let a = x % -1; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MODULO_ONE, - correctness, - "taking a number modulo +/-1, which can either panic/overflow or always returns 0" -} - declare_clippy_lint! { /// ### What it does /// Checks for the use of bindings with a single leading @@ -244,51 +121,11 @@ "using `0 as *{const, mut} T`" } -declare_clippy_lint! { - /// ### What it does - /// Checks for (in-)equality comparisons on floating-point - /// value and constant, except in functions called `*eq*` (which probably - /// implement equality for a type involving floats). - /// - /// ### Why is this bad? - /// Floating point calculations are usually imprecise, so - /// asking if two values are *exactly* equal is asking for trouble. For a good - /// guide on what to do, see [the floating point - /// guide](http://www.floating-point-gui.de/errors/comparison). - /// - /// ### Example - /// ```rust - /// let x: f64 = 1.0; - /// const ONE: f64 = 1.00; - /// - /// if x == ONE { } // where both are floats - /// ``` - /// - /// Use instead: - /// ```rust - /// # let x: f64 = 1.0; - /// # const ONE: f64 = 1.00; - /// let error_margin = f64::EPSILON; // Use an epsilon for comparison - /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. - /// // let error_margin = std::f64::EPSILON; - /// if (x - ONE).abs() < error_margin { } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub FLOAT_CMP_CONST, - restriction, - "using `==` or `!=` on float constants instead of comparing difference with an epsilon" -} - declare_lint_pass!(MiscLints => [ TOPLEVEL_REF_ARG, - CMP_NAN, - FLOAT_CMP, - CMP_OWNED, - MODULO_ONE, USED_UNDERSCORE_BINDING, SHORT_CIRCUIT_STATEMENT, ZERO_PTR, - FLOAT_CMP_CONST ]); impl<'tcx> LateLintPass<'tcx> for MiscLints { @@ -398,16 +235,9 @@ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - match expr.kind { - ExprKind::Cast(e, ty) => { - check_cast(cx, expr.span, e, ty); - return; - }, - ExprKind::Binary(ref cmp, left, right) => { - check_binary(cx, expr, cmp, left, right); - return; - }, - _ => {}, + if let ExprKind::Cast(e, ty) = expr.kind { + check_cast(cx, expr.span, e, ty); + return; } if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) { // Don't lint things expanded by #[derive(...)], etc or `await` desugaring @@ -455,236 +285,6 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { } } -fn get_lint_and_message( - is_comparing_constants: bool, - is_comparing_arrays: bool, -) -> (&'static rustc_lint::Lint, &'static str) { - if is_comparing_constants { - ( - FLOAT_CMP_CONST, - if is_comparing_arrays { - "strict comparison of `f32` or `f64` constant arrays" - } else { - "strict comparison of `f32` or `f64` constant" - }, - ) - } else { - ( - FLOAT_CMP, - if is_comparing_arrays { - "strict comparison of `f32` or `f64` arrays" - } else { - "strict comparison of `f32` or `f64`" - }, - ) - } -} - -fn check_nan(cx: &LateContext<'_>, expr: &Expr<'_>, cmp_expr: &Expr<'_>) { - if_chain! { - if !in_constant(cx, cmp_expr.hir_id); - if let Some((value, _)) = constant(cx, cx.typeck_results(), expr); - if match value { - Constant::F32(num) => num.is_nan(), - Constant::F64(num) => num.is_nan(), - _ => false, - }; - then { - span_lint( - cx, - CMP_NAN, - cmp_expr.span, - "doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead", - ); - } - } -} - -fn is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { - if let Some((_, res)) = constant(cx, cx.typeck_results(), expr) { - res - } else { - false - } -} - -fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { - match constant(cx, cx.typeck_results(), expr) { - Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(), - Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(), - Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f { - Constant::F32(f) => *f == 0.0 || (*f).is_infinite(), - Constant::F64(f) => *f == 0.0 || (*f).is_infinite(), - _ => false, - }), - _ => false, - } -} - -// Return true if `expr` is the result of `signum()` invoked on a float value. -fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - // The negation of a signum is still a signum - if let ExprKind::Unary(UnOp::Neg, child_expr) = expr.kind { - return is_signum(cx, child_expr); - } - - if_chain! { - if let ExprKind::MethodCall(method_name, [ref self_arg, ..], _) = expr.kind; - if sym!(signum) == method_name.ident.name; - // Check that the receiver of the signum() is a float (expressions[0] is the receiver of - // the method call) - then { - return is_float(cx, self_arg); - } - } - false -} - -fn is_float(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - let value = &cx.typeck_results().expr_ty(expr).peel_refs().kind(); - - if let ty::Array(arr_ty, _) = value { - return matches!(arr_ty.kind(), ty::Float(_)); - }; - - matches!(value, ty::Float(_)) -} - -fn is_array(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - matches!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _)) -} - -#[expect(clippy::too_many_lines)] -fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) { - #[derive(Default)] - struct EqImpl { - ty_eq_other: bool, - other_eq_ty: bool, - } - - impl EqImpl { - fn is_implemented(&self) -> bool { - self.ty_eq_other || self.other_eq_ty - } - } - - fn symmetric_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> Option { - cx.tcx.lang_items().eq_trait().map(|def_id| EqImpl { - ty_eq_other: implements_trait(cx, ty, def_id, &[other.into()]), - other_eq_ty: implements_trait(cx, other, def_id, &[ty.into()]), - }) - } - - let typeck = cx.typeck_results(); - let (arg, arg_span) = match expr.kind { - ExprKind::MethodCall(.., [arg], _) - if typeck - .type_dependent_def_id(expr.hir_id) - .and_then(|id| cx.tcx.trait_of_item(id)) - .map_or(false, |id| { - matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned)) - }) => - { - (arg, arg.span) - }, - ExprKind::Call(path, [arg]) - if path_def_id(cx, path) - .and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM])) - .map_or(false, |idx| match idx { - 0 => true, - 1 => !is_copy(cx, typeck.expr_ty(expr)), - _ => false, - }) => - { - (arg, arg.span) - }, - _ => return, - }; - - let arg_ty = typeck.expr_ty(arg); - let other_ty = typeck.expr_ty(other); - - let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default(); - let with_deref = arg_ty - .builtin_deref(true) - .and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty)) - .unwrap_or_default(); - - if !with_deref.is_implemented() && !without_deref.is_implemented() { - return; - } - - let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _)); - - let lint_span = if other_gets_derefed { - expr.span.to(other.span) - } else { - expr.span - }; - - span_lint_and_then( - cx, - CMP_OWNED, - lint_span, - "this creates an owned instance just for comparison", - |diag| { - // This also catches `PartialEq` implementations that call `to_owned`. - if other_gets_derefed { - diag.span_label(lint_span, "try implementing the comparison without allocating"); - return; - } - - let arg_snip = snippet(cx, arg_span, ".."); - let expr_snip; - let eq_impl; - if with_deref.is_implemented() { - expr_snip = format!("*{}", arg_snip); - eq_impl = with_deref; - } else { - expr_snip = arg_snip.to_string(); - eq_impl = without_deref; - }; - - let span; - let hint; - if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) { - span = expr.span; - hint = expr_snip; - } else { - span = expr.span.to(other.span); - - let cmp_span = if other.span < expr.span { - other.span.between(expr.span) - } else { - expr.span.between(other.span) - }; - if eq_impl.ty_eq_other { - hint = format!( - "{}{}{}", - expr_snip, - snippet(cx, cmp_span, ".."), - snippet(cx, other.span, "..") - ); - } else { - hint = format!( - "{}{}{}", - snippet(cx, other.span, ".."), - snippet(cx, cmp_span, ".."), - expr_snip - ); - } - } - - diag.span_suggestion( - span, - "try", - hint, - Applicability::MachineApplicable, // snippet - ); - }, - ); -} - /// Heuristic to see if an expression is used. Should be compatible with /// `unused_variables`'s idea /// of what it means for an expression to be "used". @@ -740,74 +340,3 @@ fn check_cast(cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>) } } } - -fn check_binary<'a>( - cx: &LateContext<'a>, - expr: &Expr<'_>, - cmp: &rustc_span::source_map::Spanned, - left: &'a Expr<'_>, - right: &'a Expr<'_>, -) { - let op = cmp.node; - if op.is_comparison() { - check_nan(cx, left, expr); - check_nan(cx, right, expr); - check_to_owned(cx, left, right, true); - check_to_owned(cx, right, left, false); - } - if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) { - if is_allowed(cx, left) || is_allowed(cx, right) { - return; - } - - // Allow comparing the results of signum() - if is_signum(cx, left) && is_signum(cx, right) { - return; - } - - if let Some(name) = get_item_name(cx, expr) { - let name = name.as_str(); - if name == "eq" || name == "ne" || name == "is_nan" || name.starts_with("eq_") || name.ends_with("_eq") { - return; - } - } - let is_comparing_arrays = is_array(cx, left) || is_array(cx, right); - let (lint, msg) = get_lint_and_message( - is_named_constant(cx, left) || is_named_constant(cx, right), - is_comparing_arrays, - ); - span_lint_and_then(cx, lint, expr.span, msg, |diag| { - let lhs = Sugg::hir(cx, left, ".."); - let rhs = Sugg::hir(cx, right, ".."); - - if !is_comparing_arrays { - diag.span_suggestion( - expr.span, - "consider comparing them within some margin of error", - format!( - "({}).abs() {} error_margin", - lhs - rhs, - if op == BinOpKind::Eq { '<' } else { '>' } - ), - Applicability::HasPlaceholders, // snippet - ); - } - diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`"); - }); - } else if op == BinOpKind::Rem { - if is_integer_const(cx, right, 1) { - span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0"); - } - - if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() { - if is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) { - span_lint( - cx, - MODULO_ONE, - expr.span, - "any number modulo -1 will panic/overflow or result in 0", - ); - } - }; - } -} diff --git a/clippy_lints/src/needless_bitwise_bool.rs b/clippy_lints/src/needless_bitwise_bool.rs deleted file mode 100644 index 623d22bc9bd..00000000000 --- a/clippy_lints/src/needless_bitwise_bool.rs +++ /dev/null @@ -1,85 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_opt; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for uses of bitwise and/or operators between booleans, where performance may be improved by using - /// a lazy and. - /// - /// ### Why is this bad? - /// The bitwise operators do not support short-circuiting, so it may hinder code performance. - /// Additionally, boolean logic "masked" as bitwise logic is not caught by lints like `unnecessary_fold` - /// - /// ### Known problems - /// This lint evaluates only when the right side is determined to have no side effects. At this time, that - /// determination is quite conservative. - /// - /// ### Example - /// ```rust - /// let (x,y) = (true, false); - /// if x & !y {} // where both x and y are booleans - /// ``` - /// Use instead: - /// ```rust - /// let (x,y) = (true, false); - /// if x && !y {} - /// ``` - #[clippy::version = "1.54.0"] - pub NEEDLESS_BITWISE_BOOL, - pedantic, - "Boolean expressions that use bitwise rather than lazy operators" -} - -declare_lint_pass!(NeedlessBitwiseBool => [NEEDLESS_BITWISE_BOOL]); - -fn is_bitwise_operation(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - let ty = cx.typeck_results().expr_ty(expr); - if_chain! { - if !expr.span.from_expansion(); - if let (&ExprKind::Binary(ref op, _, right), &ty::Bool) = (&expr.kind, &ty.kind()); - if op.node == BinOpKind::BitAnd || op.node == BinOpKind::BitOr; - if let ExprKind::Call(..) | ExprKind::MethodCall(..) | ExprKind::Binary(..) | ExprKind::Unary(..) = right.kind; - if !right.can_have_side_effects(); - then { - return true; - } - } - false -} - -fn suggestion_snippet(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - if let ExprKind::Binary(ref op, left, right) = expr.kind { - if let (Some(l_snippet), Some(r_snippet)) = (snippet_opt(cx, left.span), snippet_opt(cx, right.span)) { - let op_snippet = match op.node { - BinOpKind::BitAnd => "&&", - _ => "||", - }; - return Some(format!("{} {} {}", l_snippet, op_snippet, r_snippet)); - } - } - None -} - -impl LateLintPass<'_> for NeedlessBitwiseBool { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if is_bitwise_operation(cx, expr) { - span_lint_and_then( - cx, - NEEDLESS_BITWISE_BOOL, - expr.span, - "use of bitwise operator instead of lazy operator between booleans", - |diag| { - if let Some(sugg) = suggestion_snippet(cx, expr) { - diag.span_suggestion(expr.span, "try", sugg, Applicability::MachineApplicable); - } - }, - ); - } - } -} diff --git a/clippy_lints/src/neg_multiply.rs b/clippy_lints/src/neg_multiply.rs index ce6bb38b7c0..b087cfb36b1 100644 --- a/clippy_lints/src/neg_multiply.rs +++ b/clippy_lints/src/neg_multiply.rs @@ -1,7 +1,9 @@ use clippy_utils::consts::{self, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; +use clippy_utils::sugg::has_enclosing_paren; use if_chain::if_chain; +use rustc_ast::util::parser::PREC_PREFIX; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; @@ -58,7 +60,12 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) { then { let mut applicability = Applicability::MachineApplicable; - let suggestion = format!("-{}", snippet_with_applicability(cx, exp.span, "..", &mut applicability)); + let snip = snippet_with_applicability(cx, exp.span, "..", &mut applicability); + let suggestion = if exp.precedence().order() < PREC_PREFIX && !has_enclosing_paren(&snip) { + format!("-({})", snip) + } else { + format!("-{}", snip) + }; span_lint_and_sugg( cx, NEG_MULTIPLY, diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 1727275a4e0..a1ef32ae608 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -6,6 +6,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::in_constant; +use clippy_utils::macros::macro_backtrace; use if_chain::if_chain; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; @@ -18,7 +19,7 @@ use rustc_middle::ty::adjustment::Adjust; use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{InnerSpan, Span, DUMMY_SP}; +use rustc_span::{sym, InnerSpan, Span, DUMMY_SP}; use rustc_typeck::hir_ty_to_ty; // FIXME: this is a correctness problem but there's no suitable @@ -250,8 +251,14 @@ 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 is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, 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) + { lint(cx, Source::Item { item: it.span }); } } diff --git a/clippy_lints/src/non_expressive_names.rs b/clippy_lints/src/non_expressive_names.rs index 7f6b535c7b1..b96af06b8d7 100644 --- a/clippy_lints/src/non_expressive_names.rs +++ b/clippy_lints/src/non_expressive_names.rs @@ -159,12 +159,10 @@ fn visit_pat(&mut self, pat: &'tcx Pat) { #[must_use] fn get_exemptions(interned_name: &str) -> Option<&'static [&'static str]> { - for &list in ALLOWED_TO_BE_SIMILAR { - if allowed_to_be_similar(interned_name, list) { - return Some(list); - } - } - None + ALLOWED_TO_BE_SIMILAR + .iter() + .find(|&&list| allowed_to_be_similar(interned_name, list)) + .copied() } #[must_use] @@ -328,7 +326,7 @@ fn visit_local(&mut self, local: &'tcx Local) { // add the pattern after the expression because the bindings aren't available // yet in the init // expression - SimilarNamesNameVisitor(self).visit_pat(&*local.pat); + SimilarNamesNameVisitor(self).visit_pat(&local.pat); } fn visit_block(&mut self, blk: &'tcx Block) { self.single_char_names.push(vec![]); diff --git a/clippy_lints/src/numeric_arithmetic.rs b/clippy_lints/src/numeric_arithmetic.rs deleted file mode 100644 index 5c4de338149..00000000000 --- a/clippy_lints/src/numeric_arithmetic.rs +++ /dev/null @@ -1,170 +0,0 @@ -use clippy_utils::consts::constant_simple; -use clippy_utils::diagnostics::span_lint; -use rustc_hir as hir; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::source_map::Span; - -declare_clippy_lint! { - /// ### What it does - /// Checks for integer arithmetic operations which could overflow or panic. - /// - /// Specifically, checks for any operators (`+`, `-`, `*`, `<<`, etc) which are capable - /// of overflowing according to the [Rust - /// Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow), - /// or which can panic (`/`, `%`). No bounds analysis or sophisticated reasoning is - /// attempted. - /// - /// ### 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; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub INTEGER_ARITHMETIC, - restriction, - "any integer arithmetic expression which could overflow or panic" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for float arithmetic. - /// - /// ### Why is this bad? - /// For some embedded systems or kernel development, it - /// can be useful to rule out floating-point numbers. - /// - /// ### Example - /// ```rust - /// # let a = 0.0; - /// a + 1.0; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub FLOAT_ARITHMETIC, - restriction, - "any floating-point arithmetic statement" -} - -#[derive(Copy, Clone, Default)] -pub struct NumericArithmetic { - expr_span: Option, - /// This field is used to check whether expressions are constants, such as in enum discriminants - /// and consts - const_span: Option, -} - -impl_lint_pass!(NumericArithmetic => [INTEGER_ARITHMETIC, FLOAT_ARITHMETIC]); - -impl<'tcx> LateLintPass<'tcx> for NumericArithmetic { - 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 { - if span.contains(expr.span) { - return; - } - } - match &expr.kind { - hir::ExprKind::Binary(op, l, r) | hir::ExprKind::AssignOp(op, l, r) => { - match op.node { - hir::BinOpKind::And - | hir::BinOpKind::Or - | hir::BinOpKind::BitAnd - | hir::BinOpKind::BitOr - | hir::BinOpKind::BitXor - | hir::BinOpKind::Eq - | hir::BinOpKind::Lt - | hir::BinOpKind::Le - | hir::BinOpKind::Ne - | hir::BinOpKind::Ge - | hir::BinOpKind::Gt => return, - _ => (), - } - - let (l_ty, r_ty) = (cx.typeck_results().expr_ty(l), cx.typeck_results().expr_ty(r)); - if l_ty.peel_refs().is_integral() && r_ty.peel_refs().is_integral() { - match op.node { - hir::BinOpKind::Div | hir::BinOpKind::Rem => match &r.kind { - hir::ExprKind::Lit(_lit) => (), - hir::ExprKind::Unary(hir::UnOp::Neg, expr) => { - if let hir::ExprKind::Lit(lit) = &expr.kind { - if let rustc_ast::ast::LitKind::Int(1, _) = lit.node { - span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected"); - self.expr_span = Some(expr.span); - } - } - }, - _ => { - span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected"); - self.expr_span = Some(expr.span); - }, - }, - _ => { - span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected"); - self.expr_span = Some(expr.span); - }, - } - } else if r_ty.peel_refs().is_floating_point() && r_ty.peel_refs().is_floating_point() { - span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected"); - self.expr_span = Some(expr.span); - } - }, - hir::ExprKind::Unary(hir::UnOp::Neg, arg) => { - let ty = cx.typeck_results().expr_ty(arg); - if constant_simple(cx, cx.typeck_results(), expr).is_none() { - if ty.is_integral() { - span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected"); - self.expr_span = Some(expr.span); - } else if ty.is_floating_point() { - span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected"); - self.expr_span = Some(expr.span); - } - } - }, - _ => (), - } - } - - fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - if Some(expr.span) == self.expr_span { - self.expr_span = None; - } - } - - 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::Static(_) | hir::BodyOwnerKind::Const => { - let body_span = cx.tcx.def_span(body_owner); - - if let Some(span) = self.const_span { - if span.contains(body_span) { - return; - } - } - self.const_span = Some(body_span); - }, - hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => (), - } - } - - 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 { - if span.contains(body_span) { - return; - } - } - self.const_span = None; - } -} diff --git a/clippy_lints/src/absurd_extreme_comparisons.rs b/clippy_lints/src/operators/absurd_extreme_comparisons.rs similarity index 53% rename from clippy_lints/src/absurd_extreme_comparisons.rs rename to clippy_lints/src/operators/absurd_extreme_comparisons.rs index 7665aa8380b..1ec4240afef 100644 --- a/clippy_lints/src/absurd_extreme_comparisons.rs +++ b/clippy_lints/src/operators/absurd_extreme_comparisons.rs @@ -1,7 +1,6 @@ use rustc_hir::{BinOpKind, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::LateContext; use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; use clippy_utils::comparisons::{normalize_comparison, Rel}; use clippy_utils::consts::{constant, Constant}; @@ -10,73 +9,41 @@ use clippy_utils::ty::is_isize_or_usize; use clippy_utils::{clip, int_bits, unsext}; -declare_clippy_lint! { - /// ### What it does - /// Checks for comparisons where one side of the relation is - /// either the minimum or maximum value for its type and warns if it involves a - /// case that is always true or always false. Only integer and boolean types are - /// checked. - /// - /// ### Why is this bad? - /// An expression like `min <= x` may misleadingly imply - /// that it is possible for `x` to be less than the minimum. Expressions like - /// `max < x` are probably mistakes. - /// - /// ### Known problems - /// For `usize` the size of the current compile target will - /// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such - /// a comparison to detect target pointer width will trigger this lint. One can - /// use `mem::sizeof` and compare its value or conditional compilation - /// attributes - /// like `#[cfg(target_pointer_width = "64")] ..` instead. - /// - /// ### Example - /// ```rust - /// let vec: Vec = Vec::new(); - /// if vec.len() <= 0 {} - /// if 100 > i32::MAX {} - /// ``` - #[clippy::version = "pre 1.29.0"] - pub ABSURD_EXTREME_COMPARISONS, - correctness, - "a comparison with a maximum or minimum value that is always true or false" -} +use super::ABSURD_EXTREME_COMPARISONS; -declare_lint_pass!(AbsurdExtremeComparisons => [ABSURD_EXTREME_COMPARISONS]); +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + op: BinOpKind, + lhs: &'tcx Expr<'_>, + rhs: &'tcx Expr<'_>, +) { + if let Some((culprit, result)) = detect_absurd_comparison(cx, op, lhs, rhs) { + let msg = "this comparison involving the minimum or maximum element for this \ + type contains a case that is always true or always false"; -impl<'tcx> LateLintPass<'tcx> for AbsurdExtremeComparisons { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind { - if let Some((culprit, result)) = detect_absurd_comparison(cx, cmp.node, lhs, rhs) { - if !expr.span.from_expansion() { - let msg = "this comparison involving the minimum or maximum element for this \ - type contains a case that is always true or always false"; + let conclusion = match result { + AbsurdComparisonResult::AlwaysFalse => "this comparison is always false".to_owned(), + AbsurdComparisonResult::AlwaysTrue => "this comparison is always true".to_owned(), + AbsurdComparisonResult::InequalityImpossible => format!( + "the case where the two sides are not equal never occurs, consider using `{} == {}` \ + instead", + snippet(cx, lhs.span, "lhs"), + snippet(cx, rhs.span, "rhs") + ), + }; - let conclusion = match result { - AbsurdComparisonResult::AlwaysFalse => "this comparison is always false".to_owned(), - AbsurdComparisonResult::AlwaysTrue => "this comparison is always true".to_owned(), - AbsurdComparisonResult::InequalityImpossible => format!( - "the case where the two sides are not equal never occurs, consider using `{} == {}` \ - instead", - snippet(cx, lhs.span, "lhs"), - snippet(cx, rhs.span, "rhs") - ), - }; + let help = format!( + "because `{}` is the {} value for this type, {}", + snippet(cx, culprit.expr.span, "x"), + match culprit.which { + ExtremeType::Minimum => "minimum", + ExtremeType::Maximum => "maximum", + }, + conclusion + ); - let help = format!( - "because `{}` is the {} value for this type, {}", - snippet(cx, culprit.expr.span, "x"), - match culprit.which { - ExtremeType::Minimum => "minimum", - ExtremeType::Maximum => "maximum", - }, - conclusion - ); - - span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help); - } - } - } + span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help); } } diff --git a/clippy_lints/src/operators/assign_op_pattern.rs b/clippy_lints/src/operators/assign_op_pattern.rs new file mode 100644 index 00000000000..979e0a66707 --- /dev/null +++ b/clippy_lints/src/operators/assign_op_pattern.rs @@ -0,0 +1,101 @@ +use clippy_utils::binop_traits; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_opt; +use clippy_utils::ty::implements_trait; +use clippy_utils::{eq_expr_value, trait_ref_of_method}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::intravisit::{walk_expr, Visitor}; +use rustc_lint::LateContext; + +use super::ASSIGN_OP_PATTERN; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + assignee: &'tcx hir::Expr<'_>, + e: &'tcx hir::Expr<'_>, +) { + if let hir::ExprKind::Binary(op, l, r) = &e.kind { + let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| { + let ty = cx.typeck_results().expr_ty(assignee); + let rty = cx.typeck_results().expr_ty(rhs); + if_chain! { + if let Some((_, lang_item)) = binop_traits(op.node); + if let Ok(trait_id) = cx.tcx.lang_items().require(lang_item); + let parent_fn = cx.tcx.hir().get_parent_item(e.hir_id); + if trait_ref_of_method(cx, parent_fn) + .map_or(true, |t| t.path.res.def_id() != trait_id); + if implements_trait(cx, ty, trait_id, &[rty.into()]); + then { + span_lint_and_then( + cx, + ASSIGN_OP_PATTERN, + expr.span, + "manual implementation of an assign operation", + |diag| { + if let (Some(snip_a), Some(snip_r)) = + (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs.span)) + { + diag.span_suggestion( + expr.span, + "replace it with", + format!("{} {}= {}", snip_a, op.node.as_str(), snip_r), + Applicability::MachineApplicable, + ); + } + }, + ); + } + } + }; + + let mut visitor = ExprVisitor { + assignee, + counter: 0, + cx, + }; + + walk_expr(&mut visitor, e); + + if visitor.counter == 1 { + // a = a op b + if eq_expr_value(cx, assignee, l) { + lint(assignee, r); + } + // a = b commutative_op a + // Limited to primitive type as these ops are know to be commutative + if eq_expr_value(cx, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() { + match op.node { + hir::BinOpKind::Add + | hir::BinOpKind::Mul + | hir::BinOpKind::And + | hir::BinOpKind::Or + | hir::BinOpKind::BitXor + | hir::BinOpKind::BitAnd + | hir::BinOpKind::BitOr => { + lint(assignee, l); + }, + _ => {}, + } + } + } + } +} + +struct ExprVisitor<'a, 'tcx> { + assignee: &'a hir::Expr<'a>, + counter: u8, + cx: &'a LateContext<'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> { + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + if eq_expr_value(self.cx, self.assignee, expr) { + self.counter += 1; + } + + walk_expr(self, expr); + } +} diff --git a/clippy_lints/src/bit_mask.rs b/clippy_lints/src/operators/bit_mask.rs similarity index 51% rename from clippy_lints/src/bit_mask.rs rename to clippy_lints/src/operators/bit_mask.rs index dc7e400fdc2..74387fbc87b 100644 --- a/clippy_lints/src/bit_mask.rs +++ b/clippy_lints/src/operators/bit_mask.rs @@ -1,161 +1,23 @@ use clippy_utils::consts::{constant, Constant}; -use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; -use clippy_utils::sugg::Sugg; -use rustc_ast::ast::LitKind; -use rustc_errors::Applicability; +use clippy_utils::diagnostics::span_lint; use rustc_hir::{BinOpKind, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_lint::LateContext; use rustc_span::source_map::Span; -declare_clippy_lint! { - /// ### What it does - /// Checks for incompatible bit masks in comparisons. - /// - /// The formula for detecting if an expression of the type `_ m - /// c` (where `` is one of {`&`, `|`} and `` is one of - /// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following - /// table: - /// - /// |Comparison |Bit Op|Example |is always|Formula | - /// |------------|------|-------------|---------|----------------------| - /// |`==` or `!=`| `&` |`x & 2 == 3` |`false` |`c & m != c` | - /// |`<` or `>=`| `&` |`x & 2 < 3` |`true` |`m < c` | - /// |`>` or `<=`| `&` |`x & 1 > 1` |`false` |`m <= c` | - /// |`==` or `!=`| `\|` |`x \| 1 == 0`|`false` |`c \| m != c` | - /// |`<` or `>=`| `\|` |`x \| 1 < 1` |`false` |`m >= c` | - /// |`<=` or `>` | `\|` |`x \| 1 > 0` |`true` |`m > c` | - /// - /// ### Why is this bad? - /// If the bits that the comparison cares about are always - /// set to zero or one by the bit mask, the comparison is constant `true` or - /// `false` (depending on mask, compared value, and operators). - /// - /// So the code is actively misleading, and the only reason someone would write - /// this intentionally is to win an underhanded Rust contest or create a - /// test-case for this lint. - /// - /// ### Example - /// ```rust - /// # let x = 1; - /// if (x & 1 == 2) { } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub BAD_BIT_MASK, - correctness, - "expressions of the form `_ & mask == select` that will only ever return `true` or `false`" -} +use super::{BAD_BIT_MASK, INEFFECTIVE_BIT_MASK}; -declare_clippy_lint! { - /// ### What it does - /// Checks for bit masks in comparisons which can be removed - /// without changing the outcome. The basic structure can be seen in the - /// following table: - /// - /// |Comparison| Bit Op |Example |equals | - /// |----------|----------|------------|-------| - /// |`>` / `<=`|`\|` / `^`|`x \| 2 > 3`|`x > 3`| - /// |`<` / `>=`|`\|` / `^`|`x ^ 1 < 4` |`x < 4`| - /// - /// ### Why is this bad? - /// Not equally evil as [`bad_bit_mask`](#bad_bit_mask), - /// but still a bit misleading, because the bit mask is ineffective. - /// - /// ### Known problems - /// False negatives: This lint will only match instances - /// where we have figured out the math (which is for a power-of-two compared - /// value). This means things like `x | 1 >= 7` (which would be better written - /// as `x >= 6`) will not be reported (but bit masks like this are fairly - /// uncommon). - /// - /// ### Example - /// ```rust - /// # let x = 1; - /// if (x | 1 > 3) { } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub INEFFECTIVE_BIT_MASK, - correctness, - "expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for bit masks that can be replaced by a call - /// to `trailing_zeros` - /// - /// ### Why is this bad? - /// `x.trailing_zeros() > 4` is much clearer than `x & 15 - /// == 0` - /// - /// ### Known problems - /// llvm generates better code for `x & 15 == 0` on x86 - /// - /// ### Example - /// ```rust - /// # let x = 1; - /// if x & 0b1111 == 0 { } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub VERBOSE_BIT_MASK, - pedantic, - "expressions where a bit mask is less readable than the corresponding method call" -} - -#[derive(Copy, Clone)] -pub struct BitMask { - verbose_bit_mask_threshold: u64, -} - -impl BitMask { - #[must_use] - pub fn new(verbose_bit_mask_threshold: u64) -> Self { - Self { - verbose_bit_mask_threshold, - } - } -} - -impl_lint_pass!(BitMask => [BAD_BIT_MASK, INEFFECTIVE_BIT_MASK, VERBOSE_BIT_MASK]); - -impl<'tcx> LateLintPass<'tcx> for BitMask { - fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if let ExprKind::Binary(cmp, left, right) = &e.kind { - if cmp.node.is_comparison() { - if let Some(cmp_opt) = fetch_int_literal(cx, right) { - check_compare(cx, left, cmp.node, cmp_opt, e.span); - } else if let Some(cmp_val) = fetch_int_literal(cx, left) { - check_compare(cx, right, invert_cmp(cmp.node), cmp_val, e.span); - } - } - } - - if let ExprKind::Binary(op, left, right) = &e.kind - && BinOpKind::Eq == op.node - && let ExprKind::Binary(op1, left1, right1) = &left.kind - && BinOpKind::BitAnd == op1.node - && let ExprKind::Lit(lit) = &right1.kind - && let LitKind::Int(n, _) = lit.node - && let ExprKind::Lit(lit1) = &right.kind - && let LitKind::Int(0, _) = lit1.node - && n.leading_zeros() == n.count_zeros() - && n > u128::from(self.verbose_bit_mask_threshold) - { - span_lint_and_then( - cx, - VERBOSE_BIT_MASK, - e.span, - "bit mask could be simplified with a call to `trailing_zeros`", - |diag| { - let sugg = Sugg::hir(cx, left1, "...").maybe_par(); - diag.span_suggestion( - e.span, - "try", - format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()), - Applicability::MaybeIncorrect, - ); - }, - ); +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, +) { + if op.is_comparison() { + if let Some(cmp_opt) = fetch_int_literal(cx, right) { + check_compare(cx, left, op, cmp_opt, e.span); + } else if let Some(cmp_val) = fetch_int_literal(cx, left) { + check_compare(cx, right, invert_cmp(op), cmp_val, e.span); } } } diff --git a/clippy_lints/src/operators/cmp_nan.rs b/clippy_lints/src/operators/cmp_nan.rs new file mode 100644 index 00000000000..786ae1552ad --- /dev/null +++ b/clippy_lints/src/operators/cmp_nan.rs @@ -0,0 +1,30 @@ +use clippy_utils::consts::{constant, Constant}; +use clippy_utils::diagnostics::span_lint; +use clippy_utils::in_constant; +use rustc_hir::{BinOpKind, Expr}; +use rustc_lint::LateContext; + +use super::CMP_NAN; + +pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>) { + if op.is_comparison() && !in_constant(cx, e.hir_id) && (is_nan(cx, lhs) || is_nan(cx, rhs)) { + span_lint( + cx, + CMP_NAN, + e.span, + "doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead", + ); + } +} + +fn is_nan(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { + if let Some((value, _)) = constant(cx, cx.typeck_results(), e) { + match value { + Constant::F32(num) => num.is_nan(), + Constant::F64(num) => num.is_nan(), + _ => false, + } + } else { + false + } +} diff --git a/clippy_lints/src/operators/cmp_owned.rs b/clippy_lints/src/operators/cmp_owned.rs new file mode 100644 index 00000000000..e1f9b5906f6 --- /dev/null +++ b/clippy_lints/src/operators/cmp_owned.rs @@ -0,0 +1,147 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet; +use clippy_utils::ty::{implements_trait, is_copy}; +use clippy_utils::{match_any_def_paths, path_def_id, paths}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; +use rustc_span::symbol::sym; + +use super::CMP_OWNED; + +pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>) { + if op.is_comparison() { + check_op(cx, lhs, rhs, true); + check_op(cx, rhs, lhs, false); + } +} + +#[derive(Default)] +struct EqImpl { + ty_eq_other: bool, + other_eq_ty: bool, +} +impl EqImpl { + fn is_implemented(&self) -> bool { + self.ty_eq_other || self.other_eq_ty + } +} + +fn symmetric_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> Option { + cx.tcx.lang_items().eq_trait().map(|def_id| EqImpl { + ty_eq_other: implements_trait(cx, ty, def_id, &[other.into()]), + other_eq_ty: implements_trait(cx, other, def_id, &[ty.into()]), + }) +} + +fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) { + let typeck = cx.typeck_results(); + let (arg, arg_span) = match expr.kind { + ExprKind::MethodCall(.., [arg], _) + if typeck + .type_dependent_def_id(expr.hir_id) + .and_then(|id| cx.tcx.trait_of_item(id)) + .map_or(false, |id| { + matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned)) + }) => + { + (arg, arg.span) + }, + ExprKind::Call(path, [arg]) + if path_def_id(cx, path) + .and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM])) + .map_or(false, |idx| match idx { + 0 => true, + 1 => !is_copy(cx, typeck.expr_ty(expr)), + _ => false, + }) => + { + (arg, arg.span) + }, + _ => return, + }; + + let arg_ty = typeck.expr_ty(arg); + let other_ty = typeck.expr_ty(other); + + let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default(); + let with_deref = arg_ty + .builtin_deref(true) + .and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty)) + .unwrap_or_default(); + + if !with_deref.is_implemented() && !without_deref.is_implemented() { + return; + } + + let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _)); + + let lint_span = if other_gets_derefed { + expr.span.to(other.span) + } else { + expr.span + }; + + span_lint_and_then( + cx, + CMP_OWNED, + lint_span, + "this creates an owned instance just for comparison", + |diag| { + // This also catches `PartialEq` implementations that call `to_owned`. + if other_gets_derefed { + diag.span_label(lint_span, "try implementing the comparison without allocating"); + return; + } + + let arg_snip = snippet(cx, arg_span, ".."); + let expr_snip; + let eq_impl; + if with_deref.is_implemented() { + expr_snip = format!("*{}", arg_snip); + eq_impl = with_deref; + } else { + expr_snip = arg_snip.to_string(); + eq_impl = without_deref; + }; + + let span; + let hint; + if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) { + span = expr.span; + hint = expr_snip; + } else { + span = expr.span.to(other.span); + + let cmp_span = if other.span < expr.span { + other.span.between(expr.span) + } else { + expr.span.between(other.span) + }; + if eq_impl.ty_eq_other { + hint = format!( + "{}{}{}", + expr_snip, + snippet(cx, cmp_span, ".."), + snippet(cx, other.span, "..") + ); + } else { + hint = format!( + "{}{}{}", + snippet(cx, other.span, ".."), + snippet(cx, cmp_span, ".."), + expr_snip + ); + } + } + + diag.span_suggestion( + span, + "try", + hint, + Applicability::MachineApplicable, // snippet + ); + }, + ); +} diff --git a/clippy_lints/src/operators/double_comparison.rs b/clippy_lints/src/operators/double_comparison.rs new file mode 100644 index 00000000000..56a86d0ffa2 --- /dev/null +++ b/clippy_lints/src/operators/double_comparison.rs @@ -0,0 +1,54 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::eq_expr_value; +use clippy_utils::source::snippet_with_applicability; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +use super::DOUBLE_COMPARISONS; + +#[expect(clippy::similar_names)] +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) { + let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) { + (ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => { + (lb.node, llhs, lrhs, rb.node, rlhs, rrhs) + }, + _ => return, + }; + if !(eq_expr_value(cx, llhs, rlhs) && eq_expr_value(cx, lrhs, rrhs)) { + return; + } + macro_rules! lint_double_comparison { + ($op:tt) => {{ + let mut applicability = Applicability::MachineApplicable; + let lhs_str = snippet_with_applicability(cx, llhs.span, "", &mut applicability); + let rhs_str = snippet_with_applicability(cx, lrhs.span, "", &mut applicability); + let sugg = format!("{} {} {}", lhs_str, stringify!($op), rhs_str); + span_lint_and_sugg( + cx, + DOUBLE_COMPARISONS, + span, + "this binary expression can be simplified", + "try", + sugg, + applicability, + ); + }}; + } + match (op, lkind, rkind) { + (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Lt) | (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Eq) => { + lint_double_comparison!(<=); + }, + (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Eq) => { + lint_double_comparison!(>=); + }, + (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Lt) => { + lint_double_comparison!(!=); + }, + (BinOpKind::And, BinOpKind::Le, BinOpKind::Ge) | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => { + lint_double_comparison!(==); + }, + _ => (), + }; +} diff --git a/clippy_lints/src/operators/duration_subsec.rs b/clippy_lints/src/operators/duration_subsec.rs new file mode 100644 index 00000000000..0d067d1e196 --- /dev/null +++ b/clippy_lints/src/operators/duration_subsec.rs @@ -0,0 +1,44 @@ +use clippy_utils::consts::{constant, Constant}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::DURATION_SUBSEC; + +pub(crate) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, +) { + if op == BinOpKind::Div + && let ExprKind::MethodCall(method_path, [self_arg], _) = left.kind + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_arg).peel_refs(), sym::Duration) + && let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right) + { + let suggested_fn = match (method_path.ident.as_str(), divisor) { + ("subsec_micros", 1_000) | ("subsec_nanos", 1_000_000) => "subsec_millis", + ("subsec_nanos", 1_000) => "subsec_micros", + _ => return, + }; + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + DURATION_SUBSEC, + expr.span, + &format!("calling `{}()` is more concise than this calculation", suggested_fn), + "try", + format!( + "{}.{}()", + snippet_with_applicability(cx, self_arg.span, "_", &mut applicability), + suggested_fn + ), + applicability, + ); + } +} diff --git a/clippy_lints/src/operators/eq_op.rs b/clippy_lints/src/operators/eq_op.rs new file mode 100644 index 00000000000..44cf0bb0612 --- /dev/null +++ b/clippy_lints/src/operators/eq_op.rs @@ -0,0 +1,45 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace}; +use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, is_in_test_function}; +use rustc_hir::{BinOpKind, Expr}; +use rustc_lint::LateContext; + +use super::EQ_OP; + +pub(crate) fn check_assert<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if let Some((macro_call, macro_name)) + = first_node_macro_backtrace(cx, e).find_map(|macro_call| { + let name = cx.tcx.item_name(macro_call.def_id); + matches!(name.as_str(), "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne") + .then(|| (macro_call, name)) + }) + && let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn) + && eq_expr_value(cx, lhs, rhs) + && macro_call.is_local() + && !is_in_test_function(cx.tcx, e.hir_id) + { + span_lint( + cx, + EQ_OP, + lhs.span.to(rhs.span), + &format!("identical args used in this `{}!` macro call", macro_name), + ); + } +} + +pub(crate) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, +) { + if is_useless_with_eq_exprs(op.into()) && eq_expr_value(cx, left, right) && !is_in_test_function(cx.tcx, e.hir_id) { + span_lint( + cx, + EQ_OP, + e.span, + &format!("equal expressions as operands to `{}`", op.as_str()), + ); + } +} diff --git a/clippy_lints/src/operators/erasing_op.rs b/clippy_lints/src/operators/erasing_op.rs new file mode 100644 index 00000000000..066e08f3bd4 --- /dev/null +++ b/clippy_lints/src/operators/erasing_op.rs @@ -0,0 +1,53 @@ +use clippy_utils::consts::{constant_simple, Constant}; +use clippy_utils::diagnostics::span_lint; +use clippy_utils::ty::same_type_and_consts; + +use rustc_hir::{BinOpKind, Expr}; +use rustc_lint::LateContext; +use rustc_middle::ty::TypeckResults; + +use super::ERASING_OP; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, +) { + let tck = cx.typeck_results(); + match op { + BinOpKind::Mul | BinOpKind::BitAnd => { + check_op(cx, tck, left, right, e); + check_op(cx, tck, right, left, e); + }, + BinOpKind::Div => check_op(cx, tck, left, right, e), + _ => (), + } +} + +fn different_types(tck: &TypeckResults<'_>, input: &Expr<'_>, output: &Expr<'_>) -> bool { + let input_ty = tck.expr_ty(input).peel_refs(); + let output_ty = tck.expr_ty(output).peel_refs(); + !same_type_and_consts(input_ty, output_ty) +} + +fn check_op<'tcx>( + cx: &LateContext<'tcx>, + tck: &TypeckResults<'tcx>, + op: &Expr<'tcx>, + other: &Expr<'tcx>, + parent: &Expr<'tcx>, +) { + if constant_simple(cx, tck, op) == Some(Constant::Int(0)) { + if different_types(tck, other, parent) { + return; + } + span_lint( + cx, + ERASING_OP, + parent.span, + "this operation will always return zero. This is likely not the intended outcome", + ); + } +} diff --git a/clippy_lints/src/operators/float_cmp.rs b/clippy_lints/src/operators/float_cmp.rs new file mode 100644 index 00000000000..0ef793443ff --- /dev/null +++ b/clippy_lints/src/operators/float_cmp.rs @@ -0,0 +1,139 @@ +use clippy_utils::consts::{constant, Constant}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::get_item_name; +use clippy_utils::sugg::Sugg; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::{FLOAT_CMP, FLOAT_CMP_CONST}; + +pub(crate) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, +) { + if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) { + if is_allowed(cx, left) || is_allowed(cx, right) { + return; + } + + // Allow comparing the results of signum() + if is_signum(cx, left) && is_signum(cx, right) { + return; + } + + if let Some(name) = get_item_name(cx, expr) { + let name = name.as_str(); + if name == "eq" || name == "ne" || name == "is_nan" || name.starts_with("eq_") || name.ends_with("_eq") { + return; + } + } + let is_comparing_arrays = is_array(cx, left) || is_array(cx, right); + let (lint, msg) = get_lint_and_message( + is_named_constant(cx, left) || is_named_constant(cx, right), + is_comparing_arrays, + ); + span_lint_and_then(cx, lint, expr.span, msg, |diag| { + let lhs = Sugg::hir(cx, left, ".."); + let rhs = Sugg::hir(cx, right, ".."); + + if !is_comparing_arrays { + diag.span_suggestion( + expr.span, + "consider comparing them within some margin of error", + format!( + "({}).abs() {} error_margin", + lhs - rhs, + if op == BinOpKind::Eq { '<' } else { '>' } + ), + Applicability::HasPlaceholders, // snippet + ); + } + diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`"); + }); + } +} + +fn get_lint_and_message( + is_comparing_constants: bool, + is_comparing_arrays: bool, +) -> (&'static rustc_lint::Lint, &'static str) { + if is_comparing_constants { + ( + FLOAT_CMP_CONST, + if is_comparing_arrays { + "strict comparison of `f32` or `f64` constant arrays" + } else { + "strict comparison of `f32` or `f64` constant" + }, + ) + } else { + ( + FLOAT_CMP, + if is_comparing_arrays { + "strict comparison of `f32` or `f64` arrays" + } else { + "strict comparison of `f32` or `f64`" + }, + ) + } +} + +fn is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + if let Some((_, res)) = constant(cx, cx.typeck_results(), expr) { + res + } else { + false + } +} + +fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + match constant(cx, cx.typeck_results(), expr) { + Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(), + Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(), + Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f { + Constant::F32(f) => *f == 0.0 || (*f).is_infinite(), + Constant::F64(f) => *f == 0.0 || (*f).is_infinite(), + _ => false, + }), + _ => false, + } +} + +// Return true if `expr` is the result of `signum()` invoked on a float value. +fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + // The negation of a signum is still a signum + if let ExprKind::Unary(UnOp::Neg, child_expr) = expr.kind { + return is_signum(cx, child_expr); + } + + if_chain! { + if let ExprKind::MethodCall(method_name, [ref self_arg, ..], _) = expr.kind; + if sym!(signum) == method_name.ident.name; + // Check that the receiver of the signum() is a float (expressions[0] is the receiver of + // the method call) + then { + return is_float(cx, self_arg); + } + } + false +} + +fn is_float(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let value = &cx.typeck_results().expr_ty(expr).peel_refs().kind(); + + if let ty::Array(arr_ty, _) = value { + return matches!(arr_ty.kind(), ty::Float(_)); + }; + + matches!(value, ty::Float(_)) +} + +fn is_array(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + matches!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _)) +} diff --git a/clippy_lints/src/operators/float_equality_without_abs.rs b/clippy_lints/src/operators/float_equality_without_abs.rs new file mode 100644 index 00000000000..a0a8b6aabd9 --- /dev/null +++ b/clippy_lints/src/operators/float_equality_without_abs.rs @@ -0,0 +1,71 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::{match_def_path, paths, sugg}; +use if_chain::if_chain; +use rustc_ast::util::parser::AssocOp; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::source_map::Spanned; + +use super::FLOAT_EQUALITY_WITHOUT_ABS; + +pub(crate) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + op: BinOpKind, + lhs: &'tcx Expr<'_>, + rhs: &'tcx Expr<'_>, +) { + let (lhs, rhs) = match op { + BinOpKind::Lt => (lhs, rhs), + BinOpKind::Gt => (rhs, lhs), + _ => return, + }; + + if_chain! { + // left hand side is a subtraction + if let ExprKind::Binary( + Spanned { + node: BinOpKind::Sub, + .. + }, + val_l, + val_r, + ) = lhs.kind; + + // right hand side matches either f32::EPSILON or f64::EPSILON + if let ExprKind::Path(ref epsilon_path) = rhs.kind; + if let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id); + if match_def_path(cx, def_id, &paths::F32_EPSILON) || match_def_path(cx, def_id, &paths::F64_EPSILON); + + // values of the subtractions on the left hand side are of the type float + let t_val_l = cx.typeck_results().expr_ty(val_l); + let t_val_r = cx.typeck_results().expr_ty(val_r); + if let ty::Float(_) = t_val_l.kind(); + if let ty::Float(_) = t_val_r.kind(); + + then { + let sug_l = sugg::Sugg::hir(cx, val_l, ".."); + let sug_r = sugg::Sugg::hir(cx, val_r, ".."); + // format the suggestion + let suggestion = format!("{}.abs()", sugg::make_assoc(AssocOp::Subtract, &sug_l, &sug_r).maybe_par()); + // spans the lint + span_lint_and_then( + cx, + FLOAT_EQUALITY_WITHOUT_ABS, + expr.span, + "float equality check without `.abs()`", + | diag | { + diag.span_suggestion( + lhs.span, + "add `.abs()`", + suggestion, + Applicability::MaybeIncorrect, + ); + } + ); + } + } +} diff --git a/clippy_lints/src/identity_op.rs b/clippy_lints/src/operators/identity_op.rs similarity index 60% rename from clippy_lints/src/identity_op.rs rename to clippy_lints/src/operators/identity_op.rs index 419ea5a6811..b48d6c4e2e2 100644 --- a/clippy_lints/src/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -3,61 +3,40 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::{clip, unsext}; use rustc_errors::Applicability; -use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, Node}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_hir::{BinOpKind, Expr, ExprKind, Node}; +use rustc_lint::LateContext; use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; -declare_clippy_lint! { - /// ### What it does - /// Checks for identity operations, e.g., `x + 0`. - /// - /// ### Why is this bad? - /// This code can be removed without changing the - /// meaning. So it just obscures what's going on. Delete it mercilessly. - /// - /// ### Example - /// ```rust - /// # let x = 1; - /// x / 1 + 0 * 1 - 0 | 0; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub IDENTITY_OP, - complexity, - "using identity operations, e.g., `x + 0` or `y / 1`" -} +use super::IDENTITY_OP; -declare_lint_pass!(IdentityOp => [IDENTITY_OP]); - -impl<'tcx> LateLintPass<'tcx> for IdentityOp { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if expr.span.from_expansion() { - return; - } - if let ExprKind::Binary(cmp, left, right) = &expr.kind { - if !is_allowed(cx, *cmp, left, right) { - match cmp.node { - BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => { - check(cx, left, 0, expr.span, right.span, needs_parenthesis(cx, expr, right)); - check(cx, right, 0, expr.span, left.span, Parens::Unneeded); - }, - BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => { - check(cx, right, 0, expr.span, left.span, Parens::Unneeded); - }, - BinOpKind::Mul => { - check(cx, left, 1, expr.span, right.span, needs_parenthesis(cx, expr, right)); - check(cx, right, 1, expr.span, left.span, Parens::Unneeded); - }, - BinOpKind::Div => check(cx, right, 1, expr.span, left.span, Parens::Unneeded), - BinOpKind::BitAnd => { - check(cx, left, -1, expr.span, right.span, needs_parenthesis(cx, expr, right)); - check(cx, right, -1, expr.span, left.span, Parens::Unneeded); - }, - BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span), - _ => (), - } - } +pub(crate) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, +) { + if !is_allowed(cx, op, left, right) { + match op { + BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => { + check_op(cx, left, 0, expr.span, right.span, needs_parenthesis(cx, expr, right)); + check_op(cx, right, 0, expr.span, left.span, Parens::Unneeded); + }, + BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => { + check_op(cx, right, 0, expr.span, left.span, Parens::Unneeded); + }, + BinOpKind::Mul => { + check_op(cx, left, 1, expr.span, right.span, needs_parenthesis(cx, expr, right)); + check_op(cx, right, 1, expr.span, left.span, Parens::Unneeded); + }, + BinOpKind::Div => check_op(cx, right, 1, expr.span, left.span, Parens::Unneeded), + BinOpKind::BitAnd => { + check_op(cx, left, -1, expr.span, right.span, needs_parenthesis(cx, expr, right)); + check_op(cx, right, -1, expr.span, left.span, Parens::Unneeded); + }, + BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span), + _ => (), } } } @@ -108,12 +87,12 @@ fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>) Parens::Needed } -fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> bool { +fn is_allowed(cx: &LateContext<'_>, cmp: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> bool { // This lint applies to integers !cx.typeck_results().expr_ty(left).peel_refs().is_integral() || !cx.typeck_results().expr_ty(right).peel_refs().is_integral() // `1 << 0` is a common pattern in bit manipulation code - || (cmp.node == BinOpKind::Shl + || (cmp == BinOpKind::Shl && constant_simple(cx, cx.typeck_results(), right) == Some(Constant::Int(0)) && constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1))) } @@ -130,7 +109,7 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span } } -fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens) { +fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens) { if let Some(Constant::Int(v)) = constant_simple(cx, cx.typeck_results(), e).map(Constant::peel_refs) { let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() { ty::Int(ity) => unsext(cx.tcx, -1_i128, ity), diff --git a/clippy_lints/src/operators/integer_division.rs b/clippy_lints/src/operators/integer_division.rs new file mode 100644 index 00000000000..631d10f4a72 --- /dev/null +++ b/clippy_lints/src/operators/integer_division.rs @@ -0,0 +1,27 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::INTEGER_DIVISION; + +pub(crate) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + op: hir::BinOpKind, + left: &'tcx hir::Expr<'_>, + right: &'tcx hir::Expr<'_>, +) { + if op == hir::BinOpKind::Div + && cx.typeck_results().expr_ty(left).is_integral() + && cx.typeck_results().expr_ty(right).is_integral() + { + span_lint_and_help( + cx, + INTEGER_DIVISION, + expr.span, + "integer division", + None, + "division of integers may cause loss of precision. consider using floats", + ); + } +} diff --git a/clippy_lints/src/operators/misrefactored_assign_op.rs b/clippy_lints/src/operators/misrefactored_assign_op.rs new file mode 100644 index 00000000000..0024384d927 --- /dev/null +++ b/clippy_lints/src/operators/misrefactored_assign_op.rs @@ -0,0 +1,84 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::eq_expr_value; +use clippy_utils::source::snippet_opt; +use clippy_utils::sugg; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::MISREFACTORED_ASSIGN_OP; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + op: hir::BinOpKind, + lhs: &'tcx hir::Expr<'_>, + rhs: &'tcx hir::Expr<'_>, +) { + if let hir::ExprKind::Binary(binop, l, r) = &rhs.kind { + if op != binop.node { + return; + } + // lhs op= l op r + if eq_expr_value(cx, lhs, l) { + lint_misrefactored_assign_op(cx, expr, op, rhs, lhs, r); + } + // lhs op= l commutative_op r + if is_commutative(op) && eq_expr_value(cx, lhs, r) { + lint_misrefactored_assign_op(cx, expr, op, rhs, lhs, l); + } + } +} + +fn lint_misrefactored_assign_op( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + op: hir::BinOpKind, + rhs: &hir::Expr<'_>, + assignee: &hir::Expr<'_>, + rhs_other: &hir::Expr<'_>, +) { + span_lint_and_then( + cx, + MISREFACTORED_ASSIGN_OP, + expr.span, + "variable appears on both sides of an assignment operation", + |diag| { + if let (Some(snip_a), Some(snip_r)) = (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs_other.span)) { + let a = &sugg::Sugg::hir(cx, assignee, ".."); + let r = &sugg::Sugg::hir(cx, rhs, ".."); + let long = format!("{} = {}", snip_a, sugg::make_binop(op.into(), a, r)); + diag.span_suggestion( + expr.span, + &format!( + "did you mean `{} = {} {} {}` or `{}`? Consider replacing it with", + snip_a, + snip_a, + op.as_str(), + snip_r, + long + ), + format!("{} {}= {}", snip_a, op.as_str(), snip_r), + Applicability::MaybeIncorrect, + ); + diag.span_suggestion( + expr.span, + "or", + long, + Applicability::MaybeIncorrect, // snippet + ); + } + }, + ); +} + +#[must_use] +fn is_commutative(op: hir::BinOpKind) -> bool { + use rustc_hir::BinOpKind::{ + Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub, + }; + match op { + Add | Mul | And | Or | BitXor | BitAnd | BitOr | Eq | Ne => true, + Sub | Div | Rem | Shl | Shr | Lt | Le | Ge | Gt => false, + } +} diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs new file mode 100644 index 00000000000..35fe405bcf1 --- /dev/null +++ b/clippy_lints/src/operators/mod.rs @@ -0,0 +1,849 @@ +use rustc_hir::{Body, Expr, ExprKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +mod absurd_extreme_comparisons; +mod assign_op_pattern; +mod bit_mask; +mod cmp_nan; +mod cmp_owned; +mod double_comparison; +mod duration_subsec; +mod eq_op; +mod erasing_op; +mod float_cmp; +mod float_equality_without_abs; +mod identity_op; +mod integer_division; +mod misrefactored_assign_op; +mod modulo_arithmetic; +mod modulo_one; +mod needless_bitwise_bool; +mod numeric_arithmetic; +mod op_ref; +mod ptr_eq; +mod self_assignment; +mod verbose_bit_mask; + +declare_clippy_lint! { + /// ### What it does + /// Checks for comparisons where one side of the relation is + /// either the minimum or maximum value for its type and warns if it involves a + /// case that is always true or always false. Only integer and boolean types are + /// checked. + /// + /// ### Why is this bad? + /// An expression like `min <= x` may misleadingly imply + /// that it is possible for `x` to be less than the minimum. Expressions like + /// `max < x` are probably mistakes. + /// + /// ### Known problems + /// For `usize` the size of the current compile target will + /// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such + /// a comparison to detect target pointer width will trigger this lint. One can + /// use `mem::sizeof` and compare its value or conditional compilation + /// attributes + /// like `#[cfg(target_pointer_width = "64")] ..` instead. + /// + /// ### Example + /// ```rust + /// let vec: Vec = Vec::new(); + /// if vec.len() <= 0 {} + /// if 100 > i32::MAX {} + /// ``` + #[clippy::version = "pre 1.29.0"] + pub ABSURD_EXTREME_COMPARISONS, + correctness, + "a comparison with a maximum or minimum value that is always true or false" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for integer arithmetic operations which could overflow or panic. + /// + /// Specifically, checks for any operators (`+`, `-`, `*`, `<<`, etc) which are capable + /// of overflowing according to the [Rust + /// Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow), + /// or which can panic (`/`, `%`). No bounds analysis or sophisticated reasoning is + /// attempted. + /// + /// ### 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; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub INTEGER_ARITHMETIC, + restriction, + "any integer arithmetic expression which could overflow or panic" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for float arithmetic. + /// + /// ### Why is this bad? + /// For some embedded systems or kernel development, it + /// can be useful to rule out floating-point numbers. + /// + /// ### Example + /// ```rust + /// # let a = 0.0; + /// a + 1.0; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub FLOAT_ARITHMETIC, + restriction, + "any floating-point arithmetic statement" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `a = a op b` or `a = b commutative_op a` + /// patterns. + /// + /// ### Why is this bad? + /// These can be written as the shorter `a op= b`. + /// + /// ### Known problems + /// While forbidden by the spec, `OpAssign` traits may have + /// implementations that differ from the regular `Op` impl. + /// + /// ### Example + /// ```rust + /// let mut a = 5; + /// let b = 0; + /// // ... + /// + /// a = a + b; + /// ``` + /// + /// Use instead: + /// ```rust + /// let mut a = 5; + /// let b = 0; + /// // ... + /// + /// a += b; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub ASSIGN_OP_PATTERN, + style, + "assigning the result of an operation on a variable to that same variable" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `a op= a op b` or `a op= b op a` patterns. + /// + /// ### Why is this bad? + /// Most likely these are bugs where one meant to write `a + /// op= b`. + /// + /// ### Known problems + /// Clippy cannot know for sure if `a op= a op b` should have + /// been `a = a op a op b` or `a = a op b`/`a op= b`. Therefore, it suggests both. + /// If `a op= a op b` is really the correct behavior it should be + /// written as `a = a op a op b` as it's less confusing. + /// + /// ### Example + /// ```rust + /// let mut a = 5; + /// let b = 2; + /// // ... + /// a += a + b; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MISREFACTORED_ASSIGN_OP, + suspicious, + "having a variable on both sides of an assign op" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for incompatible bit masks in comparisons. + /// + /// The formula for detecting if an expression of the type `_ m + /// c` (where `` is one of {`&`, `|`} and `` is one of + /// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following + /// table: + /// + /// |Comparison |Bit Op|Example |is always|Formula | + /// |------------|------|-------------|---------|----------------------| + /// |`==` or `!=`| `&` |`x & 2 == 3` |`false` |`c & m != c` | + /// |`<` or `>=`| `&` |`x & 2 < 3` |`true` |`m < c` | + /// |`>` or `<=`| `&` |`x & 1 > 1` |`false` |`m <= c` | + /// |`==` or `!=`| `\|` |`x \| 1 == 0`|`false` |`c \| m != c` | + /// |`<` or `>=`| `\|` |`x \| 1 < 1` |`false` |`m >= c` | + /// |`<=` or `>` | `\|` |`x \| 1 > 0` |`true` |`m > c` | + /// + /// ### Why is this bad? + /// If the bits that the comparison cares about are always + /// set to zero or one by the bit mask, the comparison is constant `true` or + /// `false` (depending on mask, compared value, and operators). + /// + /// So the code is actively misleading, and the only reason someone would write + /// this intentionally is to win an underhanded Rust contest or create a + /// test-case for this lint. + /// + /// ### Example + /// ```rust + /// # let x = 1; + /// if (x & 1 == 2) { } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub BAD_BIT_MASK, + correctness, + "expressions of the form `_ & mask == select` that will only ever return `true` or `false`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for bit masks in comparisons which can be removed + /// without changing the outcome. The basic structure can be seen in the + /// following table: + /// + /// |Comparison| Bit Op |Example |equals | + /// |----------|----------|------------|-------| + /// |`>` / `<=`|`\|` / `^`|`x \| 2 > 3`|`x > 3`| + /// |`<` / `>=`|`\|` / `^`|`x ^ 1 < 4` |`x < 4`| + /// + /// ### Why is this bad? + /// Not equally evil as [`bad_bit_mask`](#bad_bit_mask), + /// but still a bit misleading, because the bit mask is ineffective. + /// + /// ### Known problems + /// False negatives: This lint will only match instances + /// where we have figured out the math (which is for a power-of-two compared + /// value). This means things like `x | 1 >= 7` (which would be better written + /// as `x >= 6`) will not be reported (but bit masks like this are fairly + /// uncommon). + /// + /// ### Example + /// ```rust + /// # let x = 1; + /// if (x | 1 > 3) { } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub INEFFECTIVE_BIT_MASK, + correctness, + "expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for bit masks that can be replaced by a call + /// to `trailing_zeros` + /// + /// ### Why is this bad? + /// `x.trailing_zeros() > 4` is much clearer than `x & 15 + /// == 0` + /// + /// ### Known problems + /// llvm generates better code for `x & 15 == 0` on x86 + /// + /// ### Example + /// ```rust + /// # let x = 1; + /// if x & 0b1111 == 0 { } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub VERBOSE_BIT_MASK, + pedantic, + "expressions where a bit mask is less readable than the corresponding method call" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for double comparisons that could be simplified to a single expression. + /// + /// + /// ### Why is this bad? + /// Readability. + /// + /// ### Example + /// ```rust + /// # let x = 1; + /// # let y = 2; + /// if x == y || x < y {} + /// ``` + /// + /// Use instead: + /// + /// ```rust + /// # let x = 1; + /// # let y = 2; + /// if x <= y {} + /// ``` + #[clippy::version = "pre 1.29.0"] + pub DOUBLE_COMPARISONS, + complexity, + "unnecessary double comparisons that can be simplified" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for calculation of subsecond microseconds or milliseconds + /// from other `Duration` methods. + /// + /// ### Why is this bad? + /// It's more concise to call `Duration::subsec_micros()` or + /// `Duration::subsec_millis()` than to calculate them. + /// + /// ### Example + /// ```rust + /// # use std::time::Duration; + /// # let duration = Duration::new(5, 0); + /// let micros = duration.subsec_nanos() / 1_000; + /// let millis = duration.subsec_nanos() / 1_000_000; + /// ``` + /// + /// Use instead: + /// ```rust + /// # use std::time::Duration; + /// # let duration = Duration::new(5, 0); + /// let micros = duration.subsec_micros(); + /// let millis = duration.subsec_millis(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub DURATION_SUBSEC, + complexity, + "checks for calculation of subsecond microseconds or milliseconds" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for equal operands to comparison, logical and + /// bitwise, difference and division binary operators (`==`, `>`, etc., `&&`, + /// `||`, `&`, `|`, `^`, `-` and `/`). + /// + /// ### Why is this bad? + /// This is usually just a typo or a copy and paste error. + /// + /// ### Known problems + /// False negatives: We had some false positives regarding + /// calls (notably [racer](https://github.com/phildawes/racer) had one instance + /// of `x.pop() && x.pop()`), so we removed matching any function or method + /// calls. We may introduce a list of known pure functions in the future. + /// + /// ### Example + /// ```rust + /// # let x = 1; + /// if x + 1 == x + 1 {} + /// + /// // or + /// + /// # let a = 3; + /// # let b = 4; + /// assert_eq!(a, a); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub EQ_OP, + correctness, + "equal operands on both sides of a comparison or bitwise combination (e.g., `x == x`)" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for arguments to `==` which have their address + /// taken to satisfy a bound + /// and suggests to dereference the other argument instead + /// + /// ### Why is this bad? + /// It is more idiomatic to dereference the other argument. + /// + /// ### Example + /// ```rust,ignore + /// &x == y + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// x == *y + /// ``` + #[clippy::version = "pre 1.29.0"] + pub OP_REF, + style, + "taking a reference to satisfy the type constraints on `==`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for erasing operations, e.g., `x * 0`. + /// + /// ### Why is this bad? + /// The whole expression can be replaced by zero. + /// This is most likely not the intended outcome and should probably be + /// corrected + /// + /// ### Example + /// ```rust + /// let x = 1; + /// 0 / x; + /// 0 * x; + /// x & 0; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub ERASING_OP, + correctness, + "using erasing operations, e.g., `x * 0` or `y & 0`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for statements of the form `(a - b) < f32::EPSILON` or + /// `(a - b) < f64::EPSILON`. Notes the missing `.abs()`. + /// + /// ### Why is this bad? + /// The code without `.abs()` is more likely to have a bug. + /// + /// ### Known problems + /// If the user can ensure that b is larger than a, the `.abs()` is + /// technically unnecessary. However, it will make the code more robust and doesn't have any + /// large performance implications. If the abs call was deliberately left out for performance + /// reasons, it is probably better to state this explicitly in the code, which then can be done + /// with an allow. + /// + /// ### Example + /// ```rust + /// pub fn is_roughly_equal(a: f32, b: f32) -> bool { + /// (a - b) < f32::EPSILON + /// } + /// ``` + /// Use instead: + /// ```rust + /// pub fn is_roughly_equal(a: f32, b: f32) -> bool { + /// (a - b).abs() < f32::EPSILON + /// } + /// ``` + #[clippy::version = "1.48.0"] + pub FLOAT_EQUALITY_WITHOUT_ABS, + suspicious, + "float equality check without `.abs()`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for identity operations, e.g., `x + 0`. + /// + /// ### Why is this bad? + /// This code can be removed without changing the + /// meaning. So it just obscures what's going on. Delete it mercilessly. + /// + /// ### Example + /// ```rust + /// # let x = 1; + /// x / 1 + 0 * 1 - 0 | 0; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub IDENTITY_OP, + complexity, + "using identity operations, e.g., `x + 0` or `y / 1`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for division of integers + /// + /// ### Why is this bad? + /// When outside of some very specific algorithms, + /// integer division is very often a mistake because it discards the + /// remainder. + /// + /// ### Example + /// ```rust + /// let x = 3 / 2; + /// println!("{}", x); + /// ``` + /// + /// Use instead: + /// ```rust + /// let x = 3f32 / 2f32; + /// println!("{}", x); + /// ``` + #[clippy::version = "1.37.0"] + pub INTEGER_DIVISION, + restriction, + "integer division may cause loss of precision" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for comparisons to NaN. + /// + /// ### Why is this bad? + /// NaN does not compare meaningfully to anything – not + /// even itself – so those comparisons are simply wrong. + /// + /// ### Example + /// ```rust + /// # let x = 1.0; + /// if x == f32::NAN { } + /// ``` + /// + /// Use instead: + /// ```rust + /// # let x = 1.0f32; + /// if x.is_nan() { } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CMP_NAN, + correctness, + "comparisons to `NAN`, which will always return false, probably not intended" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for conversions to owned values just for the sake + /// of a comparison. + /// + /// ### Why is this bad? + /// The comparison can operate on a reference, so creating + /// an owned value effectively throws it away directly afterwards, which is + /// needlessly consuming code and heap space. + /// + /// ### Example + /// ```rust + /// # let x = "foo"; + /// # let y = String::from("foo"); + /// if x.to_owned() == y {} + /// ``` + /// + /// Use instead: + /// ```rust + /// # let x = "foo"; + /// # let y = String::from("foo"); + /// if x == y {} + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CMP_OWNED, + perf, + "creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for (in-)equality comparisons on floating-point + /// values (apart from zero), except in functions called `*eq*` (which probably + /// implement equality for a type involving floats). + /// + /// ### Why is this bad? + /// Floating point calculations are usually imprecise, so + /// asking if two values are *exactly* equal is asking for trouble. For a good + /// guide on what to do, see [the floating point + /// guide](http://www.floating-point-gui.de/errors/comparison). + /// + /// ### Example + /// ```rust + /// let x = 1.2331f64; + /// let y = 1.2332f64; + /// + /// if y == 1.23f64 { } + /// if y != x {} // where both are floats + /// ``` + /// + /// Use instead: + /// ```rust + /// # let x = 1.2331f64; + /// # let y = 1.2332f64; + /// let error_margin = f64::EPSILON; // Use an epsilon for comparison + /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. + /// // let error_margin = std::f64::EPSILON; + /// if (y - 1.23f64).abs() < error_margin { } + /// if (y - x).abs() > error_margin { } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub FLOAT_CMP, + pedantic, + "using `==` or `!=` on float values instead of comparing difference with an epsilon" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for (in-)equality comparisons on floating-point + /// value and constant, except in functions called `*eq*` (which probably + /// implement equality for a type involving floats). + /// + /// ### Why is this bad? + /// Floating point calculations are usually imprecise, so + /// asking if two values are *exactly* equal is asking for trouble. For a good + /// guide on what to do, see [the floating point + /// guide](http://www.floating-point-gui.de/errors/comparison). + /// + /// ### Example + /// ```rust + /// let x: f64 = 1.0; + /// const ONE: f64 = 1.00; + /// + /// if x == ONE { } // where both are floats + /// ``` + /// + /// Use instead: + /// ```rust + /// # let x: f64 = 1.0; + /// # const ONE: f64 = 1.00; + /// let error_margin = f64::EPSILON; // Use an epsilon for comparison + /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. + /// // let error_margin = std::f64::EPSILON; + /// if (x - ONE).abs() < error_margin { } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub FLOAT_CMP_CONST, + restriction, + "using `==` or `!=` on float constants instead of comparing difference with an epsilon" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for getting the remainder of a division by one or minus + /// one. + /// + /// ### Why is this bad? + /// The result for a divisor of one can only ever be zero; for + /// minus one it can cause panic/overflow (if the left operand is the minimal value of + /// the respective integer type) or results in zero. No one will write such code + /// deliberately, unless trying to win an Underhanded Rust Contest. Even for that + /// contest, it's probably a bad idea. Use something more underhanded. + /// + /// ### Example + /// ```rust + /// # let x = 1; + /// let a = x % 1; + /// let a = x % -1; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MODULO_ONE, + correctness, + "taking a number modulo +/-1, which can either panic/overflow or always returns 0" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for modulo arithmetic. + /// + /// ### Why is this bad? + /// The results of modulo (%) operation might differ + /// depending on the language, when negative numbers are involved. + /// If you interop with different languages it might be beneficial + /// to double check all places that use modulo arithmetic. + /// + /// For example, in Rust `17 % -3 = 2`, but in Python `17 % -3 = -1`. + /// + /// ### Example + /// ```rust + /// let x = -17 % 3; + /// ``` + #[clippy::version = "1.42.0"] + pub MODULO_ARITHMETIC, + restriction, + "any modulo arithmetic statement" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for uses of bitwise and/or operators between booleans, where performance may be improved by using + /// a lazy and. + /// + /// ### Why is this bad? + /// The bitwise operators do not support short-circuiting, so it may hinder code performance. + /// Additionally, boolean logic "masked" as bitwise logic is not caught by lints like `unnecessary_fold` + /// + /// ### Known problems + /// This lint evaluates only when the right side is determined to have no side effects. At this time, that + /// determination is quite conservative. + /// + /// ### Example + /// ```rust + /// let (x,y) = (true, false); + /// if x & !y {} // where both x and y are booleans + /// ``` + /// Use instead: + /// ```rust + /// let (x,y) = (true, false); + /// if x && !y {} + /// ``` + #[clippy::version = "1.54.0"] + pub NEEDLESS_BITWISE_BOOL, + pedantic, + "Boolean expressions that use bitwise rather than lazy operators" +} + +declare_clippy_lint! { + /// ### What it does + /// Use `std::ptr::eq` when applicable + /// + /// ### Why is this bad? + /// `ptr::eq` can be used to compare `&T` references + /// (which coerce to `*const T` implicitly) by their address rather than + /// comparing the values they point to. + /// + /// ### Example + /// ```rust + /// let a = &[1, 2, 3]; + /// let b = &[1, 2, 3]; + /// + /// assert!(a as *const _ as usize == b as *const _ as usize); + /// ``` + /// Use instead: + /// ```rust + /// let a = &[1, 2, 3]; + /// let b = &[1, 2, 3]; + /// + /// assert!(std::ptr::eq(a, b)); + /// ``` + #[clippy::version = "1.49.0"] + pub PTR_EQ, + style, + "use `std::ptr::eq` when comparing raw pointers" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for explicit self-assignments. + /// + /// ### Why is this bad? + /// Self-assignments are redundant and unlikely to be + /// intentional. + /// + /// ### Known problems + /// If expression contains any deref coercions or + /// indexing operations they are assumed not to have any side effects. + /// + /// ### Example + /// ```rust + /// struct Event { + /// x: i32, + /// } + /// + /// fn copy_position(a: &mut Event, b: &Event) { + /// a.x = a.x; + /// } + /// ``` + /// + /// Should be: + /// ```rust + /// struct Event { + /// x: i32, + /// } + /// + /// fn copy_position(a: &mut Event, b: &Event) { + /// a.x = b.x; + /// } + /// ``` + #[clippy::version = "1.48.0"] + pub SELF_ASSIGNMENT, + correctness, + "explicit self-assignment" +} + +pub struct Operators { + arithmetic_context: numeric_arithmetic::Context, + verbose_bit_mask_threshold: u64, +} +impl_lint_pass!(Operators => [ + ABSURD_EXTREME_COMPARISONS, + INTEGER_ARITHMETIC, + FLOAT_ARITHMETIC, + ASSIGN_OP_PATTERN, + MISREFACTORED_ASSIGN_OP, + BAD_BIT_MASK, + INEFFECTIVE_BIT_MASK, + VERBOSE_BIT_MASK, + DOUBLE_COMPARISONS, + DURATION_SUBSEC, + EQ_OP, + OP_REF, + ERASING_OP, + FLOAT_EQUALITY_WITHOUT_ABS, + IDENTITY_OP, + INTEGER_DIVISION, + CMP_NAN, + CMP_OWNED, + FLOAT_CMP, + FLOAT_CMP_CONST, + MODULO_ONE, + MODULO_ARITHMETIC, + NEEDLESS_BITWISE_BOOL, + PTR_EQ, + SELF_ASSIGNMENT, +]); +impl Operators { + pub fn new(verbose_bit_mask_threshold: u64) -> Self { + Self { + arithmetic_context: numeric_arithmetic::Context::default(), + verbose_bit_mask_threshold, + } + } +} +impl<'tcx> LateLintPass<'tcx> for Operators { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + eq_op::check_assert(cx, e); + match e.kind { + ExprKind::Binary(op, lhs, rhs) => { + if !e.span.from_expansion() { + absurd_extreme_comparisons::check(cx, e, op.node, lhs, rhs); + if !(macro_with_not_op(lhs) || macro_with_not_op(rhs)) { + eq_op::check(cx, e, op.node, lhs, rhs); + op_ref::check(cx, e, op.node, lhs, rhs); + } + erasing_op::check(cx, e, op.node, lhs, rhs); + identity_op::check(cx, e, op.node, lhs, rhs); + needless_bitwise_bool::check(cx, e, op.node, lhs, rhs); + ptr_eq::check(cx, e, op.node, lhs, rhs); + } + self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs); + bit_mask::check(cx, e, op.node, lhs, rhs); + verbose_bit_mask::check(cx, e, op.node, lhs, rhs, self.verbose_bit_mask_threshold); + double_comparison::check(cx, op.node, lhs, rhs, e.span); + duration_subsec::check(cx, e, op.node, lhs, rhs); + float_equality_without_abs::check(cx, e, op.node, lhs, rhs); + integer_division::check(cx, e, op.node, lhs, rhs); + cmp_nan::check(cx, e, op.node, lhs, rhs); + cmp_owned::check(cx, op.node, lhs, rhs); + float_cmp::check(cx, e, op.node, lhs, rhs); + modulo_one::check(cx, e, op.node, rhs); + modulo_arithmetic::check(cx, e, op.node, lhs, rhs); + }, + ExprKind::AssignOp(op, lhs, rhs) => { + self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs); + misrefactored_assign_op::check(cx, e, op.node, lhs, rhs); + modulo_arithmetic::check(cx, e, op.node, lhs, rhs); + }, + ExprKind::Assign(lhs, rhs, _) => { + assign_op_pattern::check(cx, e, lhs, rhs); + self_assignment::check(cx, e, lhs, rhs); + }, + ExprKind::Unary(op, arg) => { + if op == UnOp::Neg { + self.arithmetic_context.check_negate(cx, e, arg); + } + }, + _ => (), + } + } + + fn check_expr_post(&mut self, _: &LateContext<'_>, e: &Expr<'_>) { + self.arithmetic_context.expr_post(e.hir_id); + } + + fn check_body(&mut self, cx: &LateContext<'tcx>, b: &'tcx Body<'_>) { + self.arithmetic_context.enter_body(cx, b); + } + + fn check_body_post(&mut self, cx: &LateContext<'tcx>, b: &'tcx Body<'_>) { + self.arithmetic_context.body_post(cx, b); + } +} + +fn macro_with_not_op(e: &Expr<'_>) -> bool { + if let ExprKind::Unary(_, e) = e.kind { + e.span.from_expansion() + } else { + false + } +} diff --git a/clippy_lints/src/modulo_arithmetic.rs b/clippy_lints/src/operators/modulo_arithmetic.rs similarity index 64% rename from clippy_lints/src/modulo_arithmetic.rs rename to clippy_lints/src/operators/modulo_arithmetic.rs index 195b2e5c2ee..af4e74947f4 100644 --- a/clippy_lints/src/modulo_arithmetic.rs +++ b/clippy_lints/src/operators/modulo_arithmetic.rs @@ -2,35 +2,35 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::sext; use if_chain::if_chain; -use rustc_hir::{BinOpKind, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_hir::{BinOpKind, Expr}; +use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; use std::fmt::Display; -declare_clippy_lint! { - /// ### What it does - /// Checks for modulo arithmetic. - /// - /// ### Why is this bad? - /// The results of modulo (%) operation might differ - /// depending on the language, when negative numbers are involved. - /// If you interop with different languages it might be beneficial - /// to double check all places that use modulo arithmetic. - /// - /// For example, in Rust `17 % -3 = 2`, but in Python `17 % -3 = -1`. - /// - /// ### Example - /// ```rust - /// let x = -17 % 3; - /// ``` - #[clippy::version = "1.42.0"] - pub MODULO_ARITHMETIC, - restriction, - "any modulo arithmetic statement" -} +use super::MODULO_ARITHMETIC; -declare_lint_pass!(ModuloArithmetic => [MODULO_ARITHMETIC]); +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + op: BinOpKind, + lhs: &'tcx Expr<'_>, + rhs: &'tcx Expr<'_>, +) { + if op == BinOpKind::Rem { + let lhs_operand = analyze_operand(lhs, cx, e); + let rhs_operand = analyze_operand(rhs, cx, e); + if_chain! { + if let Some(lhs_operand) = lhs_operand; + if let Some(rhs_operand) = rhs_operand; + then { + check_const_operands(cx, e, &lhs_operand, &rhs_operand); + } + else { + check_non_const_operands(cx, e, lhs); + } + } + }; +} struct OperandInfo { string_representation: Option, @@ -124,27 +124,3 @@ fn check_non_const_operands<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ); } } - -impl<'tcx> LateLintPass<'tcx> for ModuloArithmetic { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - match &expr.kind { - ExprKind::Binary(op, lhs, rhs) | ExprKind::AssignOp(op, lhs, rhs) => { - if op.node == BinOpKind::Rem { - let lhs_operand = analyze_operand(lhs, cx, expr); - let rhs_operand = analyze_operand(rhs, cx, expr); - if_chain! { - if let Some(lhs_operand) = lhs_operand; - if let Some(rhs_operand) = rhs_operand; - then { - check_const_operands(cx, expr, &lhs_operand, &rhs_operand); - } - else { - check_non_const_operands(cx, expr, lhs); - } - } - }; - }, - _ => {}, - } - } -} diff --git a/clippy_lints/src/operators/modulo_one.rs b/clippy_lints/src/operators/modulo_one.rs new file mode 100644 index 00000000000..54eea14833f --- /dev/null +++ b/clippy_lints/src/operators/modulo_one.rs @@ -0,0 +1,26 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::{is_integer_const, unsext}; +use rustc_hir::{BinOpKind, Expr}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::MODULO_ONE; + +pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, right: &Expr<'_>) { + if op == BinOpKind::Rem { + if is_integer_const(cx, right, 1) { + span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0"); + } + + if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() { + if is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) { + span_lint( + cx, + MODULO_ONE, + expr.span, + "any number modulo -1 will panic/overflow or result in 0", + ); + } + }; + } +} diff --git a/clippy_lints/src/operators/needless_bitwise_bool.rs b/clippy_lints/src/operators/needless_bitwise_bool.rs new file mode 100644 index 00000000000..e902235a014 --- /dev/null +++ b/clippy_lints/src/operators/needless_bitwise_bool.rs @@ -0,0 +1,36 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_opt; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; + +use super::NEEDLESS_BITWISE_BOOL; + +pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>) { + let op_str = match op { + BinOpKind::BitAnd => "&&", + BinOpKind::BitOr => "||", + _ => return, + }; + if matches!( + rhs.kind, + ExprKind::Call(..) | ExprKind::MethodCall(..) | ExprKind::Binary(..) | ExprKind::Unary(..) + ) && cx.typeck_results().expr_ty(e).is_bool() + && !rhs.can_have_side_effects() + { + span_lint_and_then( + cx, + NEEDLESS_BITWISE_BOOL, + e.span, + "use of bitwise operator instead of lazy operator between booleans", + |diag| { + if let Some(lhs_snip) = snippet_opt(cx, lhs.span) + && let Some(rhs_snip) = snippet_opt(cx, rhs.span) + { + let sugg = format!("{} {} {}", lhs_snip, op_str, rhs_snip); + diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable); + } + }, + ); + } +} diff --git a/clippy_lints/src/operators/numeric_arithmetic.rs b/clippy_lints/src/operators/numeric_arithmetic.rs new file mode 100644 index 00000000000..82f454d02f7 --- /dev/null +++ b/clippy_lints/src/operators/numeric_arithmetic.rs @@ -0,0 +1,127 @@ +use clippy_utils::consts::constant_simple; +use clippy_utils::diagnostics::span_lint; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +use super::{FLOAT_ARITHMETIC, INTEGER_ARITHMETIC}; + +#[derive(Default)] +pub struct Context { + expr_id: Option, + /// This field is used to check whether expressions are constants, such as in enum discriminants + /// and consts + const_span: Option, +} +impl Context { + fn skip_expr(&mut self, e: &hir::Expr<'_>) -> bool { + self.expr_id.is_some() || self.const_span.map_or(false, |span| span.contains(e.span)) + } + + pub fn check_binary<'tcx>( + &mut self, + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + op: hir::BinOpKind, + l: &'tcx hir::Expr<'_>, + r: &'tcx hir::Expr<'_>, + ) { + if self.skip_expr(expr) { + return; + } + match op { + hir::BinOpKind::And + | hir::BinOpKind::Or + | hir::BinOpKind::BitAnd + | hir::BinOpKind::BitOr + | hir::BinOpKind::BitXor + | hir::BinOpKind::Eq + | hir::BinOpKind::Lt + | hir::BinOpKind::Le + | hir::BinOpKind::Ne + | hir::BinOpKind::Ge + | hir::BinOpKind::Gt => return, + _ => (), + } + + let (l_ty, r_ty) = (cx.typeck_results().expr_ty(l), cx.typeck_results().expr_ty(r)); + if l_ty.peel_refs().is_integral() && r_ty.peel_refs().is_integral() { + match op { + hir::BinOpKind::Div | hir::BinOpKind::Rem => match &r.kind { + hir::ExprKind::Lit(_lit) => (), + hir::ExprKind::Unary(hir::UnOp::Neg, expr) => { + if let hir::ExprKind::Lit(lit) = &expr.kind { + if let rustc_ast::ast::LitKind::Int(1, _) = lit.node { + span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected"); + self.expr_id = Some(expr.hir_id); + } + } + }, + _ => { + span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected"); + self.expr_id = Some(expr.hir_id); + }, + }, + _ => { + span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected"); + self.expr_id = Some(expr.hir_id); + }, + } + } else if r_ty.peel_refs().is_floating_point() && r_ty.peel_refs().is_floating_point() { + span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected"); + self.expr_id = Some(expr.hir_id); + } + } + + pub fn check_negate<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) { + if self.skip_expr(expr) { + return; + } + let ty = cx.typeck_results().expr_ty(arg); + if constant_simple(cx, cx.typeck_results(), expr).is_none() { + if ty.is_integral() { + span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected"); + self.expr_id = Some(expr.hir_id); + } else if ty.is_floating_point() { + span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected"); + self.expr_id = Some(expr.hir_id); + } + } + } + + pub fn expr_post(&mut self, id: hir::HirId) { + if Some(id) == self.expr_id { + self.expr_id = None; + } + } + + pub fn enter_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::Static(_) | hir::BodyOwnerKind::Const => { + let body_span = cx.tcx.def_span(body_owner); + + if let Some(span) = self.const_span { + if span.contains(body_span) { + return; + } + } + self.const_span = Some(body_span); + }, + hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => (), + } + } + + pub fn 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 { + if span.contains(body_span) { + return; + } + } + self.const_span = None; + } +} diff --git a/clippy_lints/src/operators/op_ref.rs b/clippy_lints/src/operators/op_ref.rs new file mode 100644 index 00000000000..1805672e372 --- /dev/null +++ b/clippy_lints/src/operators/op_ref.rs @@ -0,0 +1,218 @@ +use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; +use clippy_utils::get_enclosing_block; +use clippy_utils::source::snippet; +use clippy_utils::ty::{implements_trait, is_copy}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{def::Res, def_id::DefId, BinOpKind, BorrowKind, Expr, ExprKind, GenericArg, ItemKind, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +use super::OP_REF; + +#[expect(clippy::similar_names, clippy::too_many_lines)] +pub(crate) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, +) { + let (trait_id, requires_ref) = match op { + BinOpKind::Add => (cx.tcx.lang_items().add_trait(), false), + BinOpKind::Sub => (cx.tcx.lang_items().sub_trait(), false), + BinOpKind::Mul => (cx.tcx.lang_items().mul_trait(), false), + BinOpKind::Div => (cx.tcx.lang_items().div_trait(), false), + BinOpKind::Rem => (cx.tcx.lang_items().rem_trait(), false), + // don't lint short circuiting ops + BinOpKind::And | BinOpKind::Or => return, + BinOpKind::BitXor => (cx.tcx.lang_items().bitxor_trait(), false), + BinOpKind::BitAnd => (cx.tcx.lang_items().bitand_trait(), false), + BinOpKind::BitOr => (cx.tcx.lang_items().bitor_trait(), false), + BinOpKind::Shl => (cx.tcx.lang_items().shl_trait(), false), + BinOpKind::Shr => (cx.tcx.lang_items().shr_trait(), false), + BinOpKind::Ne | BinOpKind::Eq => (cx.tcx.lang_items().eq_trait(), true), + BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ge | BinOpKind::Gt => { + (cx.tcx.lang_items().partial_ord_trait(), true) + }, + }; + if let Some(trait_id) = trait_id { + match (&left.kind, &right.kind) { + // do not suggest to dereference literals + (&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {}, + // &foo == &bar + (&ExprKind::AddrOf(BorrowKind::Ref, _, l), &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => { + let lty = cx.typeck_results().expr_ty(l); + let rty = cx.typeck_results().expr_ty(r); + let lcpy = is_copy(cx, lty); + let rcpy = is_copy(cx, rty); + if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) { + if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) + || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty)) + { + return; // Don't lint + } + } + // either operator autorefs or both args are copyable + if (requires_ref || (lcpy && rcpy)) && implements_trait(cx, lty, trait_id, &[rty.into()]) { + span_lint_and_then( + cx, + OP_REF, + e.span, + "needlessly taken reference of both operands", + |diag| { + let lsnip = snippet(cx, l.span, "...").to_string(); + let rsnip = snippet(cx, r.span, "...").to_string(); + multispan_sugg( + diag, + "use the values directly", + vec![(left.span, lsnip), (right.span, rsnip)], + ); + }, + ); + } else if lcpy + && !rcpy + && implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()]) + { + span_lint_and_then( + cx, + OP_REF, + e.span, + "needlessly taken reference of left operand", + |diag| { + let lsnip = snippet(cx, l.span, "...").to_string(); + diag.span_suggestion( + left.span, + "use the left value directly", + lsnip, + Applicability::MaybeIncorrect, // FIXME #2597 + ); + }, + ); + } else if !lcpy + && rcpy + && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()]) + { + span_lint_and_then( + cx, + OP_REF, + e.span, + "needlessly taken reference of right operand", + |diag| { + let rsnip = snippet(cx, r.span, "...").to_string(); + diag.span_suggestion( + right.span, + "use the right value directly", + rsnip, + Applicability::MaybeIncorrect, // FIXME #2597 + ); + }, + ); + } + }, + // &foo == bar + (&ExprKind::AddrOf(BorrowKind::Ref, _, l), _) => { + let lty = cx.typeck_results().expr_ty(l); + if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) { + let rty = cx.typeck_results().expr_ty(right); + if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) + || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty)) + { + return; // Don't lint + } + } + let lcpy = is_copy(cx, lty); + if (requires_ref || lcpy) + && implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()]) + { + span_lint_and_then( + cx, + OP_REF, + e.span, + "needlessly taken reference of left operand", + |diag| { + let lsnip = snippet(cx, l.span, "...").to_string(); + diag.span_suggestion( + left.span, + "use the left value directly", + lsnip, + Applicability::MaybeIncorrect, // FIXME #2597 + ); + }, + ); + } + }, + // foo == &bar + (_, &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => { + let rty = cx.typeck_results().expr_ty(r); + if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) { + let lty = cx.typeck_results().expr_ty(left); + if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) + || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty)) + { + return; // Don't lint + } + } + let rcpy = is_copy(cx, rty); + if (requires_ref || rcpy) + && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()]) + { + span_lint_and_then(cx, OP_REF, e.span, "taken reference of right operand", |diag| { + let rsnip = snippet(cx, r.span, "...").to_string(); + diag.span_suggestion( + right.span, + "use the right value directly", + rsnip, + Applicability::MaybeIncorrect, // FIXME #2597 + ); + }); + } + }, + _ => {}, + } + } +} + +fn in_impl<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + bin_op: DefId, +) -> Option<(&'tcx rustc_hir::Ty<'tcx>, &'tcx rustc_hir::Ty<'tcx>)> { + if_chain! { + if let Some(block) = get_enclosing_block(cx, e.hir_id); + if let Some(impl_def_id) = cx.tcx.impl_of_method(block.hir_id.owner.to_def_id()); + let item = cx.tcx.hir().expect_item(impl_def_id.expect_local()); + if let ItemKind::Impl(item) = &item.kind; + if let Some(of_trait) = &item.of_trait; + if let Some(seg) = of_trait.path.segments.last(); + if let Some(Res::Def(_, trait_id)) = seg.res; + if trait_id == bin_op; + if let Some(generic_args) = seg.args; + if let Some(GenericArg::Type(other_ty)) = generic_args.args.last(); + + then { + Some((item.self_ty, other_ty)) + } + else { + None + } + } +} + +fn are_equal<'tcx>(cx: &LateContext<'tcx>, middle_ty: Ty<'_>, hir_ty: &rustc_hir::Ty<'_>) -> bool { + if_chain! { + if let ty::Adt(adt_def, _) = middle_ty.kind(); + if let Some(local_did) = adt_def.did().as_local(); + let item = cx.tcx.hir().expect_item(local_did); + let middle_ty_id = item.def_id.to_def_id(); + if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind; + if let Res::Def(_, hir_ty_id) = path.res; + + then { + hir_ty_id == middle_ty_id + } + else { + false + } + } +} diff --git a/clippy_lints/src/operators/ptr_eq.rs b/clippy_lints/src/operators/ptr_eq.rs new file mode 100644 index 00000000000..1aefc2741c2 --- /dev/null +++ b/clippy_lints/src/operators/ptr_eq.rs @@ -0,0 +1,65 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_opt; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; + +use super::PTR_EQ; + +static LINT_MSG: &str = "use `std::ptr::eq` when comparing raw pointers"; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, +) { + if BinOpKind::Eq == op { + let (left, right) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) { + (Some(lhs), Some(rhs)) => (lhs, rhs), + _ => (left, right), + }; + + if_chain! { + if let Some(left_var) = expr_as_cast_to_raw_pointer(cx, left); + if let Some(right_var) = expr_as_cast_to_raw_pointer(cx, right); + if let Some(left_snip) = snippet_opt(cx, left_var.span); + if let Some(right_snip) = snippet_opt(cx, right_var.span); + then { + span_lint_and_sugg( + cx, + PTR_EQ, + expr.span, + LINT_MSG, + "try", + format!("std::ptr::eq({}, {})", left_snip, right_snip), + Applicability::MachineApplicable, + ); + } + } + } +} + +// If the given expression is a cast to a usize, return the lhs of the cast +// E.g., `foo as *const _ as usize` returns `foo as *const _`. +fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + if cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize { + if let ExprKind::Cast(expr, _) = cast_expr.kind { + return Some(expr); + } + } + None +} + +// If the given expression is a cast to a `*const` pointer, return the lhs of the cast +// E.g., `foo as *const _` returns `foo`. +fn expr_as_cast_to_raw_pointer<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + if cx.typeck_results().expr_ty(cast_expr).is_unsafe_ptr() { + if let ExprKind::Cast(expr, _) = cast_expr.kind { + return Some(expr); + } + } + None +} diff --git a/clippy_lints/src/operators/self_assignment.rs b/clippy_lints/src/operators/self_assignment.rs new file mode 100644 index 00000000000..9d6bec05bf0 --- /dev/null +++ b/clippy_lints/src/operators/self_assignment.rs @@ -0,0 +1,20 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::eq_expr_value; +use clippy_utils::source::snippet; +use rustc_hir::Expr; +use rustc_lint::LateContext; + +use super::SELF_ASSIGNMENT; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>) { + if eq_expr_value(cx, lhs, rhs) { + let lhs = snippet(cx, lhs.span, ""); + let rhs = snippet(cx, rhs.span, ""); + span_lint( + cx, + SELF_ASSIGNMENT, + e.span, + &format!("self-assignment of `{}` to `{}`", rhs, lhs), + ); + } +} diff --git a/clippy_lints/src/operators/verbose_bit_mask.rs b/clippy_lints/src/operators/verbose_bit_mask.rs new file mode 100644 index 00000000000..ff85fd55429 --- /dev/null +++ b/clippy_lints/src/operators/verbose_bit_mask.rs @@ -0,0 +1,44 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg::Sugg; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; + +use super::VERBOSE_BIT_MASK; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, + threshold: u64, +) { + if BinOpKind::Eq == op + && let ExprKind::Binary(op1, left1, right1) = &left.kind + && BinOpKind::BitAnd == op1.node + && let ExprKind::Lit(lit) = &right1.kind + && let LitKind::Int(n, _) = lit.node + && let ExprKind::Lit(lit1) = &right.kind + && let LitKind::Int(0, _) = lit1.node + && n.leading_zeros() == n.count_zeros() + && n > u128::from(threshold) + { + span_lint_and_then( + cx, + VERBOSE_BIT_MASK, + e.span, + "bit mask could be simplified with a call to `trailing_zeros`", + |diag| { + let sugg = Sugg::hir(cx, left1, "...").maybe_par(); + diag.span_suggestion( + e.span, + "try", + format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()), + Applicability::MaybeIncorrect, + ); + }, + ); + } +} diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index 05ab62786f4..5fa4fd74853 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -3,17 +3,20 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; -use clippy_utils::ty::is_copy; +use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy}; use clippy_utils::{is_self, is_self_ty}; +use core::ops::ControlFlow; use if_chain::if_chain; use rustc_ast::attr; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::intravisit::FnKind; use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; +use rustc_middle::ty::adjustment::{Adjust, PointerCast}; use rustc_middle::ty::layout::LayoutOf; +use rustc_middle::ty::{self, RegionKind}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::def_id::LocalDefId; use rustc_span::{sym, Span}; @@ -141,50 +144,76 @@ fn check_poly_fn(&mut self, cx: &LateContext<'tcx>, def_id: LocalDefId, decl: &F } let fn_sig = cx.tcx.fn_sig(def_id); - let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig); - let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id)); - for (index, (input, &ty)) in iter::zip(decl.inputs, fn_sig.inputs()).enumerate() { + // Gather all the lifetimes found in the output type which may affect whether + // `TRIVIALLY_COPY_PASS_BY_REF` should be linted. + let mut output_regions = FxHashSet::default(); + for_each_top_level_late_bound_region(fn_sig.skip_binder().output(), |region| -> ControlFlow { + output_regions.insert(region); + ControlFlow::Continue(()) + }); + + for (index, (input, ty)) in iter::zip( + decl.inputs, + fn_sig.skip_binder().inputs().iter().map(|&ty| fn_sig.rebind(ty)), + ) + .enumerate() + { // All spans generated from a proc-macro invocation are the same... match span { - Some(s) if s == input.span => return, + Some(s) if s == input.span => continue, _ => (), } - match ty.kind() { - ty::Ref(input_lt, ty, Mutability::Not) => { - // Use lifetimes to determine if we're returning a reference to the - // argument. In that case we can't switch to pass-by-value as the - // argument will not live long enough. - let output_lts = match *fn_sig.output().kind() { - ty::Ref(output_lt, _, _) => vec![output_lt], - ty::Adt(_, substs) => substs.regions().collect(), - _ => vec![], - }; + match *ty.skip_binder().kind() { + ty::Ref(lt, ty, Mutability::Not) => { + match lt.kind() { + RegionKind::ReLateBound(index, region) + if index.as_u32() == 0 && output_regions.contains(®ion) => + { + continue; + }, + // Early bound regions on functions are either from the containing item, are bounded by another + // lifetime, or are used as a bound for a type or lifetime. + RegionKind::ReEarlyBound(..) => continue, + _ => (), + } - if_chain! { - if !output_lts.contains(input_lt); - if is_copy(cx, *ty); - if let Some(size) = cx.layout_of(*ty).ok().map(|l| l.size.bytes()); - if size <= self.ref_min_size; - if let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind; - then { - let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) { - "self".into() - } else { - snippet(cx, decl_ty.span, "_").into() - }; - span_lint_and_sugg( - cx, - TRIVIALLY_COPY_PASS_BY_REF, - input.span, - &format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size), - "consider passing by value instead", - value_type, - Applicability::Unspecified, - ); + let ty = cx.tcx.erase_late_bound_regions(fn_sig.rebind(ty)); + if is_copy(cx, ty) + && let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes()) + && size <= self.ref_min_size + && let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind + { + if let Some(typeck) = cx.maybe_typeck_results() { + // Don't lint if an unsafe pointer is created. + // TODO: Limit the check only to unsafe pointers to the argument (or part of the argument) + // which escape the current function. + if typeck.node_types().iter().any(|(_, &ty)| ty.is_unsafe_ptr()) + || typeck + .adjustments() + .iter() + .flat_map(|(_, a)| a) + .any(|a| matches!(a.kind, Adjust::Pointer(PointerCast::UnsafeFnPointer))) + { + continue; + } } + let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) { + "self".into() + } else { + snippet(cx, decl_ty.span, "_").into() + }; + span_lint_and_sugg( + cx, + TRIVIALLY_COPY_PASS_BY_REF, + input.span, + &format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size), + "consider passing by value instead", + value_type, + Applicability::Unspecified, + ); } }, @@ -196,6 +225,7 @@ fn check_poly_fn(&mut self, cx: &LateContext<'tcx>, def_id: LocalDefId, decl: &F _ => continue, } } + let ty = cx.tcx.erase_late_bound_regions(ty); if_chain! { if is_copy(cx, ty); diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index b06eba13d2f..25b73918c0a 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -1,6 +1,6 @@ //! Checks for usage of `&Vec[_]` and `&String`. -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::source::snippet_opt; use clippy_utils::ty::expr_sig; use clippy_utils::visitors::contains_unsafe_block; @@ -166,15 +166,14 @@ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_> ) .filter(|arg| arg.mutability() == Mutability::Not) { - span_lint_and_sugg( - cx, - PTR_ARG, - arg.span, - &arg.build_msg(), - "change this to", - format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)), - Applicability::Unspecified, - ); + span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, &arg.build_msg(), |diag| { + diag.span_suggestion( + arg.span, + "change this to", + format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)), + Applicability::Unspecified, + ); + }); } } } @@ -221,7 +220,7 @@ fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { let results = check_ptr_arg_usage(cx, body, &lint_args); for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) { - span_lint_and_then(cx, PTR_ARG, args.span, &args.build_msg(), |diag| { + span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, &args.build_msg(), |diag| { diag.multipart_suggestion( "change this to", iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx)))) @@ -315,6 +314,7 @@ struct PtrArgReplacement { struct PtrArg<'tcx> { idx: usize, + emission_id: hir::HirId, span: Span, ty_did: DefId, ty_name: Symbol, @@ -419,10 +419,8 @@ fn check_fn_args<'cx, 'tcx: 'cx>( if let [.., name] = path.segments; if cx.tcx.item_name(adt.did()) == name.ident.name; - if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id); - if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id)); - then { + let emission_id = params.get(i).map_or(hir_ty.hir_id, |param| param.hir_id); let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) { Some(sym::Vec) => ( [("clone", ".to_owned()")].as_slice(), @@ -455,14 +453,20 @@ fn check_fn_args<'cx, 'tcx: 'cx>( }) .and_then(|arg| snippet_opt(cx, arg.span)) .unwrap_or_else(|| substs.type_at(1).to_string()); - span_lint_and_sugg( + span_lint_hir_and_then( cx, PTR_ARG, + emission_id, hir_ty.span, "using a reference to `Cow` is not recommended", - "change this to", - format!("&{}{}", mutability.prefix_str(), ty_name), - Applicability::Unspecified, + |diag| { + diag.span_suggestion( + hir_ty.span, + "change this to", + format!("&{}{}", mutability.prefix_str(), ty_name), + Applicability::Unspecified, + ); + } ); return None; }, @@ -470,6 +474,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>( }; return Some(PtrArg { idx: i, + emission_id, span: hir_ty.span, ty_did: adt.did(), ty_name: name.ident.name, @@ -574,14 +579,13 @@ fn visit_expr(&mut self, e: &'tcx Expr<'_>) { Some((Node::Expr(e), child_id)) => match e.kind { ExprKind::Call(f, expr_args) => { let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0); - if expr_sig(self.cx, f) - .map(|sig| sig.input(i).skip_binder().peel_refs()) - .map_or(true, |ty| match *ty.kind() { + if expr_sig(self.cx, f).and_then(|sig| sig.input(i)).map_or(true, |ty| { + match *ty.skip_binder().peel_refs().kind() { ty::Param(_) => true, ty::Adt(def, _) => def.did() == args.ty_did, _ => false, - }) - { + } + }) { // Passed to a function taking the non-dereferenced type. set_skip_flag(); } diff --git a/clippy_lints/src/ptr_eq.rs b/clippy_lints/src/ptr_eq.rs deleted file mode 100644 index 2bec93ac606..00000000000 --- a/clippy_lints/src/ptr_eq.rs +++ /dev/null @@ -1,97 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_opt; -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// ### What it does - /// Use `std::ptr::eq` when applicable - /// - /// ### Why is this bad? - /// `ptr::eq` can be used to compare `&T` references - /// (which coerce to `*const T` implicitly) by their address rather than - /// comparing the values they point to. - /// - /// ### Example - /// ```rust - /// let a = &[1, 2, 3]; - /// let b = &[1, 2, 3]; - /// - /// assert!(a as *const _ as usize == b as *const _ as usize); - /// ``` - /// Use instead: - /// ```rust - /// let a = &[1, 2, 3]; - /// let b = &[1, 2, 3]; - /// - /// assert!(std::ptr::eq(a, b)); - /// ``` - #[clippy::version = "1.49.0"] - pub PTR_EQ, - style, - "use `std::ptr::eq` when comparing raw pointers" -} - -declare_lint_pass!(PtrEq => [PTR_EQ]); - -static LINT_MSG: &str = "use `std::ptr::eq` when comparing raw pointers"; - -impl<'tcx> LateLintPass<'tcx> for PtrEq { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if expr.span.from_expansion() { - return; - } - - if let ExprKind::Binary(ref op, left, right) = expr.kind { - if BinOpKind::Eq == op.node { - let (left, right) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) { - (Some(lhs), Some(rhs)) => (lhs, rhs), - _ => (left, right), - }; - - if_chain! { - if let Some(left_var) = expr_as_cast_to_raw_pointer(cx, left); - if let Some(right_var) = expr_as_cast_to_raw_pointer(cx, right); - if let Some(left_snip) = snippet_opt(cx, left_var.span); - if let Some(right_snip) = snippet_opt(cx, right_var.span); - then { - span_lint_and_sugg( - cx, - PTR_EQ, - expr.span, - LINT_MSG, - "try", - format!("std::ptr::eq({}, {})", left_snip, right_snip), - Applicability::MachineApplicable, - ); - } - } - } - } - } -} - -// If the given expression is a cast to a usize, return the lhs of the cast -// E.g., `foo as *const _ as usize` returns `foo as *const _`. -fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { - if cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize { - if let ExprKind::Cast(expr, _) = cast_expr.kind { - return Some(expr); - } - } - None -} - -// If the given expression is a cast to a `*const` pointer, return the lhs of the cast -// E.g., `foo as *const _` returns `foo`. -fn expr_as_cast_to_raw_pointer<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { - if cx.typeck_results().expr_ty(cast_expr).is_unsafe_ptr() { - if let ExprKind::Cast(expr, _) = cast_expr.kind { - return Some(expr); - } - } - None -} diff --git a/clippy_lints/src/redundant_static_lifetimes.rs b/clippy_lints/src/redundant_static_lifetimes.rs index 0825f00f421..f8801f769e8 100644 --- a/clippy_lints/src/redundant_static_lifetimes.rs +++ b/clippy_lints/src/redundant_static_lifetimes.rs @@ -87,7 +87,7 @@ fn visit_type(&mut self, ty: &Ty, cx: &EarlyContext<'_>, reason: &str) { _ => {}, } } - self.visit_type(&*borrow_type.ty, cx, reason); + self.visit_type(&borrow_type.ty, cx, reason); }, _ => {}, } diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index e525eba53e2..5ae04947b82 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::{snippet_opt, snippet_with_context}; use clippy_utils::{fn_def_id, path_to_local_id}; use if_chain::if_chain; @@ -94,9 +94,10 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { if !in_external_macro(cx.sess(), retexpr.span); if !local.span.from_expansion(); then { - span_lint_and_then( + span_lint_hir_and_then( cx, LET_AND_RETURN, + retexpr.hir_id, retexpr.span, "returning the result of a `let` binding from a block", |err| { @@ -185,6 +186,7 @@ fn check_final_expr<'tcx>( if !borrows { emit_return_lint( cx, + inner.map_or(expr.hir_id, |inner| inner.hir_id), span.expect("`else return` is not possible"), inner.as_ref().map(|i| i.span), replacement, @@ -220,50 +222,81 @@ fn check_final_expr<'tcx>( } } -fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option, replacement: RetReplacement) { +fn emit_return_lint( + cx: &LateContext<'_>, + emission_place: HirId, + ret_span: Span, + inner_span: Option, + replacement: RetReplacement, +) { if ret_span.from_expansion() { return; } match inner_span { Some(inner_span) => { let mut applicability = Applicability::MachineApplicable; - span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| { - let (snippet, _) = snippet_with_context(cx, inner_span, ret_span.ctxt(), "..", &mut applicability); - diag.span_suggestion(ret_span, "remove `return`", snippet, applicability); - }); + span_lint_hir_and_then( + cx, + NEEDLESS_RETURN, + emission_place, + ret_span, + "unneeded `return` statement", + |diag| { + let (snippet, _) = snippet_with_context(cx, inner_span, ret_span.ctxt(), "..", &mut applicability); + diag.span_suggestion(ret_span, "remove `return`", snippet, applicability); + }, + ); }, None => match replacement { RetReplacement::Empty => { - span_lint_and_sugg( + span_lint_hir_and_then( cx, NEEDLESS_RETURN, + emission_place, ret_span, "unneeded `return` statement", - "remove `return`", - String::new(), - Applicability::MachineApplicable, + |diag| { + diag.span_suggestion( + ret_span, + "remove `return`", + String::new(), + Applicability::MachineApplicable, + ); + }, ); }, RetReplacement::Block => { - span_lint_and_sugg( + span_lint_hir_and_then( cx, NEEDLESS_RETURN, + emission_place, ret_span, "unneeded `return` statement", - "replace `return` with an empty block", - "{}".to_string(), - Applicability::MachineApplicable, + |diag| { + diag.span_suggestion( + ret_span, + "replace `return` with an empty block", + "{}".to_string(), + Applicability::MachineApplicable, + ); + }, ); }, RetReplacement::Unit => { - span_lint_and_sugg( + span_lint_hir_and_then( cx, NEEDLESS_RETURN, + emission_place, ret_span, "unneeded `return` statement", - "replace `return` with a unit value", - "()".to_string(), - Applicability::MachineApplicable, + |diag| { + diag.span_suggestion( + ret_span, + "replace `return` with a unit value", + "()".to_string(), + Applicability::MachineApplicable, + ); + }, ); }, }, diff --git a/clippy_lints/src/self_assignment.rs b/clippy_lints/src/self_assignment.rs deleted file mode 100644 index b14f0518bdb..00000000000 --- a/clippy_lints/src/self_assignment.rs +++ /dev/null @@ -1,56 +0,0 @@ -use clippy_utils::diagnostics::span_lint; -use clippy_utils::eq_expr_value; -use clippy_utils::source::snippet; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for explicit self-assignments. - /// - /// ### Why is this bad? - /// Self-assignments are redundant and unlikely to be - /// intentional. - /// - /// ### Known problems - /// If expression contains any deref coercions or - /// indexing operations they are assumed not to have any side effects. - /// - /// ### Example - /// ```rust - /// struct Event { - /// id: usize, - /// x: i32, - /// y: i32, - /// } - /// - /// fn copy_position(a: &mut Event, b: &Event) { - /// a.x = b.x; - /// a.y = a.y; - /// } - /// ``` - #[clippy::version = "1.48.0"] - pub SELF_ASSIGNMENT, - correctness, - "explicit self-assignment" -} - -declare_lint_pass!(SelfAssignment => [SELF_ASSIGNMENT]); - -impl<'tcx> LateLintPass<'tcx> for SelfAssignment { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::Assign(lhs, rhs, _) = &expr.kind { - if eq_expr_value(cx, lhs, rhs) { - let lhs = snippet(cx, lhs.span, ""); - let rhs = snippet(cx, rhs.span, ""); - span_lint( - cx, - SELF_ASSIGNMENT, - expr.span, - &format!("self-assignment of `{}` to `{}`", rhs, lhs), - ); - } - } - } -} diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index 975a0a06e38..2c8aa17e80d 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -26,6 +26,9 @@ /// let mut vec1 = Vec::with_capacity(len); /// vec1.resize(len, 0); /// + /// let mut vec1 = Vec::with_capacity(len); + /// vec1.resize(vec1.capacity(), 0); + /// /// let mut vec2 = Vec::with_capacity(len); /// vec2.extend(repeat(0).take(len)); /// ``` @@ -211,23 +214,20 @@ fn search_slow_extend_filling(&mut self, expr: &'tcx Expr<'_>) { /// Checks if the given expression is resizing a vector with 0 fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) { - if_chain! { - if self.initialization_found; - if let ExprKind::MethodCall(path, [self_arg, len_arg, fill_arg], _) = expr.kind; - if path_to_local_id(self_arg, self.vec_alloc.local_id); - if path.ident.name == sym!(resize); - + if self.initialization_found + && let ExprKind::MethodCall(path, [self_arg, len_arg, fill_arg], _) = expr.kind + && path_to_local_id(self_arg, self.vec_alloc.local_id) + && path.ident.name == sym!(resize) // Check that is filled with 0 - if let ExprKind::Lit(ref lit) = fill_arg.kind; - if let LitKind::Int(0, _) = lit.node; - - // Check that len expression is equals to `with_capacity` expression - if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr); - - then { - self.slow_expression = Some(InitializationType::Resize(expr)); + && let ExprKind::Lit(ref lit) = fill_arg.kind + && let LitKind::Int(0, _) = lit.node { + // Check that len expression is equals to `with_capacity` expression + if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) { + self.slow_expression = Some(InitializationType::Resize(expr)); + } else if let ExprKind::MethodCall(path, _, _) = len_arg.kind && path.ident.as_str() == "capacity" { + self.slow_expression = Some(InitializationType::Resize(expr)); + } } - } } /// Returns `true` if give expression is `repeat(0).take(...)` @@ -240,12 +240,15 @@ fn is_repeat_take(&self, expr: &Expr<'_>) -> bool { if let Some(repeat_expr) = take_args.get(0); if self.is_repeat_zero(repeat_expr); - // Check that len expression is equals to `with_capacity` expression if let Some(len_arg) = take_args.get(1); - if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr); then { - return true; + // Check that len expression is equals to `with_capacity` expression + if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) { + return true; + } else if let ExprKind::MethodCall(path, _, _) = len_arg.kind && path.ident.as_str() == "capacity" { + return true; + } } } diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 71f3e6b6a6e..eb704a07451 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -60,6 +60,12 @@ /// let x = "Hello".to_owned(); /// x + ", World"; /// ``` + /// + /// Use instead: + /// ```rust + /// let mut x = "Hello".to_owned(); + /// x.push_str(", World"); + /// ``` #[clippy::version = "pre 1.29.0"] pub STRING_ADD, restriction, diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index cbe1406728b..5f3e98144f4 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -16,9 +16,10 @@ use clippy_utils::in_constant; use if_chain::if_chain; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::symbol::sym; declare_clippy_lint! { @@ -385,7 +386,10 @@ "transmute to or from a type with an undefined representation" } -declare_lint_pass!(Transmute => [ +pub struct Transmute { + msrv: Option, +} +impl_lint_pass!(Transmute => [ CROSSPOINTER_TRANSMUTE, TRANSMUTE_PTR_TO_REF, TRANSMUTE_PTR_TO_PTR, @@ -401,13 +405,18 @@ TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, TRANSMUTE_UNDEFINED_REPR, ]); - +impl Transmute { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} impl<'tcx> LateLintPass<'tcx> for Transmute { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { if_chain! { if let ExprKind::Call(path_expr, [arg]) = e.kind; - if let ExprKind::Path(ref qpath) = path_expr.kind; - if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id(); + if let ExprKind::Path(QPath::Resolved(None, path)) = path_expr.kind; + if let Some(def_id) = path.res.opt_def_id(); if cx.tcx.is_diagnostic_item(sym::transmute, def_id); then { // Avoid suggesting non-const operations in const contexts: @@ -427,7 +436,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { let linted = wrong_transmute::check(cx, e, from_ty, to_ty) | crosspointer_transmute::check(cx, e, from_ty, to_ty) - | transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, qpath) + | transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv) | transmute_int_to_char::check(cx, e, from_ty, to_ty, arg, const_context) | transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context) | transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg) @@ -446,4 +455,6 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { } } } + + extract_msrv_attr!(LateContext); } diff --git a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs index f3653199b37..3ed5d5c6950 100644 --- a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs +++ b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs @@ -1,11 +1,12 @@ -use super::utils::get_type_snippet; use super::TRANSMUTE_PTR_TO_REF; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{meets_msrv, msrvs, sugg}; use rustc_errors::Applicability; -use rustc_hir::{Expr, Mutability, QPath}; +use rustc_hir::{self as hir, Expr, GenericArg, Mutability, Path, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::{self, Ty, TypeFoldable}; +use rustc_semver::RustcVersion; /// Checks for `transmute_ptr_to_ref` lint. /// Returns `true` if it's triggered, otherwise returns `false`. @@ -15,7 +16,8 @@ pub(super) fn check<'tcx>( from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, arg: &'tcx Expr<'_>, - qpath: &'tcx QPath<'_>, + path: &'tcx Path<'_>, + msrv: Option, ) -> bool { match (&from_ty.kind(), &to_ty.kind()) { (ty::RawPtr(from_ptr_ty), ty::Ref(_, to_ref_ty, mutbl)) => { @@ -34,19 +36,34 @@ pub(super) fn check<'tcx>( } else { ("&*", "*const") }; + let mut app = Applicability::MachineApplicable; - let arg = if from_ptr_ty.ty == *to_ref_ty { - arg + let sugg = if let Some(ty) = get_explicit_type(path) { + let ty_snip = snippet_with_applicability(cx, ty.span, "..", &mut app); + if meets_msrv(msrv, msrvs::POINTER_CAST) { + format!("{}{}.cast::<{}>()", deref, arg.maybe_par(), ty_snip) + } else if from_ptr_ty.has_erased_regions() { + sugg::make_unop(deref, arg.as_ty(format!("{} () as {} {}", cast, cast, ty_snip))) + .to_string() + } else { + sugg::make_unop(deref, arg.as_ty(format!("{} {}", cast, ty_snip))).to_string() + } + } else if from_ptr_ty.ty == *to_ref_ty { + if from_ptr_ty.has_erased_regions() { + if meets_msrv(msrv, msrvs::POINTER_CAST) { + format!("{}{}.cast::<{}>()", deref, arg.maybe_par(), to_ref_ty) + } else { + sugg::make_unop(deref, arg.as_ty(format!("{} () as {} {}", cast, cast, to_ref_ty))) + .to_string() + } + } else { + sugg::make_unop(deref, arg).to_string() + } } else { - arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, *to_ref_ty))) + sugg::make_unop(deref, arg.as_ty(format!("{} {}", cast, to_ref_ty))).to_string() }; - diag.span_suggestion( - e.span, - "try", - sugg::make_unop(deref, arg).to_string(), - Applicability::Unspecified, - ); + diag.span_suggestion(e.span, "try", sugg, app); }, ); true @@ -54,3 +71,14 @@ pub(super) fn check<'tcx>( _ => false, } } + +/// Gets the type `Bar` in `…::transmute`. +fn get_explicit_type<'tcx>(path: &'tcx Path<'tcx>) -> Option<&'tcx hir::Ty<'tcx>> { + if let GenericArg::Type(ty) = path.segments.last()?.args?.args.get(1)? + && let TyKind::Rptr(_, ty) = &ty.kind + { + Some(ty.ty) + } else { + None + } +} diff --git a/clippy_lints/src/transmute/utils.rs b/clippy_lints/src/transmute/utils.rs index 0cbf5ccefa6..74927570b40 100644 --- a/clippy_lints/src/transmute/utils.rs +++ b/clippy_lints/src/transmute/utils.rs @@ -1,35 +1,9 @@ -use clippy_utils::last_path_segment; -use clippy_utils::source::snippet; -use if_chain::if_chain; -use rustc_hir::{Expr, GenericArg, QPath, TyKind}; +use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty::{cast::CastKind, Ty}; use rustc_span::DUMMY_SP; use rustc_typeck::check::{cast::CastCheck, FnCtxt, Inherited}; -/// Gets the snippet of `Bar` in `…::transmute`. If that snippet is -/// not available , use -/// the type's `ToString` implementation. In weird cases it could lead to types -/// with invalid `'_` -/// lifetime, but it should be rare. -pub(super) fn get_type_snippet(cx: &LateContext<'_>, path: &QPath<'_>, to_ref_ty: Ty<'_>) -> String { - let seg = last_path_segment(path); - if_chain! { - if let Some(params) = seg.args; - if !params.parenthesized; - if let Some(to_ty) = params.args.iter().filter_map(|arg| match arg { - GenericArg::Type(ty) => Some(ty), - _ => None, - }).nth(1); - if let TyKind::Rptr(_, ref to_ty) = to_ty.kind; - then { - return snippet(cx, to_ty.ty.span, &to_ref_ty.to_string()).to_string(); - } - } - - to_ref_ty.to_string() -} - // check if the component types of the transmuted collection and the result have different ABI, // size or alignment pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool { diff --git a/clippy_lints/src/unused_async.rs b/clippy_lints/src/unused_async.rs index c8ec4442ab1..a832dfcccaf 100644 --- a/clippy_lints/src/unused_async.rs +++ b/clippy_lints/src/unused_async.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, Visitor}; -use rustc_hir::{Body, Expr, ExprKind, FnDecl, FnHeader, HirId, IsAsync, YieldSource}; +use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, IsAsync, YieldSource}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -68,20 +68,18 @@ fn check_fn( span: Span, hir_id: HirId, ) { - if let FnKind::ItemFn(_, _, FnHeader { asyncness, .. }) = &fn_kind { - if matches!(asyncness, IsAsync::Async) { - let mut visitor = AsyncFnVisitor { cx, found_await: false }; - walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), span, hir_id); - if !visitor.found_await { - span_lint_and_help( - cx, - UNUSED_ASYNC, - span, - "unused `async` for function with no await statements", - None, - "consider removing the `async` from this function", - ); - } + if !span.from_expansion() && fn_kind.asyncness() == IsAsync::Async { + let mut visitor = AsyncFnVisitor { cx, found_await: false }; + walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), span, hir_id); + if !visitor.found_await { + span_lint_and_help( + cx, + UNUSED_ASYNC, + span, + "unused `async` for function with no await statements", + None, + "consider removing the `async` from this function", + ); } } } diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index 9b9e25326f9..d3f9e5abfd7 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::higher; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{path_to_local, usage::is_potentially_mutated}; @@ -251,9 +251,10 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { unwrappable.kind.error_variant_pattern() }; - span_lint_and_then( + span_lint_hir_and_then( self.cx, UNNECESSARY_UNWRAP, + expr.hir_id, expr.span, &format!( "called `{}` on `{}` after checking its variant with `{}`", @@ -283,9 +284,10 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { }, ); } else { - span_lint_and_then( + span_lint_hir_and_then( self.cx, PANICKING_UNWRAP, + expr.hir_id, expr.span, &format!("this call to `{}()` will always panic", method_name.ident.name), diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index b885e5132f1..a94f0357977 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -1,3 +1,4 @@ +use crate::utils::internal_lints::metadata_collector::is_deprecated_lint; use clippy_utils::consts::{constant_simple, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::macros::root_macro_call_first_node; @@ -338,6 +339,46 @@ "checking if all necessary steps were taken when adding a MSRV to a lint" } +declare_clippy_lint! { + /// ### What it does + /// Checks for cases of an auto-generated deprecated lint without an updated reason, + /// i.e. `"default deprecation note"`. + /// + /// ### Why is this bad? + /// Indicates that the documentation is incomplete. + /// + /// ### Example + /// ```rust,ignore + /// declare_deprecated_lint! { + /// /// ### What it does + /// /// Nothing. This lint has been deprecated. + /// /// + /// /// ### Deprecation reason + /// /// TODO + /// #[clippy::version = "1.63.0"] + /// pub COOL_LINT, + /// "default deprecation note" + /// } + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// declare_deprecated_lint! { + /// /// ### What it does + /// /// Nothing. This lint has been deprecated. + /// /// + /// /// ### Deprecation reason + /// /// This lint has been replaced by `cooler_lint` + /// #[clippy::version = "1.63.0"] + /// pub COOL_LINT, + /// "this lint has been replaced by `cooler_lint`" + /// } + /// ``` + pub DEFAULT_DEPRECATION_REASON, + internal, + "found 'default deprecation note' in a deprecated lint declaration" +} + declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]); impl EarlyLintPass for ClippyLintsInternal { @@ -375,42 +416,67 @@ pub struct LintWithoutLintPass { registered_lints: FxHashSet, } -impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE, MISSING_CLIPPY_VERSION_ATTRIBUTE]); +impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE, MISSING_CLIPPY_VERSION_ATTRIBUTE, DEFAULT_DEPRECATION_REASON]); impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if is_lint_allowed(cx, DEFAULT_LINT, item.hir_id()) { + if is_lint_allowed(cx, DEFAULT_LINT, item.hir_id()) + || is_lint_allowed(cx, DEFAULT_DEPRECATION_REASON, item.hir_id()) + { return; } if let hir::ItemKind::Static(ty, Mutability::Not, body_id) = item.kind { - if is_lint_ref_type(cx, ty) { + let is_lint_ref_ty = is_lint_ref_type(cx, ty); + if is_deprecated_lint(cx, ty) || is_lint_ref_ty { check_invalid_clippy_version_attribute(cx, item); let expr = &cx.tcx.hir().body(body_id).value; - if_chain! { - if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind; - if let ExprKind::Struct(_, fields, _) = inner_exp.kind; - let field = fields - .iter() - .find(|f| f.ident.as_str() == "desc") - .expect("lints must have a description field"); - if let ExprKind::Lit(Spanned { - node: LitKind::Str(ref sym, _), - .. - }) = field.expr.kind; - if sym.as_str() == "default lint description"; + let fields; + if is_lint_ref_ty { + if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind + && let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind { + fields = struct_fields; + } else { + return; + } + } else if let ExprKind::Struct(_, struct_fields, _) = expr.kind { + fields = struct_fields; + } else { + return; + } - then { + let field = fields + .iter() + .find(|f| f.ident.as_str() == "desc") + .expect("lints must have a description field"); + + if let ExprKind::Lit(Spanned { + node: LitKind::Str(ref sym, _), + .. + }) = field.expr.kind + { + let sym_str = sym.as_str(); + if is_lint_ref_ty { + if sym_str == "default lint description" { + span_lint( + cx, + DEFAULT_LINT, + item.span, + &format!("the lint `{}` has the default lint description", item.ident.name), + ); + } + + self.declared_lints.insert(item.ident.name, item.span); + } else if sym_str == "default deprecation note" { span_lint( cx, - DEFAULT_LINT, + DEFAULT_DEPRECATION_REASON, item.span, - &format!("the lint `{}` has the default lint description", item.ident.name), + &format!("the lint `{}` has the default deprecation reason", item.ident.name), ); } } - self.declared_lints.insert(item.ident.name, item.span); } } else if let Some(macro_call) = root_macro_call_first_node(cx, item) { if !matches!( @@ -668,6 +734,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { 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; then { let and_then_snippets = get_and_then_snippets(cx, and_then_args); let mut sle = SpanlessEq::new(cx).deny_side_effects(); diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 2564099f4db..6518e0a6ea0 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -104,7 +104,7 @@ macro_rules! RENAME_VALUE_TEMPLATE { }; } -const LINT_EMISSION_FUNCTIONS: [&[&str]; 8] = [ +const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [ &["clippy_utils", "diagnostics", "span_lint"], &["clippy_utils", "diagnostics", "span_lint_and_help"], &["clippy_utils", "diagnostics", "span_lint_and_note"], @@ -190,7 +190,12 @@ pub fn new() -> Self { lints: BinaryHeap::::default(), applicability_info: FxHashMap::::default(), config: collect_configs(), - clippy_project_root: clippy_dev::clippy_project_root(), + clippy_project_root: std::env::current_dir() + .expect("failed to get current dir") + .ancestors() + .nth(1) + .expect("failed to get project root") + .to_path_buf(), } } @@ -841,7 +846,7 @@ fn get_lint_level_from_group(lint_group: &str) -> Option<&'static str> { .find_map(|(group_name, group_level)| (*group_name == lint_group).then(|| *group_level)) } -fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { +pub(super) fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { if let hir::TyKind::Path(ref path) = ty.kind { if let hir::def::Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, ty.hir_id) { return match_def_path(cx, def_id, &DEPRECATED_LINT_TYPE); diff --git a/clippy_lints/src/vec_resize_to_zero.rs b/clippy_lints/src/vec_resize_to_zero.rs index 4d86abd0fa1..0fee3e812d2 100644 --- a/clippy_lints/src/vec_resize_to_zero.rs +++ b/clippy_lints/src/vec_resize_to_zero.rs @@ -20,6 +20,11 @@ /// ```rust /// vec!(1, 2, 3, 4, 5).resize(0, 5) /// ``` + /// + /// Use instead: + /// ```rust + /// vec!(1, 2, 3, 4, 5).clear() + /// ``` #[clippy::version = "1.46.0"] pub VEC_RESIZE_TO_ZERO, correctness, diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index c4e0b8448ab..bb443bdc116 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.63" +version = "0.1.64" edition = "2021" publish = false diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index af62c4afd5a..793e3cc58c2 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -1,4 +1,5 @@ use crate::consts::constant_simple; +use crate::macros::macro_backtrace; use crate::source::snippet_opt; use rustc_ast::ast::InlineAsmTemplatePiece; use rustc_data_structures::fx::FxHasher; @@ -12,9 +13,13 @@ use rustc_lexer::{tokenize, TokenKind}; use rustc_lint::LateContext; use rustc_middle::ty::TypeckResults; -use rustc_span::Symbol; +use rustc_span::{sym, Symbol}; use std::hash::{Hash, Hasher}; +/// Callback that is called when two expressions are not equal in the sense of `SpanlessEq`, but +/// other conditions would make them equal. +type SpanlessEqCallback<'a> = dyn FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a; + /// Type used to check whether two ast are the same. This is different from the /// operator `==` on ast types as this operator would compare true equality with /// ID and span. @@ -25,7 +30,7 @@ pub struct SpanlessEq<'a, 'tcx> { cx: &'a LateContext<'tcx>, maybe_typeck_results: Option<(&'tcx TypeckResults<'tcx>, &'tcx TypeckResults<'tcx>)>, allow_side_effects: bool, - expr_fallback: Option, &Expr<'_>) -> bool + 'a>>, + expr_fallback: Option>>, } impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { @@ -121,6 +126,9 @@ pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool { /// 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 @@ -171,6 +179,38 @@ fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool { } } + 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| { + 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 { match (left, right) { (ArrayLen::Infer(..), ArrayLen::Infer(..)) => true, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 5106c39b5c6..9fa28e137f9 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1539,9 +1539,13 @@ fn is_err(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { None } -/// Returns `true` if the lint is allowed in the current context +/// Returns `true` if the lint is allowed in the current context. This is useful for +/// skipping long running code when it's unnecessary /// -/// Useful for skipping long running code when it's unnecessary +/// This function should check the lint level for the same node, that the lint will +/// be emitted at. If the information is buffered to be emitted at a later point, please +/// make sure to use `span_lint_hir` functions to emit the lint. This ensures that +/// expectations at the checked nodes will be fulfilled. pub fn is_lint_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool { cx.tcx.lint_level_at_node(lint, id).0 == Level::Allow } @@ -2058,6 +2062,21 @@ pub fn peel_hir_expr_refs<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) { (e, count) } +/// Peels off all references on the type. Returns the underlying type and the number of references +/// removed. +pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize) { + let mut count = 0; + loop { + match &ty.kind { + TyKind::Rptr(_, ref_ty) => { + ty = ref_ty.ty; + count += 1; + }, + _ => break (ty, count), + } + } +} + /// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is /// dereferenced. An overloaded deref such as `Vec` to slice would not be removed. pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> { @@ -2110,7 +2129,7 @@ fn with_test_item_names<'tcx>(tcx: TyCtxt<'tcx>, module: LocalDefId, f: impl Fn( } } names.sort_unstable(); - f(&*entry.insert(names)) + f(entry.insert(names)) }, } } @@ -2168,6 +2187,50 @@ pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool { && item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests") } +/// Walks the HIR tree from the given expression, up to the node where the value produced by the +/// expression is consumed. Calls the function for every node encountered this way until it returns +/// `Some`. +/// +/// This allows walking through `if`, `match`, `break`, block expressions to find where the value +/// produced by the expression is consumed. +pub fn walk_to_expr_usage<'tcx, T>( + cx: &LateContext<'tcx>, + e: &Expr<'tcx>, + mut f: impl FnMut(Node<'tcx>, HirId) -> Option, +) -> Option { + let map = cx.tcx.hir(); + let mut iter = map.parent_iter(e.hir_id); + let mut child_id = e.hir_id; + + while let Some((parent_id, parent)) = iter.next() { + if let Some(x) = f(parent, child_id) { + return Some(x); + } + let parent = match parent { + Node::Expr(e) => e, + Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => { + child_id = parent_id; + continue; + }, + Node::Arm(a) if a.body.hir_id == child_id => { + child_id = parent_id; + continue; + }, + _ => return None, + }; + match parent.kind { + ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id, + ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => { + child_id = id; + iter = map.parent_iter(id); + }, + ExprKind::Block(..) => child_id = parent_id, + _ => return None, + } + } + None +} + macro_rules! op_utils { ($($name:ident $assign:ident)*) => { /// Binary operation traits like `LangItem::Add` diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index b9ec2c19fdd..b09c929f76e 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -12,8 +12,8 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { - 1,53,0 { OR_PATTERNS, MANUAL_BITS } - 1,52,0 { STR_SPLIT_ONCE } + 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 } 1,50,0 { BOOL_THEN } 1,47,0 { TAU } @@ -23,14 +23,15 @@ macro_rules! msrv_aliases { 1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS } 1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE } 1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF } - 1,38,0 { POINTER_CAST } + 1,38,0 { POINTER_CAST, REM_EUCLID } 1,37,0 { TYPE_ALIAS_ENUM_VARIANTS } 1,36,0 { ITERATOR_COPIED } 1,35,0 { OPTION_COPIED, RANGE_CONTAINS } 1,34,0 { TRY_FROM } 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } 1,28,0 { FROM_BOOL } - 1,26,0 { RANGE_INCLUSIVE } + 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN } + 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN } 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR } 1,16,0 { STR_REPEAT } 1,24,0 { IS_ASCII_DIGIT } diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 89789c3d851..6542e77113b 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -21,8 +21,14 @@ pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"]; pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"]; pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"]; +pub const BTREESET_ITER: [&str; 6] = ["alloc", "collections", "btree", "set", "BTreeSet", "iter"]; pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"]; pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"]; +pub const CORE_ITER_COLLECT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "collect"]; +pub const CORE_ITER_CLONED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "cloned"]; +pub const CORE_ITER_COPIED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "copied"]; +pub const CORE_ITER_FILTER: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "filter"]; +pub const CORE_ITER_INTO_ITER: [&str; 6] = ["core", "iter", "traits", "collect", "IntoIterator", "into_iter"]; pub const CSTRING_AS_C_STR: [&str; 5] = ["alloc", "ffi", "c_str", "CString", "as_c_str"]; pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"]; pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"]; @@ -50,6 +56,7 @@ pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"]; pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"]; pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"]; +pub const HASHSET_ITER: [&str; 6] = ["std", "collections", "hash", "set", "HashSet", "iter"]; #[cfg(feature = "internal")] pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"]; #[cfg(feature = "internal")] @@ -62,6 +69,7 @@ pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"]; pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"]; pub const ITER_COUNT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "count"]; +pub const ITER_EMPTY: [&str; 5] = ["core", "iter", "sources", "empty", "Empty"]; pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"]; #[expect(clippy::invalid_paths)] // internal lints do not know about all external crates pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"]; @@ -83,8 +91,6 @@ pub const ORD: [&str; 3] = ["core", "cmp", "Ord"]; pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"]; pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"]; -pub const PARKING_LOT_RAWMUTEX: [&str; 3] = ["parking_lot", "raw_mutex", "RawMutex"]; -pub const PARKING_LOT_RAWRWLOCK: [&str; 3] = ["parking_lot", "raw_rwlock", "RawRwLock"]; pub const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard"]; pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockReadGuard"]; pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockWriteGuard"]; @@ -144,6 +150,7 @@ pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"]; pub const SLICE_GET: [&str; 4] = ["core", "slice", "", "get"]; pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "", "into_vec"]; +pub const SLICE_INTO: [&str; 4] = ["core", "slice", "", "iter"]; pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"]; pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"]; pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"]; @@ -153,6 +160,7 @@ pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"]; pub const STRING_NEW: [&str; 4] = ["alloc", "string", "String", "new"]; 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_LEN: [&str; 4] = ["core", "str", "", "len"]; @@ -178,6 +186,7 @@ pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"]; pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"]; pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"]; +pub const VEC_DEQUE_ITER: [&str; 5] = ["alloc", "collections", "vec_deque", "VecDeque", "iter"]; pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"]; pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"]; pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"]; diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 227e97d37ec..6ca36eed4e6 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -2,19 +2,20 @@ #![allow(clippy::module_name_repetitions)] +use core::ops::ControlFlow; use rustc_ast::ast::Mutability; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; -use rustc_hir::def::{CtorKind, DefKind, Res}; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{Expr, LangItem, TyKind, Unsafety}; +use rustc_hir::{Expr, FnDecl, LangItem, TyKind, Unsafety}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::mir::interpret::{ConstValue, Scalar}; use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst}; use rustc_middle::ty::{ - self, AdtDef, Binder, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, Ty, TyCtxt, TypeFoldable, UintTy, - VariantDiscr, + self, AdtDef, Binder, BoundRegion, DefIdTree, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, ProjectionTy, + Region, RegionKind, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitor, UintTy, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; @@ -501,16 +502,46 @@ pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator { Sig(Binder<'tcx, FnSig<'tcx>>), - Closure(Binder<'tcx, FnSig<'tcx>>), + Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>), Trait(Binder<'tcx, Ty<'tcx>>, Option>>), } impl<'tcx> ExprFnSig<'tcx> { - /// Gets the argument type at the given offset. - pub fn input(self, i: usize) -> Binder<'tcx, Ty<'tcx>> { + /// Gets the argument type at the given offset. This will return `None` when the index is out of + /// bounds only for variadic functions, otherwise this will panic. + pub fn input(self, i: usize) -> Option>> { match self { - Self::Sig(sig) => sig.input(i), - Self::Closure(sig) => sig.input(0).map_bound(|ty| ty.tuple_fields()[i]), - Self::Trait(inputs, _) => inputs.map_bound(|ty| ty.tuple_fields()[i]), + Self::Sig(sig) => { + if sig.c_variadic() { + sig.inputs().map_bound(|inputs| inputs.get(i).copied()).transpose() + } else { + Some(sig.input(i)) + } + }, + Self::Closure(_, sig) => Some(sig.input(0).map_bound(|ty| ty.tuple_fields()[i])), + Self::Trait(inputs, _) => Some(inputs.map_bound(|ty| ty.tuple_fields()[i])), + } + } + + /// Gets the argument type at the given offset. For closures this will also get the type as + /// written. This will return `None` when the index is out of bounds only for variadic + /// 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) => { + if sig.c_variadic() { + sig.inputs() + .map_bound(|inputs| inputs.get(i).copied()) + .transpose() + .map(|arg| (None, arg)) + } else { + Some((None, sig.input(i))) + } + }, + Self::Closure(decl, sig) => Some(( + decl.and_then(|decl| decl.inputs.get(i)), + sig.input(0).map_bound(|ty| ty.tuple_fields()[i]), + )), + Self::Trait(inputs, _) => Some((None, inputs.map_bound(|ty| ty.tuple_fields()[i]))), } } @@ -518,7 +549,7 @@ pub fn input(self, i: usize) -> Binder<'tcx, Ty<'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, } } @@ -529,74 +560,123 @@ pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option Some(ExprFnSig::Closure(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::Dynamic(bounds, _) => { - let lang_items = cx.tcx.lang_items(); - match bounds.principal() { - Some(bound) - if Some(bound.def_id()) == lang_items.fn_trait() - || Some(bound.def_id()) == lang_items.fn_once_trait() - || Some(bound.def_id()) == lang_items.fn_mut_trait() => - { - let output = bounds - .projection_bounds() - .find(|p| lang_items.fn_once_output().map_or(false, |id| id == p.item_def_id())) - .map(|p| p.map_bound(|p| p.term.ty().expect("return type was a const"))); - Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output)) - }, - _ => None, - } - }, - ty::Param(_) | ty::Projection(..) => { - let mut inputs = None; - let mut output = None; - let lang_items = cx.tcx.lang_items(); + ty_sig(cx, cx.typeck_results().expr_ty_adjusted(expr).peel_refs()) + } +} - for (pred, _) in all_predicates_of(cx.tcx, cx.typeck_results().hir_owner.to_def_id()) { - let mut is_input = false; - if let Some(ty) = pred - .kind() - .map_bound(|pred| match pred { - PredicateKind::Trait(p) - if (lang_items.fn_trait() == Some(p.def_id()) - || lang_items.fn_mut_trait() == Some(p.def_id()) - || lang_items.fn_once_trait() == Some(p.def_id())) - && p.self_ty() == ty => - { - is_input = true; - Some(p.trait_ref.substs.type_at(1)) - }, - PredicateKind::Projection(p) - if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() - && p.projection_ty.self_ty() == ty => - { - is_input = false; - p.term.ty() - }, - _ => None, - }) - .transpose() - { - if is_input && inputs.is_none() { - inputs = Some(ty); - } else if !is_input && output.is_none() { - output = Some(ty); - } else { - // Multiple different fn trait impls. Is this even allowed? - return None; - } - } - } +fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { + match *ty.kind() { + ty::Closure(id, subs) => { + let decl = id + .as_local() + .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::Dynamic(bounds, _) => { + let lang_items = cx.tcx.lang_items(); + match bounds.principal() { + Some(bound) + if Some(bound.def_id()) == lang_items.fn_trait() + || Some(bound.def_id()) == lang_items.fn_once_trait() + || Some(bound.def_id()) == lang_items.fn_mut_trait() => + { + let output = bounds + .projection_bounds() + .find(|p| lang_items.fn_once_output().map_or(false, |id| id == p.item_def_id())) + .map(|p| p.map_bound(|p| p.term.ty().unwrap())); + Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output)) + }, + _ => None, + } + }, + ty::Projection(proj) => match cx.tcx.try_normalize_erasing_regions(cx.param_env, ty) { + Ok(normalized_ty) if normalized_ty != ty => ty_sig(cx, normalized_ty), + _ => sig_for_projection(cx, proj).or_else(|| sig_from_bounds(cx, ty)), + }, + ty::Param(_) => sig_from_bounds(cx, ty), + _ => None, + } +} - inputs.map(|ty| ExprFnSig::Trait(ty, output)) +fn sig_from_bounds<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { + let mut inputs = None; + let mut output = None; + let lang_items = cx.tcx.lang_items(); + + for (pred, _) in all_predicates_of(cx.tcx, cx.typeck_results().hir_owner.to_def_id()) { + match pred.kind().skip_binder() { + PredicateKind::Trait(p) + if (lang_items.fn_trait() == Some(p.def_id()) + || lang_items.fn_mut_trait() == Some(p.def_id()) + || lang_items.fn_once_trait() == Some(p.def_id())) + && p.self_ty() == ty => + { + if inputs.is_some() { + // Multiple different fn trait impls. Is this even allowed? + return None; + } + inputs = Some(pred.kind().rebind(p.trait_ref.substs.type_at(1))); }, - _ => None, + PredicateKind::Projection(p) + if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() + && p.projection_ty.self_ty() == ty => + { + if output.is_some() { + // Multiple different fn trait impls. Is this even allowed? + return None; + } + output = Some(pred.kind().rebind(p.term.ty().unwrap())); + }, + _ => (), } } + + inputs.map(|ty| ExprFnSig::Trait(ty, output)) +} + +fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: ProjectionTy<'tcx>) -> Option> { + let mut inputs = None; + let mut output = None; + let lang_items = cx.tcx.lang_items(); + + for pred in cx + .tcx + .bound_explicit_item_bounds(ty.item_def_id) + .transpose_iter() + .map(|x| x.map_bound(|(p, _)| p)) + { + match pred.0.kind().skip_binder() { + PredicateKind::Trait(p) + if (lang_items.fn_trait() == Some(p.def_id()) + || lang_items.fn_mut_trait() == Some(p.def_id()) + || lang_items.fn_once_trait() == Some(p.def_id())) => + { + if inputs.is_some() { + // Multiple different fn trait impls. Is this even allowed? + return None; + } + inputs = Some( + pred.map_bound(|pred| pred.kind().rebind(p.trait_ref.substs.type_at(1))) + .subst(cx.tcx, ty.substs), + ); + }, + PredicateKind::Projection(p) if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() => { + if output.is_some() { + // Multiple different fn trait impls. Is this even allowed? + return None; + } + output = Some( + pred.map_bound(|pred| pred.kind().rebind(p.term.ty().unwrap())) + .subst(cx.tcx, ty.substs), + ); + }, + _ => (), + } + } + + inputs.map(|ty| ExprFnSig::Trait(ty, output)) } #[derive(Clone, Copy)] @@ -667,3 +747,45 @@ pub fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { false } } + +pub fn for_each_top_level_late_bound_region( + ty: Ty<'_>, + f: impl FnMut(BoundRegion) -> ControlFlow, +) -> ControlFlow { + struct V { + index: u32, + f: F, + } + impl<'tcx, B, F: FnMut(BoundRegion) -> ControlFlow> TypeVisitor<'tcx> for V { + type BreakTy = B; + fn visit_region(&mut self, r: Region<'tcx>) -> ControlFlow { + if let RegionKind::ReLateBound(idx, bound) = r.kind() && idx.as_u32() == self.index { + (self.f)(bound) + } else { + ControlFlow::Continue(()) + } + } + fn visit_binder>(&mut self, t: &Binder<'tcx, T>) -> ControlFlow { + self.index += 1; + let res = t.super_visit_with(self); + self.index -= 1; + res + } + } + ty.visit_with(&mut V { index: 0, f }) +} + +/// Gets the struct or enum variant from the given `Res` +pub fn variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<&'tcx VariantDef> { + match res { + Res::Def(DefKind::Struct, id) => Some(cx.tcx.adt_def(id).non_enum_variant()), + Res::Def(DefKind::Variant, id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).variant_with_id(id)), + Res::Def(DefKind::Ctor(CtorOf::Struct, _), id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).non_enum_variant()), + Res::Def(DefKind::Ctor(CtorOf::Variant, _), id) => { + let var_id = cx.tcx.parent(id); + Some(cx.tcx.adt_def(cx.tcx.parent(var_id)).variant_with_id(var_id)) + }, + Res::SelfCtor(id) => Some(cx.tcx.type_of(id).ty_adt_def().unwrap().non_enum_variant()), + _ => None, + } +} diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index b6c8f1d516e..68cfa8c1aa8 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -1,16 +1,18 @@ +use crate::ty::needs_ordered_drop; use crate::{get_enclosing_block, path_to_local_id}; use core::ops::ControlFlow; use rustc_hir as hir; -use rustc_hir::def::{DefKind, Res}; +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, Stmt, UnOp, UnsafeSource, - Unsafety, + Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, QPath, Stmt, UnOp, + UnsafeSource, Unsafety, }; use rustc_lint::LateContext; use rustc_middle::hir::map::Map; use rustc_middle::hir::nested_filter; -use rustc_middle::ty; +use rustc_middle::ty::adjustment::Adjust; +use rustc_middle::ty::{self, Ty, TypeckResults}; /// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested /// bodies (i.e. closures) are visited. @@ -494,3 +496,124 @@ fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) { ControlFlow::Continue(()) } } + +// Calls the given function for every unconsumed temporary created by the expression. Note the +// function is only guaranteed to be called for types which need to be dropped, but it may be called +// for other types. +pub fn for_each_unconsumed_temporary<'tcx, B>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + mut f: impl FnMut(Ty<'tcx>) -> ControlFlow, +) -> ControlFlow { + // Todo: Handle partially consumed values. + fn helper<'tcx, B>( + typeck: &'tcx TypeckResults<'tcx>, + consume: bool, + e: &'tcx Expr<'tcx>, + f: &mut impl FnMut(Ty<'tcx>) -> ControlFlow, + ) -> ControlFlow { + if !consume + || matches!( + typeck.expr_adjustments(e), + [adjust, ..] if matches!(adjust.kind, Adjust::Borrow(_) | Adjust::Deref(_)) + ) + { + match e.kind { + ExprKind::Path(QPath::Resolved(None, p)) + if matches!(p.res, Res::Def(DefKind::Ctor(_, CtorKind::Const), _)) => + { + f(typeck.expr_ty(e))?; + }, + ExprKind::Path(_) + | ExprKind::Unary(UnOp::Deref, _) + | ExprKind::Index(..) + | ExprKind::Field(..) + | ExprKind::AddrOf(..) => (), + _ => f(typeck.expr_ty(e))?, + } + } + match e.kind { + ExprKind::AddrOf(_, _, e) + | ExprKind::Field(e, _) + | ExprKind::Unary(UnOp::Deref, e) + | ExprKind::Match(e, ..) + | ExprKind::Let(&Let { init: e, .. }) => { + helper(typeck, false, e, f)?; + }, + ExprKind::Block(&Block { expr: Some(e), .. }, _) + | ExprKind::Box(e) + | ExprKind::Cast(e, _) + | ExprKind::Unary(_, e) => { + helper(typeck, true, e, f)?; + }, + ExprKind::Call(callee, args) => { + helper(typeck, true, callee, f)?; + for arg in args { + helper(typeck, true, arg, f)?; + } + }, + ExprKind::MethodCall(_, args, _) | ExprKind::Tup(args) | ExprKind::Array(args) => { + for arg in args { + helper(typeck, true, arg, f)?; + } + }, + ExprKind::Index(borrowed, consumed) + | ExprKind::Assign(borrowed, consumed, _) + | ExprKind::AssignOp(_, borrowed, consumed) => { + helper(typeck, false, borrowed, f)?; + helper(typeck, true, consumed, f)?; + }, + ExprKind::Binary(_, lhs, rhs) => { + helper(typeck, true, lhs, f)?; + helper(typeck, true, rhs, f)?; + }, + ExprKind::Struct(_, fields, default) => { + for field in fields { + helper(typeck, true, field.expr, f)?; + } + if let Some(default) = default { + helper(typeck, false, default, f)?; + } + }, + ExprKind::If(cond, then, else_expr) => { + helper(typeck, true, cond, f)?; + helper(typeck, true, then, f)?; + if let Some(else_expr) = else_expr { + helper(typeck, true, else_expr, f)?; + } + }, + ExprKind::Type(e, _) => { + helper(typeck, consume, e, f)?; + }, + + // Either drops temporaries, jumps out of the current expression, or has no sub expression. + ExprKind::DropTemps(_) + | ExprKind::Ret(_) + | ExprKind::Break(..) + | ExprKind::Yield(..) + | ExprKind::Block(..) + | ExprKind::Loop(..) + | ExprKind::Repeat(..) + | ExprKind::Lit(_) + | ExprKind::ConstBlock(_) + | ExprKind::Closure { .. } + | ExprKind::Path(_) + | ExprKind::Continue(_) + | ExprKind::InlineAsm(_) + | ExprKind::Err => (), + } + ControlFlow::Continue(()) + } + helper(cx.typeck_results(), true, e, &mut f) +} + +pub fn any_temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool { + for_each_unconsumed_temporary(cx, e, |ty| { + if needs_ordered_drop(cx, ty) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_break() +} diff --git a/rust-toolchain b/rust-toolchain index 6ad56aacf8c..6cc6d5036b3 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-06-16" +channel = "nightly-2022-06-30" components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/src/driver.rs b/src/driver.rs index 67467f89b47..96d542cfe10 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -60,6 +60,7 @@ fn test_arg_value() { assert_eq!(arg_value(args, "--bar", |p| p == "foo"), None); assert_eq!(arg_value(args, "--foobar", |p| p == "foo"), None); assert_eq!(arg_value(args, "--foobar", |p| p == "123"), Some("123")); + assert_eq!(arg_value(args, "--foobar", |p| p.contains("12")), Some("123")); assert_eq!(arg_value(args, "--foo", |_| true), None); } @@ -152,7 +153,8 @@ fn display_help() { const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new"; -static ICE_HOOK: LazyLock) + Sync + Send + 'static>> = LazyLock::new(|| { +type PanicCallback = dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static; +static ICE_HOOK: LazyLock> = LazyLock::new(|| { let hook = panic::take_hook(); panic::set_hook(Box::new(|info| report_clippy_ice(info, BUG_REPORT_URL))); hook @@ -334,15 +336,13 @@ pub fn main() { // - IF Clippy is run on the main crate, not on deps (`!cap_lints_allow`) THEN // - IF `--no-deps` is not set (`!no_deps`) OR // - IF `--no-deps` is set and Clippy is run on the specified primary package - let cap_lints_allow = arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_some(); + let cap_lints_allow = arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_some() + && arg_value(&orig_args, "--force-warn", |val| val.contains("clippy::")).is_none(); let in_primary_package = env::var("CARGO_PRIMARY_PACKAGE").is_ok(); let clippy_enabled = !cap_lints_allow && (!no_deps || in_primary_package); if clippy_enabled { args.extend(clippy_args); - } - - if clippy_enabled { rustc_driver::RunCompiler::new(&args, &mut ClippyCallbacks { clippy_args_var }).run() } else { rustc_driver::RunCompiler::new(&args, &mut RustcCallbacks { clippy_args_var }).run() diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 061cda7e01e..bf7a39edf4c 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -23,6 +23,7 @@ /// All crates used in UI tests are listed here static TEST_DEPENDENCIES: &[&str] = &[ + "clippy_lints", "clippy_utils", "derive_new", "futures", @@ -41,6 +42,8 @@ // Test dependencies may need an `extern crate` here to ensure that they show up // in the depinfo file (otherwise cargo thinks they are unused) #[allow(unused_extern_crates)] +extern crate clippy_lints; +#[allow(unused_extern_crates)] extern crate clippy_utils; #[allow(unused_extern_crates)] extern crate derive_new; @@ -124,7 +127,7 @@ fn base_config(test_dir: &str) -> compiletest::Config { let mut config = compiletest::Config { edition: Some("2021".into()), mode: TestMode::Ui, - ..compiletest::Config::default() + ..Default::default() }; if let Ok(filters) = env::var("TESTNAME") { @@ -280,6 +283,24 @@ fn run_tests( } env::set_current_dir(&src_path)?; + + let cargo_toml_path = case.path().join("Cargo.toml"); + let cargo_content = fs::read(&cargo_toml_path)?; + let cargo_parsed: toml::Value = toml::from_str( + std::str::from_utf8(&cargo_content).expect("`Cargo.toml` is not a valid utf-8 file!"), + ) + .expect("Can't parse `Cargo.toml`"); + + let _g = VarGuard::set("CARGO_MANIFEST_DIR", case.path()); + let _h = VarGuard::set( + "CARGO_PKG_RUST_VERSION", + cargo_parsed + .get("package") + .and_then(|p| p.get("rust-version")) + .and_then(toml::Value::as_str) + .unwrap_or(""), + ); + for file in fs::read_dir(&src_path)? { let file = file?; if file.file_type()?.is_dir() { 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 new file mode 100644 index 00000000000..73ec29c5803 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_both_diff/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-both-diff" +version = "0.1.0" +rust-version = "1.56" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/cargo_rust_version/fail_both_diff/clippy.toml b/tests/ui-cargo/cargo_rust_version/fail_both_diff/clippy.toml new file mode 100644 index 00000000000..abe19b3a007 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_both_diff/clippy.toml @@ -0,0 +1 @@ +msrv = "1.59" diff --git a/tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.rs b/tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.rs new file mode 100644 index 00000000000..5b91d550867 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.stderr b/tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.stderr new file mode 100644 index 00000000000..9a7d802dc6d --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.stderr @@ -0,0 +1,16 @@ +warning: the MSRV in `clippy.toml` and `Cargo.toml` differ; using `1.59.0` from `clippy.toml` + +error: unnecessary structure name repetition + --> $DIR/main.rs:6:21 + | +LL | pub fn bar() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + | +note: the lint level is defined here + --> $DIR/main.rs:1:9 + | +LL | #![deny(clippy::use_self)] + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error; 1 warning emitted + 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 new file mode 100644 index 00000000000..2d6d547e4fe --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_both_same/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-both-same" +version = "0.1.0" +rust-version = "1.57.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/cargo_rust_version/fail_both_same/clippy.toml b/tests/ui-cargo/cargo_rust_version/fail_both_same/clippy.toml new file mode 100644 index 00000000000..5cccb362c14 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_both_same/clippy.toml @@ -0,0 +1 @@ +msrv = "1.57" diff --git a/tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.rs b/tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.rs new file mode 100644 index 00000000000..5b91d550867 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.stderr b/tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.stderr new file mode 100644 index 00000000000..a280e1bacdf --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.stderr @@ -0,0 +1,14 @@ +error: unnecessary structure name repetition + --> $DIR/main.rs:6:21 + | +LL | pub fn bar() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + | +note: the lint level is defined here + --> $DIR/main.rs:1:9 + | +LL | #![deny(clippy::use_self)] + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/tests/ui-cargo/cargo_rust_version/fail_cargo/Cargo.toml b/tests/ui-cargo/cargo_rust_version/fail_cargo/Cargo.toml new file mode 100644 index 00000000000..36a53bd829d --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_cargo/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-cargo" +version = "0.1.0" +rust-version = "1.56.1" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.rs b/tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.rs new file mode 100644 index 00000000000..5b91d550867 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.stderr b/tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.stderr new file mode 100644 index 00000000000..a280e1bacdf --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.stderr @@ -0,0 +1,14 @@ +error: unnecessary structure name repetition + --> $DIR/main.rs:6:21 + | +LL | pub fn bar() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + | +note: the lint level is defined here + --> $DIR/main.rs:1:9 + | +LL | #![deny(clippy::use_self)] + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/tests/ui-cargo/cargo_rust_version/fail_clippy/Cargo.toml b/tests/ui-cargo/cargo_rust_version/fail_clippy/Cargo.toml new file mode 100644 index 00000000000..9f644a1a39a --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_clippy/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "fail-clippy" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/cargo_rust_version/fail_clippy/clippy.toml b/tests/ui-cargo/cargo_rust_version/fail_clippy/clippy.toml new file mode 100644 index 00000000000..ddbdbc1fa25 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_clippy/clippy.toml @@ -0,0 +1 @@ +msrv = "1.58" diff --git a/tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.rs b/tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.rs new file mode 100644 index 00000000000..5b91d550867 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.stderr b/tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.stderr new file mode 100644 index 00000000000..a280e1bacdf --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.stderr @@ -0,0 +1,14 @@ +error: unnecessary structure name repetition + --> $DIR/main.rs:6:21 + | +LL | pub fn bar() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + | +note: the lint level is defined here + --> $DIR/main.rs:1:9 + | +LL | #![deny(clippy::use_self)] + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + 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 new file mode 100644 index 00000000000..5380e993b29 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_file_attr/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-file-attr" +version = "0.1.0" +rust-version = "1.13" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/cargo_rust_version/fail_file_attr/clippy.toml b/tests/ui-cargo/cargo_rust_version/fail_file_attr/clippy.toml new file mode 100644 index 00000000000..ea5d806594b --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_file_attr/clippy.toml @@ -0,0 +1 @@ +msrv = "1.13.0" diff --git a/tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.rs b/tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.rs new file mode 100644 index 00000000000..bcbffa82a54 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.rs @@ -0,0 +1,16 @@ +// FIXME: this should produce a warning, because the attribute says 1.58 and the cargo.toml file +// says 1.13 + +#![feature(custom_inner_attributes)] +#![clippy::msrv = "1.58.0"] +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.stderr b/tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.stderr new file mode 100644 index 00000000000..88f6e00922b --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.stderr @@ -0,0 +1,14 @@ +error: unnecessary structure name repetition + --> $DIR/main.rs:11:21 + | +LL | pub fn bar() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + | +note: the lint level is defined here + --> $DIR/main.rs:6:9 + | +LL | #![deny(clippy::use_self)] + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + 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 new file mode 100644 index 00000000000..1f9bd8f9a84 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/pass_both_same/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-both-same" +version = "0.1.0" +rust-version = "1.13.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/cargo_rust_version/pass_both_same/clippy.toml b/tests/ui-cargo/cargo_rust_version/pass_both_same/clippy.toml new file mode 100644 index 00000000000..5e8e48b636b --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/pass_both_same/clippy.toml @@ -0,0 +1 @@ +msrv = "1.13" diff --git a/tests/ui-cargo/cargo_rust_version/pass_both_same/src/main.rs b/tests/ui-cargo/cargo_rust_version/pass_both_same/src/main.rs new file mode 100644 index 00000000000..5b91d550867 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/pass_both_same/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/tests/ui-cargo/cargo_rust_version/pass_cargo/Cargo.toml b/tests/ui-cargo/cargo_rust_version/pass_cargo/Cargo.toml new file mode 100644 index 00000000000..77538027c0f --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/pass_cargo/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-cargo" +version = "0.1.0" +rust-version = "1.13.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/cargo_rust_version/pass_cargo/src/main.rs b/tests/ui-cargo/cargo_rust_version/pass_cargo/src/main.rs new file mode 100644 index 00000000000..5b91d550867 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/pass_cargo/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/tests/ui-cargo/cargo_rust_version/pass_clippy/Cargo.toml b/tests/ui-cargo/cargo_rust_version/pass_clippy/Cargo.toml new file mode 100644 index 00000000000..9f644a1a39a --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/pass_clippy/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "fail-clippy" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/cargo_rust_version/pass_clippy/clippy.toml b/tests/ui-cargo/cargo_rust_version/pass_clippy/clippy.toml new file mode 100644 index 00000000000..5e8e48b636b --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/pass_clippy/clippy.toml @@ -0,0 +1 @@ +msrv = "1.13" diff --git a/tests/ui-cargo/cargo_rust_version/pass_clippy/src/main.rs b/tests/ui-cargo/cargo_rust_version/pass_clippy/src/main.rs new file mode 100644 index 00000000000..5b91d550867 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/pass_clippy/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} 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 new file mode 100644 index 00000000000..f0387cd90b8 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/pass_file_attr/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-file-attr" +version = "0.1.0" +rust-version = "1.59" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/cargo_rust_version/pass_file_attr/src/main.rs b/tests/ui-cargo/cargo_rust_version/pass_file_attr/src/main.rs new file mode 100644 index 00000000000..27fe4771d2d --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/pass_file_attr/src/main.rs @@ -0,0 +1,13 @@ +#![feature(custom_inner_attributes)] +#![clippy::msrv = "1.13.0"] +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} 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 new file mode 100644 index 00000000000..a19d5b33fe5 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/warn_both_diff/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "warn-both-diff" +version = "0.1.0" +rust-version = "1.56.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/cargo_rust_version/warn_both_diff/clippy.toml b/tests/ui-cargo/cargo_rust_version/warn_both_diff/clippy.toml new file mode 100644 index 00000000000..5e8e48b636b --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/warn_both_diff/clippy.toml @@ -0,0 +1 @@ +msrv = "1.13" diff --git a/tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.rs b/tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.rs new file mode 100644 index 00000000000..5b91d550867 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.stderr b/tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.stderr new file mode 100644 index 00000000000..eeae5b7b275 --- /dev/null +++ b/tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.stderr @@ -0,0 +1,4 @@ +warning: the MSRV in `clippy.toml` and `Cargo.toml` differ; using `1.13.0` from `clippy.toml` + +warning: 1 warning emitted + diff --git a/tests/ui-cargo/multiple_config_files/warn/src/main.stderr b/tests/ui-cargo/multiple_config_files/warn/src/main.stderr index 2abb4e3e06e..98697e001f9 100644 --- a/tests/ui-cargo/multiple_config_files/warn/src/main.stderr +++ b/tests/ui-cargo/multiple_config_files/warn/src/main.stderr @@ -1,2 +1,2 @@ -Using config file `$SRC_DIR/tests/ui-cargo/multiple_config_files/warn/.clippy.toml` -Warning: `$SRC_DIR/tests/ui-cargo/multiple_config_files/warn/clippy.toml` will be ignored. +Using config file `$SRC_DIR/.clippy.toml` +Warning: `$SRC_DIR/clippy.toml` will be ignored. diff --git a/tests/ui-internal/collapsible_span_lint_calls.fixed b/tests/ui-internal/collapsible_span_lint_calls.fixed index a5a6f20ddd5..9f299d7dec7 100644 --- a/tests/ui-internal/collapsible_span_lint_calls.fixed +++ b/tests/ui-internal/collapsible_span_lint_calls.fixed @@ -45,7 +45,12 @@ impl EarlyLintPass for Pass { if predicate { db.note(note_msg); } - }) + }); + + // Issue #8798 + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.help(help_msg).help(help_msg); + }); } } diff --git a/tests/ui-internal/collapsible_span_lint_calls.rs b/tests/ui-internal/collapsible_span_lint_calls.rs index 6d783aa0786..2b113f555e4 100644 --- a/tests/ui-internal/collapsible_span_lint_calls.rs +++ b/tests/ui-internal/collapsible_span_lint_calls.rs @@ -55,7 +55,12 @@ fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) { if predicate { db.note(note_msg); } - }) + }); + + // Issue #8798 + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.help(help_msg).help(help_msg); + }); } } diff --git a/tests/ui-internal/default_deprecation_reason.rs b/tests/ui-internal/default_deprecation_reason.rs new file mode 100644 index 00000000000..c8961d5e1f0 --- /dev/null +++ b/tests/ui-internal/default_deprecation_reason.rs @@ -0,0 +1,30 @@ +#![deny(clippy::internal)] +#![feature(rustc_private)] + +#[macro_use] +extern crate clippy_lints; +use clippy_lints::deprecated_lints::ClippyDeprecatedLint; + +declare_deprecated_lint! { + /// ### What it does + /// Nothing. This lint has been deprecated. + /// + /// ### Deprecation reason + /// TODO + #[clippy::version = "1.63.0"] + pub COOL_LINT_DEFAULT, + "default deprecation note" +} + +declare_deprecated_lint! { + /// ### What it does + /// Nothing. This lint has been deprecated. + /// + /// ### Deprecation reason + /// This lint has been replaced by `cooler_lint` + #[clippy::version = "1.63.0"] + pub COOL_LINT, + "this lint has been replaced by `cooler_lint`" +} + +fn main() {} diff --git a/tests/ui-internal/default_deprecation_reason.stderr b/tests/ui-internal/default_deprecation_reason.stderr new file mode 100644 index 00000000000..ca26b649f98 --- /dev/null +++ b/tests/ui-internal/default_deprecation_reason.stderr @@ -0,0 +1,22 @@ +error: the lint `COOL_LINT_DEFAULT` has the default deprecation reason + --> $DIR/default_deprecation_reason.rs:8:1 + | +LL | / declare_deprecated_lint! { +LL | | /// ### What it does +LL | | /// Nothing. This lint has been deprecated. +LL | | /// +... | +LL | | "default deprecation note" +LL | | } + | |_^ + | +note: the lint level is defined here + --> $DIR/default_deprecation_reason.rs:1:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::default_deprecation_reason)]` implied by `#[deny(clippy::internal)]` + = note: this error originates in the macro `declare_deprecated_lint` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/tests/ui/auxiliary/macro_rules.rs b/tests/ui/auxiliary/macro_rules.rs index 9f283337c7e..5bd2c2799f0 100644 --- a/tests/ui/auxiliary/macro_rules.rs +++ b/tests/ui/auxiliary/macro_rules.rs @@ -127,3 +127,11 @@ macro_rules! ptr_as_ptr_cast { $ptr as *const i32 }; } + +#[macro_export] +macro_rules! manual_rem_euclid { + () => { + let value: i32 = 5; + let _: i32 = ((value % 4) + 4) % 4; + }; +} diff --git a/tests/ui/auxiliary/proc_macro_derive.rs b/tests/ui/auxiliary/proc_macro_derive.rs index ed7b17651e6..a89a06308d0 100644 --- a/tests/ui/auxiliary/proc_macro_derive.rs +++ b/tests/ui/auxiliary/proc_macro_derive.rs @@ -72,3 +72,17 @@ fn line_wrapper() { } ) } + +#[proc_macro_derive(ExtraLifetimeDerive)] +#[allow(unused)] +pub fn extra_lifetime(_input: TokenStream) -> TokenStream { + quote!( + pub struct ExtraLifetime; + + impl<'b> ExtraLifetime { + pub fn something<'c>() -> Self { + Self + } + } + ) +} diff --git a/tests/ui/escape_analysis.rs b/tests/ui/boxed_local.rs similarity index 97% rename from tests/ui/escape_analysis.rs rename to tests/ui/boxed_local.rs index 13e2b6c7a2e..4639f00a8d8 100644 --- a/tests/ui/escape_analysis.rs +++ b/tests/ui/boxed_local.rs @@ -1,4 +1,5 @@ #![feature(box_syntax)] +#![feature(lint_reasons)] #![allow( clippy::borrowed_box, clippy::needless_pass_by_value, @@ -202,3 +203,7 @@ trait WarnTrait { fn foo(x: Box) {} } } + +fn check_expect(#[expect(clippy::boxed_local)] x: Box) { + x.foo(); +} diff --git a/tests/ui/escape_analysis.stderr b/tests/ui/boxed_local.stderr similarity index 81% rename from tests/ui/escape_analysis.stderr rename to tests/ui/boxed_local.stderr index 4a82b4419f9..9036529f39c 100644 --- a/tests/ui/escape_analysis.stderr +++ b/tests/ui/boxed_local.stderr @@ -1,5 +1,5 @@ error: local variable doesn't need to be boxed here - --> $DIR/escape_analysis.rs:40:13 + --> $DIR/boxed_local.rs:41:13 | LL | fn warn_arg(x: Box) { | ^ @@ -7,19 +7,19 @@ LL | fn warn_arg(x: Box) { = note: `-D clippy::boxed-local` implied by `-D warnings` error: local variable doesn't need to be boxed here - --> $DIR/escape_analysis.rs:131:12 + --> $DIR/boxed_local.rs:132:12 | LL | pub fn new(_needs_name: Box>) -> () {} | ^^^^^^^^^^^ error: local variable doesn't need to be boxed here - --> $DIR/escape_analysis.rs:195:44 + --> $DIR/boxed_local.rs:196:44 | LL | fn default_impl_x(self: Box, x: Box) -> u32 { | ^ error: local variable doesn't need to be boxed here - --> $DIR/escape_analysis.rs:202:16 + --> $DIR/boxed_local.rs:203:16 | LL | fn foo(x: Box) {} | ^ diff --git a/tests/ui/checked_unwrap/simple_conditionals.rs b/tests/ui/checked_unwrap/simple_conditionals.rs index ee3fdfabe9d..82dce81979f 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.rs +++ b/tests/ui/checked_unwrap/simple_conditionals.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] #![allow(clippy::if_same_then_else, clippy::branches_sharing_code)] @@ -84,3 +85,18 @@ fn main() { assert!(x.is_ok(), "{:?}", x.unwrap_err()); // ok, it's a common test pattern } + +fn check_expect() { + let x = Some(()); + if x.is_some() { + #[expect(clippy::unnecessary_unwrap)] + x.unwrap(); // unnecessary + #[expect(clippy::unnecessary_unwrap)] + x.expect("an error message"); // unnecessary + } else { + #[expect(clippy::panicking_unwrap)] + x.unwrap(); // will panic + #[expect(clippy::panicking_unwrap)] + x.expect("an error message"); // will panic + } +} diff --git a/tests/ui/checked_unwrap/simple_conditionals.stderr b/tests/ui/checked_unwrap/simple_conditionals.stderr index 34131592802..ef688274222 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.stderr +++ b/tests/ui/checked_unwrap/simple_conditionals.stderr @@ -1,5 +1,5 @@ error: called `unwrap` on `x` after checking its variant with `is_some` - --> $DIR/simple_conditionals.rs:39:9 + --> $DIR/simple_conditionals.rs:40:9 | LL | if x.is_some() { | -------------- help: try: `if let Some(..) = x` @@ -7,13 +7,13 @@ LL | x.unwrap(); // unnecessary | ^^^^^^^^^^ | note: the lint level is defined here - --> $DIR/simple_conditionals.rs:1:35 + --> $DIR/simple_conditionals.rs:2:35 | LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: called `expect` on `x` after checking its variant with `is_some` - --> $DIR/simple_conditionals.rs:40:9 + --> $DIR/simple_conditionals.rs:41:9 | LL | if x.is_some() { | -------------- help: try: `if let Some(..) = x` @@ -22,7 +22,7 @@ LL | x.expect("an error message"); // unnecessary | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> $DIR/simple_conditionals.rs:42:9 + --> $DIR/simple_conditionals.rs:43:9 | LL | if x.is_some() { | ----------- because of this check @@ -31,13 +31,13 @@ LL | x.unwrap(); // will panic | ^^^^^^^^^^ | note: the lint level is defined here - --> $DIR/simple_conditionals.rs:1:9 + --> $DIR/simple_conditionals.rs:2:9 | LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `expect()` will always panic - --> $DIR/simple_conditionals.rs:43:9 + --> $DIR/simple_conditionals.rs:44:9 | LL | if x.is_some() { | ----------- because of this check @@ -46,7 +46,7 @@ LL | x.expect("an error message"); // will panic | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> $DIR/simple_conditionals.rs:46:9 + --> $DIR/simple_conditionals.rs:47:9 | LL | if x.is_none() { | ----------- because of this check @@ -54,7 +54,7 @@ LL | x.unwrap(); // will panic | ^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_none` - --> $DIR/simple_conditionals.rs:48:9 + --> $DIR/simple_conditionals.rs:49:9 | LL | if x.is_none() { | -------------- help: try: `if let Some(..) = x` @@ -63,7 +63,7 @@ LL | x.unwrap(); // unnecessary | ^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_some` - --> $DIR/simple_conditionals.rs:7:13 + --> $DIR/simple_conditionals.rs:8:13 | LL | if $a.is_some() { | --------------- help: try: `if let Some(..) = x` @@ -76,7 +76,7 @@ LL | m!(x); = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: called `unwrap` on `x` after checking its variant with `is_ok` - --> $DIR/simple_conditionals.rs:56:9 + --> $DIR/simple_conditionals.rs:57:9 | LL | if x.is_ok() { | ------------ help: try: `if let Ok(..) = x` @@ -84,7 +84,7 @@ LL | x.unwrap(); // unnecessary | ^^^^^^^^^^ error: called `expect` on `x` after checking its variant with `is_ok` - --> $DIR/simple_conditionals.rs:57:9 + --> $DIR/simple_conditionals.rs:58:9 | LL | if x.is_ok() { | ------------ help: try: `if let Ok(..) = x` @@ -93,7 +93,7 @@ LL | x.expect("an error message"); // unnecessary | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap_err()` will always panic - --> $DIR/simple_conditionals.rs:58:9 + --> $DIR/simple_conditionals.rs:59:9 | LL | if x.is_ok() { | --------- because of this check @@ -102,7 +102,7 @@ LL | x.unwrap_err(); // will panic | ^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> $DIR/simple_conditionals.rs:60:9 + --> $DIR/simple_conditionals.rs:61:9 | LL | if x.is_ok() { | --------- because of this check @@ -111,7 +111,7 @@ LL | x.unwrap(); // will panic | ^^^^^^^^^^ error: this call to `expect()` will always panic - --> $DIR/simple_conditionals.rs:61:9 + --> $DIR/simple_conditionals.rs:62:9 | LL | if x.is_ok() { | --------- because of this check @@ -120,7 +120,7 @@ LL | x.expect("an error message"); // will panic | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap_err` on `x` after checking its variant with `is_ok` - --> $DIR/simple_conditionals.rs:62:9 + --> $DIR/simple_conditionals.rs:63:9 | LL | if x.is_ok() { | ------------ help: try: `if let Err(..) = x` @@ -129,7 +129,7 @@ LL | x.unwrap_err(); // unnecessary | ^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> $DIR/simple_conditionals.rs:65:9 + --> $DIR/simple_conditionals.rs:66:9 | LL | if x.is_err() { | ---------- because of this check @@ -137,7 +137,7 @@ LL | x.unwrap(); // will panic | ^^^^^^^^^^ error: called `unwrap_err` on `x` after checking its variant with `is_err` - --> $DIR/simple_conditionals.rs:66:9 + --> $DIR/simple_conditionals.rs:67:9 | LL | if x.is_err() { | ------------- help: try: `if let Err(..) = x` @@ -146,7 +146,7 @@ LL | x.unwrap_err(); // unnecessary | ^^^^^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_err` - --> $DIR/simple_conditionals.rs:68:9 + --> $DIR/simple_conditionals.rs:69:9 | LL | if x.is_err() { | ------------- help: try: `if let Ok(..) = x` @@ -155,7 +155,7 @@ LL | x.unwrap(); // unnecessary | ^^^^^^^^^^ error: this call to `unwrap_err()` will always panic - --> $DIR/simple_conditionals.rs:69:9 + --> $DIR/simple_conditionals.rs:70:9 | LL | if x.is_err() { | ---------- because of this check diff --git a/tests/ui/declare_interior_mutable_const/others.rs b/tests/ui/declare_interior_mutable_const/others.rs index 48c5e9537d6..62af545db50 100644 --- a/tests/ui/declare_interior_mutable_const/others.rs +++ b/tests/ui/declare_interior_mutable_const/others.rs @@ -31,4 +31,9 @@ macro_rules! declare_const { 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) }; +} + fn main() {} diff --git a/tests/ui/default_instead_of_iter_empty.fixed b/tests/ui/default_instead_of_iter_empty.fixed new file mode 100644 index 00000000000..f1abfdcd6ce --- /dev/null +++ b/tests/ui/default_instead_of_iter_empty.fixed @@ -0,0 +1,21 @@ +// run-rustfix +#![warn(clippy::default_instead_of_iter_empty)] +#![allow(dead_code)] +use std::collections::HashMap; + +#[derive(Default)] +struct Iter { + iter: std::iter::Empty, +} + +fn main() { + // Do lint. + let _ = std::iter::empty::(); + let _ = std::iter::empty::>(); + let _foo: std::iter::Empty = std::iter::empty(); + + // Do not lint. + let _ = Vec::::default(); + let _ = String::default(); + let _ = Iter::default(); +} diff --git a/tests/ui/default_instead_of_iter_empty.rs b/tests/ui/default_instead_of_iter_empty.rs new file mode 100644 index 00000000000..2630519c46d --- /dev/null +++ b/tests/ui/default_instead_of_iter_empty.rs @@ -0,0 +1,21 @@ +// run-rustfix +#![warn(clippy::default_instead_of_iter_empty)] +#![allow(dead_code)] +use std::collections::HashMap; + +#[derive(Default)] +struct Iter { + iter: std::iter::Empty, +} + +fn main() { + // Do lint. + let _ = std::iter::Empty::::default(); + let _ = std::iter::Empty::>::default(); + let _foo: std::iter::Empty = std::iter::Empty::default(); + + // Do not lint. + let _ = Vec::::default(); + let _ = String::default(); + let _ = Iter::default(); +} diff --git a/tests/ui/default_instead_of_iter_empty.stderr b/tests/ui/default_instead_of_iter_empty.stderr new file mode 100644 index 00000000000..460fc84def8 --- /dev/null +++ b/tests/ui/default_instead_of_iter_empty.stderr @@ -0,0 +1,22 @@ +error: `std::iter::empty()` is the more idiomatic way + --> $DIR/default_instead_of_iter_empty.rs:13:13 + | +LL | let _ = std::iter::Empty::::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::iter::empty::()` + | + = note: `-D clippy::default-instead-of-iter-empty` implied by `-D warnings` + +error: `std::iter::empty()` is the more idiomatic way + --> $DIR/default_instead_of_iter_empty.rs:14:13 + | +LL | let _ = std::iter::Empty::>::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::iter::empty::>()` + +error: `std::iter::empty()` is the more idiomatic way + --> $DIR/default_instead_of_iter_empty.rs:15:41 + | +LL | let _foo: std::iter::Empty = std::iter::Empty::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::iter::empty()` + +error: aborting due to 3 previous errors + diff --git a/tests/ui/enum_variants.rs b/tests/ui/enum_variants.rs index b2bf7c4e360..efed12ee2ef 100644 --- a/tests/ui/enum_variants.rs +++ b/tests/ui/enum_variants.rs @@ -158,4 +158,25 @@ enum Phase { PostLookup, } +mod issue9018 { + enum DoLint { + _TypeCreate, + _TypeRead, + _TypeUpdate, + _TypeDestroy, + } + + enum DoLintToo { + _CreateType, + _UpdateType, + _DeleteType, + } + + enum DoNotLint { + _Foo, + _Bar, + _Baz, + } +} + fn main() {} diff --git a/tests/ui/enum_variants.stderr b/tests/ui/enum_variants.stderr index 8a3265086e8..7342aff80f0 100644 --- a/tests/ui/enum_variants.stderr +++ b/tests/ui/enum_variants.stderr @@ -120,5 +120,30 @@ LL | | } | = help: remove the postfixes and use full paths to the variants instead of glob imports -error: aborting due to 12 previous errors +error: all variants have the same prefix: `_Type` + --> $DIR/enum_variants.rs:162:5 + | +LL | / enum DoLint { +LL | | _TypeCreate, +LL | | _TypeRead, +LL | | _TypeUpdate, +LL | | _TypeDestroy, +LL | | } + | |_____^ + | + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: all variants have the same postfix: `Type` + --> $DIR/enum_variants.rs:169:5 + | +LL | / enum DoLintToo { +LL | | _CreateType, +LL | | _UpdateType, +LL | | _DeleteType, +LL | | } + | |_____^ + | + = help: remove the postfixes and use full paths to the variants instead of glob imports + +error: aborting due to 14 previous errors diff --git a/tests/ui/explicit_auto_deref.fixed b/tests/ui/explicit_auto_deref.fixed new file mode 100644 index 00000000000..d4ff1b1566d --- /dev/null +++ b/tests/ui/explicit_auto_deref.fixed @@ -0,0 +1,214 @@ +// run-rustfix + +#![warn(clippy::explicit_auto_deref)] +#![allow( + dead_code, + unused_braces, + clippy::borrowed_box, + clippy::needless_borrow, + clippy::needless_return, + clippy::ptr_arg, + clippy::redundant_field_names, + clippy::too_many_arguments, + clippy::borrow_deref_ref, + clippy::let_unit_value +)] + +trait CallableStr { + type T: Fn(&str); + fn callable_str(&self) -> Self::T; +} +impl CallableStr for () { + type T = fn(&str); + fn callable_str(&self) -> Self::T { + fn f(_: &str) {} + f + } +} +impl CallableStr for i32 { + type T = <() as CallableStr>::T; + fn callable_str(&self) -> Self::T { + ().callable_str() + } +} + +trait CallableT { + type T: Fn(&U); + fn callable_t(&self) -> Self::T; +} +impl CallableT for () { + type T = fn(&U); + fn callable_t(&self) -> Self::T { + fn f(_: &U) {} + f:: + } +} +impl CallableT for i32 { + type T = <() as CallableT>::T; + fn callable_t(&self) -> Self::T { + ().callable_t() + } +} + +fn f_str(_: &str) {} +fn f_string(_: &String) {} +fn f_t(_: T) {} +fn f_ref_t(_: &T) {} + +fn f_str_t(_: &str, _: T) {} + +fn f_box_t(_: &Box) {} + +extern "C" { + fn var(_: u32, ...); +} + +fn main() { + let s = String::new(); + + let _: &str = &s; + let _ = &*s; // Don't lint. Inferred type would change. + let _: &_ = &*s; // Don't lint. Inferred type would change. + + f_str(&s); + f_t(&*s); // Don't lint. Inferred type would change. + f_ref_t(&*s); // Don't lint. Inferred type would change. + + f_str_t(&s, &*s); // Don't lint second param. + + let b = Box::new(Box::new(Box::new(5))); + let _: &Box = &b; + let _: &Box<_> = &**b; // Don't lint. Inferred type would change. + + f_box_t(&**b); // Don't lint. Inferred type would change. + + let c = |_x: &str| (); + c(&s); + + let c = |_x| (); + c(&*s); // Don't lint. Inferred type would change. + + fn _f(x: &String) -> &str { + x + } + + fn _f1(x: &String) -> &str { + { x } + } + + fn _f2(x: &String) -> &str { + { x } + } + + fn _f3(x: &Box>>) -> &Box { + x + } + + fn _f4( + x: String, + f1: impl Fn(&str), + f2: &dyn Fn(&str), + f3: fn(&str), + f4: impl CallableStr, + f5: <() as CallableStr>::T, + f6: ::T, + f7: &dyn CallableStr, + f8: impl CallableT, + f9: <() as CallableT>::T, + f10: >::T, + f11: &dyn CallableT, + ) { + f1(&x); + f2(&x); + f3(&x); + f4.callable_str()(&x); + f5(&x); + f6(&x); + f7.callable_str()(&x); + f8.callable_t()(&x); + f9(&x); + f10(&x); + f11.callable_t()(&x); + } + + struct S1<'a>(&'a str); + let _ = S1(&s); + + struct S2<'a> { + s: &'a str, + } + let _ = S2 { s: &s }; + + struct S3<'a, T: ?Sized>(&'a T); + let _ = S3(&*s); // Don't lint. Inferred type would change. + + struct S4<'a, T: ?Sized> { + s: &'a T, + } + let _ = S4 { s: &*s }; // Don't lint. Inferred type would change. + + enum E1<'a> { + S1(&'a str), + S2 { s: &'a str }, + } + impl<'a> E1<'a> { + fn m1(s: &'a String) { + let _ = Self::S1(s); + let _ = Self::S2 { s: s }; + } + } + let _ = E1::S1(&s); + let _ = E1::S2 { s: &s }; + + enum E2<'a, T: ?Sized> { + S1(&'a T), + S2 { s: &'a T }, + } + let _ = E2::S1(&*s); // Don't lint. Inferred type would change. + let _ = E2::S2 { s: &*s }; // Don't lint. Inferred type would change. + + let ref_s = &s; + let _: &String = &*ref_s; // Don't lint reborrow. + f_string(&*ref_s); // Don't lint reborrow. + + struct S5 { + foo: u32, + } + let b = Box::new(Box::new(S5 { foo: 5 })); + let _ = b.foo; + let _ = b.foo; + let _ = b.foo; + + struct S6 { + foo: S5, + } + impl core::ops::Deref for S6 { + type Target = S5; + fn deref(&self) -> &Self::Target { + &self.foo + } + } + let s6 = S6 { foo: S5 { foo: 5 } }; + let _ = (*s6).foo; // Don't lint. `S6` also has a field named `foo` + + let ref_str = &"foo"; + let _ = f_str(ref_str); + let ref_ref_str = &ref_str; + let _ = f_str(ref_ref_str); + + fn _f5(x: &u32) -> u32 { + if true { + *x + } else { + return *x; + } + } + + f_str(&&ref_str); // `needless_borrow` will suggest removing both references + f_str(&ref_str); // `needless_borrow` will suggest removing only one reference + + let x = &&40; + unsafe { + var(0, &**x); + } +} diff --git a/tests/ui/explicit_auto_deref.rs b/tests/ui/explicit_auto_deref.rs new file mode 100644 index 00000000000..99294a7947b --- /dev/null +++ b/tests/ui/explicit_auto_deref.rs @@ -0,0 +1,214 @@ +// run-rustfix + +#![warn(clippy::explicit_auto_deref)] +#![allow( + dead_code, + unused_braces, + clippy::borrowed_box, + clippy::needless_borrow, + clippy::needless_return, + clippy::ptr_arg, + clippy::redundant_field_names, + clippy::too_many_arguments, + clippy::borrow_deref_ref, + clippy::let_unit_value +)] + +trait CallableStr { + type T: Fn(&str); + fn callable_str(&self) -> Self::T; +} +impl CallableStr for () { + type T = fn(&str); + fn callable_str(&self) -> Self::T { + fn f(_: &str) {} + f + } +} +impl CallableStr for i32 { + type T = <() as CallableStr>::T; + fn callable_str(&self) -> Self::T { + ().callable_str() + } +} + +trait CallableT { + type T: Fn(&U); + fn callable_t(&self) -> Self::T; +} +impl CallableT for () { + type T = fn(&U); + fn callable_t(&self) -> Self::T { + fn f(_: &U) {} + f:: + } +} +impl CallableT for i32 { + type T = <() as CallableT>::T; + fn callable_t(&self) -> Self::T { + ().callable_t() + } +} + +fn f_str(_: &str) {} +fn f_string(_: &String) {} +fn f_t(_: T) {} +fn f_ref_t(_: &T) {} + +fn f_str_t(_: &str, _: T) {} + +fn f_box_t(_: &Box) {} + +extern "C" { + fn var(_: u32, ...); +} + +fn main() { + let s = String::new(); + + let _: &str = &*s; + let _ = &*s; // Don't lint. Inferred type would change. + let _: &_ = &*s; // Don't lint. Inferred type would change. + + f_str(&*s); + f_t(&*s); // Don't lint. Inferred type would change. + f_ref_t(&*s); // Don't lint. Inferred type would change. + + f_str_t(&*s, &*s); // Don't lint second param. + + let b = Box::new(Box::new(Box::new(5))); + let _: &Box = &**b; + let _: &Box<_> = &**b; // Don't lint. Inferred type would change. + + f_box_t(&**b); // Don't lint. Inferred type would change. + + let c = |_x: &str| (); + c(&*s); + + let c = |_x| (); + c(&*s); // Don't lint. Inferred type would change. + + fn _f(x: &String) -> &str { + &**x + } + + fn _f1(x: &String) -> &str { + { &**x } + } + + fn _f2(x: &String) -> &str { + &**{ x } + } + + fn _f3(x: &Box>>) -> &Box { + &***x + } + + fn _f4( + x: String, + f1: impl Fn(&str), + f2: &dyn Fn(&str), + f3: fn(&str), + f4: impl CallableStr, + f5: <() as CallableStr>::T, + f6: ::T, + f7: &dyn CallableStr, + f8: impl CallableT, + f9: <() as CallableT>::T, + f10: >::T, + f11: &dyn CallableT, + ) { + f1(&*x); + f2(&*x); + f3(&*x); + f4.callable_str()(&*x); + f5(&*x); + f6(&*x); + f7.callable_str()(&*x); + f8.callable_t()(&*x); + f9(&*x); + f10(&*x); + f11.callable_t()(&*x); + } + + struct S1<'a>(&'a str); + let _ = S1(&*s); + + struct S2<'a> { + s: &'a str, + } + let _ = S2 { s: &*s }; + + struct S3<'a, T: ?Sized>(&'a T); + let _ = S3(&*s); // Don't lint. Inferred type would change. + + struct S4<'a, T: ?Sized> { + s: &'a T, + } + let _ = S4 { s: &*s }; // Don't lint. Inferred type would change. + + enum E1<'a> { + S1(&'a str), + S2 { s: &'a str }, + } + impl<'a> E1<'a> { + fn m1(s: &'a String) { + let _ = Self::S1(&**s); + let _ = Self::S2 { s: &**s }; + } + } + let _ = E1::S1(&*s); + let _ = E1::S2 { s: &*s }; + + enum E2<'a, T: ?Sized> { + S1(&'a T), + S2 { s: &'a T }, + } + let _ = E2::S1(&*s); // Don't lint. Inferred type would change. + let _ = E2::S2 { s: &*s }; // Don't lint. Inferred type would change. + + let ref_s = &s; + let _: &String = &*ref_s; // Don't lint reborrow. + f_string(&*ref_s); // Don't lint reborrow. + + struct S5 { + foo: u32, + } + let b = Box::new(Box::new(S5 { foo: 5 })); + let _ = b.foo; + let _ = (*b).foo; + let _ = (**b).foo; + + struct S6 { + foo: S5, + } + impl core::ops::Deref for S6 { + type Target = S5; + fn deref(&self) -> &Self::Target { + &self.foo + } + } + let s6 = S6 { foo: S5 { foo: 5 } }; + let _ = (*s6).foo; // Don't lint. `S6` also has a field named `foo` + + let ref_str = &"foo"; + let _ = f_str(*ref_str); + let ref_ref_str = &ref_str; + let _ = f_str(**ref_ref_str); + + fn _f5(x: &u32) -> u32 { + if true { + *x + } else { + return *x; + } + } + + f_str(&&*ref_str); // `needless_borrow` will suggest removing both references + f_str(&&**ref_str); // `needless_borrow` will suggest removing only one reference + + let x = &&40; + unsafe { + var(0, &**x); + } +} diff --git a/tests/ui/explicit_auto_deref.stderr b/tests/ui/explicit_auto_deref.stderr new file mode 100644 index 00000000000..55f956e37ae --- /dev/null +++ b/tests/ui/explicit_auto_deref.stderr @@ -0,0 +1,196 @@ +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:69:20 + | +LL | let _: &str = &*s; + | ^^ help: try this: `s` + | + = note: `-D clippy::explicit-auto-deref` implied by `-D warnings` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:73:12 + | +LL | f_str(&*s); + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:77:14 + | +LL | f_str_t(&*s, &*s); // Don't lint second param. + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:80:25 + | +LL | let _: &Box = &**b; + | ^^^ help: try this: `b` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:86:8 + | +LL | c(&*s); + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:92:9 + | +LL | &**x + | ^^^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:96:11 + | +LL | { &**x } + | ^^^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:100:9 + | +LL | &**{ x } + | ^^^^^^^^ help: try this: `{ x }` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:104:9 + | +LL | &***x + | ^^^^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:121:13 + | +LL | f1(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:122:13 + | +LL | f2(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:123:13 + | +LL | f3(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:124:28 + | +LL | f4.callable_str()(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:125:13 + | +LL | f5(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:126:13 + | +LL | f6(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:127:28 + | +LL | f7.callable_str()(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:128:26 + | +LL | f8.callable_t()(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:129:13 + | +LL | f9(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:130:14 + | +LL | f10(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:131:27 + | +LL | f11.callable_t()(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:135:17 + | +LL | let _ = S1(&*s); + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:140:22 + | +LL | let _ = S2 { s: &*s }; + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:156:30 + | +LL | let _ = Self::S1(&**s); + | ^^^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:157:35 + | +LL | let _ = Self::S2 { s: &**s }; + | ^^^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:160:21 + | +LL | let _ = E1::S1(&*s); + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:161:26 + | +LL | let _ = E1::S2 { s: &*s }; + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:179:13 + | +LL | let _ = (*b).foo; + | ^^^^ help: try this: `b` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:180:13 + | +LL | let _ = (**b).foo; + | ^^^^^ help: try this: `b` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:195:19 + | +LL | let _ = f_str(*ref_str); + | ^^^^^^^^ help: try this: `ref_str` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:197:19 + | +LL | let _ = f_str(**ref_ref_str); + | ^^^^^^^^^^^^^ help: try this: `ref_ref_str` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:207:13 + | +LL | f_str(&&*ref_str); // `needless_borrow` will suggest removing both references + | ^^^^^^^^ help: try this: `ref_str` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:208:12 + | +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 + diff --git a/tests/ui/explicit_deref_methods.fixed b/tests/ui/explicit_deref_methods.fixed index 92f27e68549..523cae183ee 100644 --- a/tests/ui/explicit_deref_methods.fixed +++ b/tests/ui/explicit_deref_methods.fixed @@ -4,7 +4,8 @@ unused_variables, clippy::clone_double_ref, clippy::needless_borrow, - clippy::borrow_deref_ref + clippy::borrow_deref_ref, + clippy::explicit_auto_deref )] #![warn(clippy::explicit_deref_methods)] diff --git a/tests/ui/explicit_deref_methods.rs b/tests/ui/explicit_deref_methods.rs index d118607f992..0bbc1ae57cd 100644 --- a/tests/ui/explicit_deref_methods.rs +++ b/tests/ui/explicit_deref_methods.rs @@ -4,7 +4,8 @@ unused_variables, clippy::clone_double_ref, clippy::needless_borrow, - clippy::borrow_deref_ref + clippy::borrow_deref_ref, + clippy::explicit_auto_deref )] #![warn(clippy::explicit_deref_methods)] diff --git a/tests/ui/explicit_deref_methods.stderr b/tests/ui/explicit_deref_methods.stderr index 8e8b358972b..4b10ed1377b 100644 --- a/tests/ui/explicit_deref_methods.stderr +++ b/tests/ui/explicit_deref_methods.stderr @@ -1,5 +1,5 @@ error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:35:19 + --> $DIR/explicit_deref_methods.rs:36:19 | LL | let b: &str = a.deref(); | ^^^^^^^^^ help: try this: `&*a` @@ -7,67 +7,67 @@ LL | let b: &str = a.deref(); = note: `-D clippy::explicit-deref-methods` implied by `-D warnings` error: explicit `deref_mut` method call - --> $DIR/explicit_deref_methods.rs:37:23 + --> $DIR/explicit_deref_methods.rs:38:23 | LL | let b: &mut str = a.deref_mut(); | ^^^^^^^^^^^^^ help: try this: `&mut **a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:40:39 + --> $DIR/explicit_deref_methods.rs:41:39 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try this: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:40:50 + --> $DIR/explicit_deref_methods.rs:41:50 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try this: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:42:20 + --> $DIR/explicit_deref_methods.rs:43:20 | LL | println!("{}", a.deref()); | ^^^^^^^^^ help: try this: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:45:11 + --> $DIR/explicit_deref_methods.rs:46:11 | LL | match a.deref() { | ^^^^^^^^^ help: try this: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:49:28 + --> $DIR/explicit_deref_methods.rs:50:28 | LL | let b: String = concat(a.deref()); | ^^^^^^^^^ help: try this: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:51:13 + --> $DIR/explicit_deref_methods.rs:52:13 | LL | let b = just_return(a).deref(); | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `just_return(a)` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:53:28 + --> $DIR/explicit_deref_methods.rs:54:28 | LL | let b: String = concat(just_return(a).deref()); | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `just_return(a)` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:55:19 + --> $DIR/explicit_deref_methods.rs:56:19 | LL | let b: &str = a.deref().deref(); | ^^^^^^^^^^^^^^^^^ help: try this: `&**a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:58:13 + --> $DIR/explicit_deref_methods.rs:59:13 | LL | let b = opt_a.unwrap().deref(); | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&*opt_a.unwrap()` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:84:31 + --> $DIR/explicit_deref_methods.rs:85:31 | LL | let b: &str = expr_deref!(a.deref()); | ^^^^^^^^^ help: try this: `&*a` diff --git a/tests/ui/extra_unused_lifetimes.rs b/tests/ui/extra_unused_lifetimes.rs index f76127a7105..d6631e01290 100644 --- a/tests/ui/extra_unused_lifetimes.rs +++ b/tests/ui/extra_unused_lifetimes.rs @@ -1,3 +1,5 @@ +// aux-build:proc_macro_derive.rs + #![allow( unused, dead_code, @@ -7,6 +9,9 @@ )] #![warn(clippy::extra_unused_lifetimes)] +#[macro_use] +extern crate proc_macro_derive; + fn empty() {} fn used_lt<'a>(x: &'a u8) {} @@ -114,4 +119,11 @@ fn hey() {} } } +// Should not lint +#[derive(ExtraLifetimeDerive)] +struct Human<'a> { + pub bones: i32, + pub name: &'a str, +} + fn main() {} diff --git a/tests/ui/extra_unused_lifetimes.stderr b/tests/ui/extra_unused_lifetimes.stderr index fcc12d4ce14..26ebc3976df 100644 --- a/tests/ui/extra_unused_lifetimes.stderr +++ b/tests/ui/extra_unused_lifetimes.stderr @@ -1,5 +1,5 @@ error: this lifetime isn't used in the function definition - --> $DIR/extra_unused_lifetimes.rs:14:14 + --> $DIR/extra_unused_lifetimes.rs:19:14 | LL | fn unused_lt<'a>(x: u8) {} | ^^ @@ -7,31 +7,31 @@ LL | fn unused_lt<'a>(x: u8) {} = note: `-D clippy::extra-unused-lifetimes` implied by `-D warnings` error: this lifetime isn't used in the function definition - --> $DIR/extra_unused_lifetimes.rs:41:10 + --> $DIR/extra_unused_lifetimes.rs:46:10 | LL | fn x<'a>(&self) {} | ^^ error: this lifetime isn't used in the function definition - --> $DIR/extra_unused_lifetimes.rs:67:22 + --> $DIR/extra_unused_lifetimes.rs:72:22 | LL | fn unused_lt<'a>(x: u8) {} | ^^ error: this lifetime isn't used in the impl - --> $DIR/extra_unused_lifetimes.rs:78:10 + --> $DIR/extra_unused_lifetimes.rs:83:10 | LL | impl<'a> std::ops::AddAssign<&Scalar> for &mut Scalar { | ^^ error: this lifetime isn't used in the impl - --> $DIR/extra_unused_lifetimes.rs:84:10 + --> $DIR/extra_unused_lifetimes.rs:89:10 | LL | impl<'b> Scalar { | ^^ error: this lifetime isn't used in the function definition - --> $DIR/extra_unused_lifetimes.rs:85:26 + --> $DIR/extra_unused_lifetimes.rs:90:26 | LL | pub fn something<'c>() -> Self { | ^^ diff --git a/tests/ui/if_same_then_else.rs b/tests/ui/if_same_then_else.rs index ef956745500..2598c2ab426 100644 --- a/tests/ui/if_same_then_else.rs +++ b/tests/ui/if_same_then_else.rs @@ -6,7 +6,9 @@ clippy::no_effect, clippy::unused_unit, clippy::zero_divided_by_zero, - clippy::branches_sharing_code + clippy::branches_sharing_code, + dead_code, + unreachable_code )] struct Foo { @@ -155,4 +157,61 @@ fn mul_not_always_commutative(x: i32, y: i32) -> i32 { } } +mod issue_8836 { + fn do_not_lint() { + if true { + todo!() + } else { + todo!() + } + if true { + todo!(); + } else { + todo!(); + } + if true { + unimplemented!() + } else { + unimplemented!() + } + if true { + unimplemented!(); + } else { + unimplemented!(); + } + + if true { + println!("FOO"); + todo!(); + } else { + println!("FOO"); + todo!(); + } + + if true { + println!("FOO"); + unimplemented!(); + } else { + println!("FOO"); + unimplemented!(); + } + + if true { + println!("FOO"); + todo!() + } else { + println!("FOO"); + todo!() + } + + if true { + println!("FOO"); + unimplemented!() + } else { + println!("FOO"); + unimplemented!() + } + } +} + fn main() {} diff --git a/tests/ui/if_same_then_else.stderr b/tests/ui/if_same_then_else.stderr index 2f38052fc20..2cdf442486a 100644 --- a/tests/ui/if_same_then_else.stderr +++ b/tests/ui/if_same_then_else.stderr @@ -1,5 +1,5 @@ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:21:13 + --> $DIR/if_same_then_else.rs:23:13 | LL | if true { | _____________^ @@ -13,7 +13,7 @@ LL | | } else { | = note: `-D clippy::if-same-then-else` implied by `-D warnings` note: same as this - --> $DIR/if_same_then_else.rs:29:12 + --> $DIR/if_same_then_else.rs:31:12 | LL | } else { | ____________^ @@ -26,7 +26,7 @@ LL | | } | |_____^ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:65:21 + --> $DIR/if_same_then_else.rs:67:21 | LL | let _ = if true { | _____________________^ @@ -35,7 +35,7 @@ LL | | } else { | |_____^ | note: same as this - --> $DIR/if_same_then_else.rs:67:12 + --> $DIR/if_same_then_else.rs:69:12 | LL | } else { | ____________^ @@ -45,7 +45,7 @@ LL | | }; | |_____^ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:72:21 + --> $DIR/if_same_then_else.rs:74:21 | LL | let _ = if true { | _____________________^ @@ -54,7 +54,7 @@ LL | | } else { | |_____^ | note: same as this - --> $DIR/if_same_then_else.rs:74:12 + --> $DIR/if_same_then_else.rs:76:12 | LL | } else { | ____________^ @@ -64,7 +64,7 @@ LL | | }; | |_____^ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:88:21 + --> $DIR/if_same_then_else.rs:90:21 | LL | let _ = if true { | _____________________^ @@ -73,7 +73,7 @@ LL | | } else { | |_____^ | note: same as this - --> $DIR/if_same_then_else.rs:90:12 + --> $DIR/if_same_then_else.rs:92:12 | LL | } else { | ____________^ @@ -83,7 +83,7 @@ LL | | }; | |_____^ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:95:13 + --> $DIR/if_same_then_else.rs:97:13 | LL | if true { | _____________^ @@ -96,7 +96,7 @@ LL | | } else { | |_____^ | note: same as this - --> $DIR/if_same_then_else.rs:102:12 + --> $DIR/if_same_then_else.rs:104:12 | LL | } else { | ____________^ diff --git a/tests/ui/implicit_return.fixed b/tests/ui/implicit_return.fixed index a51f7bc6a29..5e55b8b6739 100644 --- a/tests/ui/implicit_return.fixed +++ b/tests/ui/implicit_return.fixed @@ -1,5 +1,5 @@ // run-rustfix - +#![feature(lint_reasons)] #![warn(clippy::implicit_return)] #![allow(clippy::needless_return, clippy::needless_bool, unused, clippy::never_loop)] @@ -128,3 +128,13 @@ async fn foo() -> bool { } fn main() {} + +fn check_expect() -> bool { + if true { + // no error! + return true; + } + + #[expect(clippy::implicit_return)] + true +} diff --git a/tests/ui/implicit_return.rs b/tests/ui/implicit_return.rs index 03f8ec49d51..76f0a980352 100644 --- a/tests/ui/implicit_return.rs +++ b/tests/ui/implicit_return.rs @@ -1,5 +1,5 @@ // run-rustfix - +#![feature(lint_reasons)] #![warn(clippy::implicit_return)] #![allow(clippy::needless_return, clippy::needless_bool, unused, clippy::never_loop)] @@ -128,3 +128,13 @@ async fn foo() -> bool { } fn main() {} + +fn check_expect() -> bool { + if true { + // no error! + return true; + } + + #[expect(clippy::implicit_return)] + true +} diff --git a/tests/ui/let_underscore_lock.rs b/tests/ui/let_underscore_lock.rs index 539d74d1d4c..7a7c4e92499 100644 --- a/tests/ui/let_underscore_lock.rs +++ b/tests/ui/let_underscore_lock.rs @@ -13,6 +13,10 @@ fn main() { let _ = rw.try_read(); let _ = rw.try_write(); + // These shouldn't throw an error. + let _ = m; + let _ = rw; + use parking_lot::{lock_api::RawMutex, Mutex, RwLock}; let p_m: Mutex<()> = Mutex::const_new(RawMutex::INIT, ()); @@ -24,4 +28,9 @@ fn main() { let p_rw = RwLock::new(0); let _ = p_rw.read(); let _ = p_rw.write(); + + // These shouldn't throw an error. + let _ = p_m; + let _ = p_m1; + let _ = p_rw; } diff --git a/tests/ui/let_underscore_lock.stderr b/tests/ui/let_underscore_lock.stderr index 3a2bc17bf7b..4365b48fabb 100644 --- a/tests/ui/let_underscore_lock.stderr +++ b/tests/ui/let_underscore_lock.stderr @@ -48,7 +48,7 @@ LL | let _ = rw.try_write(); = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` error: non-binding let on a synchronization lock - --> $DIR/let_underscore_lock.rs:19:5 + --> $DIR/let_underscore_lock.rs:23:5 | LL | let _ = p_m.lock(); | ^^^^^^^^^^^^^^^^^^^ @@ -56,7 +56,7 @@ LL | let _ = p_m.lock(); = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` error: non-binding let on a synchronization lock - --> $DIR/let_underscore_lock.rs:22:5 + --> $DIR/let_underscore_lock.rs:26:5 | LL | let _ = p_m1.lock(); | ^^^^^^^^^^^^^^^^^^^^ @@ -64,7 +64,7 @@ LL | let _ = p_m1.lock(); = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` error: non-binding let on a synchronization lock - --> $DIR/let_underscore_lock.rs:25:5 + --> $DIR/let_underscore_lock.rs:29:5 | LL | let _ = p_rw.read(); | ^^^^^^^^^^^^^^^^^^^^ @@ -72,7 +72,7 @@ LL | let _ = p_rw.read(); = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` error: non-binding let on a synchronization lock - --> $DIR/let_underscore_lock.rs:26:5 + --> $DIR/let_underscore_lock.rs:30:5 | LL | let _ = p_rw.write(); | ^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/logic_bug.rs b/tests/ui/logic_bug.rs index 4eaa2dd98eb..dd6b1db5f70 100644 --- a/tests/ui/logic_bug.rs +++ b/tests/ui/logic_bug.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![allow(unused, clippy::diverging_sub_expression)] #![warn(clippy::logic_bug)] @@ -24,3 +25,10 @@ fn equality_stuff() { let _ = a > b && a <= b; let _ = a > b && a == b; } + +fn check_expect() { + let a: i32 = unimplemented!(); + let b: i32 = unimplemented!(); + #[expect(clippy::logic_bug)] + let _ = a < b && a >= b; +} diff --git a/tests/ui/logic_bug.stderr b/tests/ui/logic_bug.stderr index 8f55e1c8ad8..4021fbf4570 100644 --- a/tests/ui/logic_bug.stderr +++ b/tests/ui/logic_bug.stderr @@ -1,60 +1,60 @@ error: this boolean expression contains a logic bug - --> $DIR/logic_bug.rs:10:13 + --> $DIR/logic_bug.rs:11:13 | LL | let _ = a && b || a; | ^^^^^^^^^^^ help: it would look like the following: `a` | = note: `-D clippy::logic-bug` implied by `-D warnings` help: this expression can be optimized out by applying boolean operations to the outer expression - --> $DIR/logic_bug.rs:10:18 + --> $DIR/logic_bug.rs:11:18 | LL | let _ = a && b || a; | ^ error: this boolean expression contains a logic bug - --> $DIR/logic_bug.rs:12:13 + --> $DIR/logic_bug.rs:13:13 | LL | let _ = false && a; | ^^^^^^^^^^ help: it would look like the following: `false` | help: this expression can be optimized out by applying boolean operations to the outer expression - --> $DIR/logic_bug.rs:12:22 + --> $DIR/logic_bug.rs:13:22 | LL | let _ = false && a; | ^ error: this boolean expression contains a logic bug - --> $DIR/logic_bug.rs:22:13 + --> $DIR/logic_bug.rs:23:13 | LL | let _ = a == b && a != b; | ^^^^^^^^^^^^^^^^ help: it would look like the following: `false` | help: this expression can be optimized out by applying boolean operations to the outer expression - --> $DIR/logic_bug.rs:22:13 + --> $DIR/logic_bug.rs:23:13 | LL | let _ = a == b && a != b; | ^^^^^^ error: this boolean expression contains a logic bug - --> $DIR/logic_bug.rs:23:13 + --> $DIR/logic_bug.rs:24:13 | LL | let _ = a < b && a >= b; | ^^^^^^^^^^^^^^^ help: it would look like the following: `false` | help: this expression can be optimized out by applying boolean operations to the outer expression - --> $DIR/logic_bug.rs:23:13 + --> $DIR/logic_bug.rs:24:13 | LL | let _ = a < b && a >= b; | ^^^^^ error: this boolean expression contains a logic bug - --> $DIR/logic_bug.rs:24:13 + --> $DIR/logic_bug.rs:25:13 | LL | let _ = a > b && a <= b; | ^^^^^^^^^^^^^^^ help: it would look like the following: `false` | help: this expression can be optimized out by applying boolean operations to the outer expression - --> $DIR/logic_bug.rs:24:13 + --> $DIR/logic_bug.rs:25:13 | LL | let _ = a > b && a <= b; | ^^^^^ diff --git a/tests/ui/macro_use_imports.fixed b/tests/ui/macro_use_imports.fixed index a83c8ba0b64..e612480d264 100644 --- a/tests/ui/macro_use_imports.fixed +++ b/tests/ui/macro_use_imports.fixed @@ -4,6 +4,7 @@ // run-rustfix // ignore-32bit +#![feature(lint_reasons)] #![allow(unused_imports, unreachable_code, unused_variables, dead_code, unused_attributes)] #![allow(clippy::single_component_path_imports)] #![warn(clippy::macro_use_imports)] diff --git a/tests/ui/macro_use_imports.rs b/tests/ui/macro_use_imports.rs index e26a7545ea6..b34817cc3b2 100644 --- a/tests/ui/macro_use_imports.rs +++ b/tests/ui/macro_use_imports.rs @@ -4,6 +4,7 @@ // run-rustfix // ignore-32bit +#![feature(lint_reasons)] #![allow(unused_imports, unreachable_code, unused_variables, dead_code, unused_attributes)] #![allow(clippy::single_component_path_imports)] #![warn(clippy::macro_use_imports)] diff --git a/tests/ui/macro_use_imports.stderr b/tests/ui/macro_use_imports.stderr index 9028a636e7f..bf7b6edd0e3 100644 --- a/tests/ui/macro_use_imports.stderr +++ b/tests/ui/macro_use_imports.stderr @@ -1,28 +1,28 @@ error: `macro_use` attributes are no longer needed in the Rust 2018 edition - --> $DIR/macro_use_imports.rs:18:5 + --> $DIR/macro_use_imports.rs:23:5 | LL | #[macro_use] - | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{pub_macro, function_macro, ty_macro, inner_mod_macro, pub_in_private_macro};` + | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{inner::foofoo, inner::try_err};` | = note: `-D clippy::macro-use-imports` implied by `-D warnings` error: `macro_use` attributes are no longer needed in the Rust 2018 edition - --> $DIR/macro_use_imports.rs:20:5 + --> $DIR/macro_use_imports.rs:21:5 | LL | #[macro_use] | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mini_mac::ClippyMiniMacroTest;` error: `macro_use` attributes are no longer needed in the Rust 2018 edition - --> $DIR/macro_use_imports.rs:22:5 - | -LL | #[macro_use] - | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{inner::foofoo, inner::try_err};` - -error: `macro_use` attributes are no longer needed in the Rust 2018 edition - --> $DIR/macro_use_imports.rs:24:5 + --> $DIR/macro_use_imports.rs:25:5 | LL | #[macro_use] | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::inner::nested::string_add;` +error: `macro_use` attributes are no longer needed in the Rust 2018 edition + --> $DIR/macro_use_imports.rs:19:5 + | +LL | #[macro_use] + | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{pub_macro, function_macro, ty_macro, inner_mod_macro, pub_in_private_macro};` + error: aborting due to 4 previous errors diff --git a/tests/ui/macro_use_imports_expect.rs b/tests/ui/macro_use_imports_expect.rs new file mode 100644 index 00000000000..8a1b05da9ef --- /dev/null +++ b/tests/ui/macro_use_imports_expect.rs @@ -0,0 +1,51 @@ +// aux-build:macro_rules.rs +// aux-build:macro_use_helper.rs +// aux-build:proc_macro_derive.rs +// ignore-32bit + +#![feature(lint_reasons)] +#![allow(unused_imports, unreachable_code, unused_variables, dead_code, unused_attributes)] +#![allow(clippy::single_component_path_imports)] +#![warn(clippy::macro_use_imports)] + +#[macro_use] +extern crate macro_use_helper as mac; + +#[macro_use] +extern crate proc_macro_derive as mini_mac; + +mod a { + #[expect(clippy::macro_use_imports)] + #[macro_use] + use mac; + #[expect(clippy::macro_use_imports)] + #[macro_use] + use mini_mac; + #[expect(clippy::macro_use_imports)] + #[macro_use] + use mac::inner; + #[expect(clippy::macro_use_imports)] + #[macro_use] + use mac::inner::nested; + + #[derive(ClippyMiniMacroTest)] + struct Test; + + fn test() { + pub_macro!(); + inner_mod_macro!(); + pub_in_private_macro!(_var); + function_macro!(); + let v: ty_macro!() = Vec::default(); + + inner::try_err!(); + inner::foofoo!(); + nested::string_add!(); + } +} + +// issue #7015, ICE due to calling `module_children` with local `DefId` +#[macro_use] +use a as b; + +fn main() {} diff --git a/tests/ui/manual_find.rs b/tests/ui/manual_find.rs new file mode 100644 index 00000000000..257fe045f78 --- /dev/null +++ b/tests/ui/manual_find.rs @@ -0,0 +1,22 @@ +#![allow(unused)] +#![warn(clippy::manual_find)] + +fn vec_string(strings: Vec) -> Option { + for s in strings { + if s == String::new() { + return Some(s); + } + } + None +} + +fn tuple(arr: Vec<(String, i32)>) -> Option { + for (s, _) in arr { + if s == String::new() { + return Some(s); + } + } + None +} + +fn main() {} diff --git a/tests/ui/manual_find.stderr b/tests/ui/manual_find.stderr new file mode 100644 index 00000000000..da0fd4aaef7 --- /dev/null +++ b/tests/ui/manual_find.stderr @@ -0,0 +1,29 @@ +error: manual implementation of `Iterator::find` + --> $DIR/manual_find.rs:5:5 + | +LL | / for s in strings { +LL | | if s == String::new() { +LL | | return Some(s); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `strings.into_iter().find(|s| s == String::new())` + | + = note: `-D clippy::manual-find` implied by `-D warnings` + = note: you may need to dereference some variables + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find.rs:14:5 + | +LL | / for (s, _) in arr { +LL | | if s == String::new() { +LL | | return Some(s); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().map(|(s, _)| s).find(|s| s == String::new())` + | + = note: you may need to dereference some variables + +error: aborting due to 2 previous errors + diff --git a/tests/ui/manual_find_fixable.fixed b/tests/ui/manual_find_fixable.fixed new file mode 100644 index 00000000000..36d1644c22b --- /dev/null +++ b/tests/ui/manual_find_fixable.fixed @@ -0,0 +1,182 @@ +// run-rustfix + +#![allow(unused, clippy::needless_return)] +#![warn(clippy::manual_find)] + +use std::collections::HashMap; + +const ARRAY: &[u32; 5] = &[2, 7, 1, 9, 3]; + +fn lookup(n: u32) -> Option { + ARRAY.iter().find(|&&v| v == n).copied() +} + +fn with_pat(arr: Vec<(u32, u32)>) -> Option { + arr.into_iter().map(|(a, _)| a).find(|&a| a % 2 == 0) +} + +struct Data { + name: String, + is_true: bool, +} +fn with_struct(arr: Vec) -> Option { + arr.into_iter().find(|el| el.name.len() == 10) +} + +struct Tuple(usize, usize); +fn with_tuple_struct(arr: Vec) -> Option { + arr.into_iter().map(|Tuple(a, _)| a).find(|&a| a >= 3) +} + +struct A; +impl A { + fn should_keep(&self) -> bool { + true + } +} +fn with_method_call(arr: Vec) -> Option { + arr.into_iter().find(|el| el.should_keep()) +} + +fn with_closure(arr: Vec) -> Option { + let f = |el: u32| -> u32 { el + 10 }; + arr.into_iter().find(|&el| f(el) == 20) +} + +fn with_closure2(arr: HashMap) -> Option { + let f = |el: i32| -> bool { el == 10 }; + arr.values().find(|&&el| f(el)).copied() +} + +fn with_bool(arr: Vec) -> Option { + arr.into_iter().find(|el| el.is_true) +} + +fn with_side_effects(arr: Vec) -> Option { + for v in arr { + if v == 1 { + println!("side effect"); + return Some(v); + } + } + None +} + +fn with_else(arr: Vec) -> Option { + for el in arr { + if el % 2 == 0 { + return Some(el); + } else { + println!("{}", el); + } + } + None +} + +fn tuple_with_ref(v: [(u8, &u8); 3]) -> Option { + v.into_iter().map(|(_, &x)| x).find(|&x| x > 10) +} + +fn ref_to_tuple_with_ref(v: &[(u8, &u8)]) -> Option { + v.iter().map(|&(_, &x)| x).find(|&x| x > 10) +} + +fn explicit_ret(arr: Vec) -> Option { + arr.into_iter().find(|&x| x >= 5) +} + +fn plus_one(a: i32) -> Option { + Some(a + 1) +} +fn fn_instead_of_some(a: &[i32]) -> Option { + for &x in a { + if x == 1 { + return plus_one(x); + } + } + None +} + +fn for_in_condition(a: &[i32], b: bool) -> Option { + if b { + for &x in a { + if x == 1 { + return Some(x); + } + } + } + None +} + +fn intermediate_statements(a: &[i32]) -> Option { + for &x in a { + if x == 1 { + return Some(x); + } + } + + println!("side effect"); + + None +} + +fn mixed_binding_modes(arr: Vec<(i32, String)>) -> Option { + for (x, mut s) in arr { + if x == 1 && s.as_mut_str().len() == 2 { + return Some(x); + } + } + None +} + +fn as_closure() { + #[rustfmt::skip] + let f = |arr: Vec| -> Option { + arr.into_iter().find(|&x| x < 1) + }; +} + +fn in_block(a: &[i32]) -> Option { + let should_be_none = { + for &x in a { + if x == 1 { + return Some(x); + } + } + None + }; + + assert!(should_be_none.is_none()); + + should_be_none +} + +// Not handled yet +fn mut_binding(v: Vec) -> Option { + for mut s in v { + if s.as_mut_str().len() > 1 { + return Some(s); + } + } + None +} + +fn subpattern(v: Vec<[u32; 32]>) -> Option<[u32; 32]> { + for a @ [first, ..] in v { + if a[12] == first { + return Some(a); + } + } + None +} + +fn two_bindings(v: Vec<(u8, u8)>) -> Option { + for (a, n) in v { + if a == n { + return Some(a); + } + } + None +} + +fn main() {} diff --git a/tests/ui/manual_find_fixable.rs b/tests/ui/manual_find_fixable.rs new file mode 100644 index 00000000000..ed277ddaa72 --- /dev/null +++ b/tests/ui/manual_find_fixable.rs @@ -0,0 +1,242 @@ +// run-rustfix + +#![allow(unused, clippy::needless_return)] +#![warn(clippy::manual_find)] + +use std::collections::HashMap; + +const ARRAY: &[u32; 5] = &[2, 7, 1, 9, 3]; + +fn lookup(n: u32) -> Option { + for &v in ARRAY { + if v == n { + return Some(v); + } + } + None +} + +fn with_pat(arr: Vec<(u32, u32)>) -> Option { + for (a, _) in arr { + if a % 2 == 0 { + return Some(a); + } + } + None +} + +struct Data { + name: String, + is_true: bool, +} +fn with_struct(arr: Vec) -> Option { + for el in arr { + if el.name.len() == 10 { + return Some(el); + } + } + None +} + +struct Tuple(usize, usize); +fn with_tuple_struct(arr: Vec) -> Option { + for Tuple(a, _) in arr { + if a >= 3 { + return Some(a); + } + } + None +} + +struct A; +impl A { + fn should_keep(&self) -> bool { + true + } +} +fn with_method_call(arr: Vec) -> Option { + for el in arr { + if el.should_keep() { + return Some(el); + } + } + None +} + +fn with_closure(arr: Vec) -> Option { + let f = |el: u32| -> u32 { el + 10 }; + for el in arr { + if f(el) == 20 { + return Some(el); + } + } + None +} + +fn with_closure2(arr: HashMap) -> Option { + let f = |el: i32| -> bool { el == 10 }; + for &el in arr.values() { + if f(el) { + return Some(el); + } + } + None +} + +fn with_bool(arr: Vec) -> Option { + for el in arr { + if el.is_true { + return Some(el); + } + } + None +} + +fn with_side_effects(arr: Vec) -> Option { + for v in arr { + if v == 1 { + println!("side effect"); + return Some(v); + } + } + None +} + +fn with_else(arr: Vec) -> Option { + for el in arr { + if el % 2 == 0 { + return Some(el); + } else { + println!("{}", el); + } + } + None +} + +fn tuple_with_ref(v: [(u8, &u8); 3]) -> Option { + for (_, &x) in v { + if x > 10 { + return Some(x); + } + } + None +} + +fn ref_to_tuple_with_ref(v: &[(u8, &u8)]) -> Option { + for &(_, &x) in v { + if x > 10 { + return Some(x); + } + } + None +} + +fn explicit_ret(arr: Vec) -> Option { + for x in arr { + if x >= 5 { + return Some(x); + } + } + return None; +} + +fn plus_one(a: i32) -> Option { + Some(a + 1) +} +fn fn_instead_of_some(a: &[i32]) -> Option { + for &x in a { + if x == 1 { + return plus_one(x); + } + } + None +} + +fn for_in_condition(a: &[i32], b: bool) -> Option { + if b { + for &x in a { + if x == 1 { + return Some(x); + } + } + } + None +} + +fn intermediate_statements(a: &[i32]) -> Option { + for &x in a { + if x == 1 { + return Some(x); + } + } + + println!("side effect"); + + None +} + +fn mixed_binding_modes(arr: Vec<(i32, String)>) -> Option { + for (x, mut s) in arr { + if x == 1 && s.as_mut_str().len() == 2 { + return Some(x); + } + } + None +} + +fn as_closure() { + #[rustfmt::skip] + let f = |arr: Vec| -> Option { + for x in arr { + if x < 1 { + return Some(x); + } + } + None + }; +} + +fn in_block(a: &[i32]) -> Option { + let should_be_none = { + for &x in a { + if x == 1 { + return Some(x); + } + } + None + }; + + assert!(should_be_none.is_none()); + + should_be_none +} + +// Not handled yet +fn mut_binding(v: Vec) -> Option { + for mut s in v { + if s.as_mut_str().len() > 1 { + return Some(s); + } + } + None +} + +fn subpattern(v: Vec<[u32; 32]>) -> Option<[u32; 32]> { + for a @ [first, ..] in v { + if a[12] == first { + return Some(a); + } + } + None +} + +fn two_bindings(v: Vec<(u8, u8)>) -> Option { + for (a, n) in v { + if a == n { + return Some(a); + } + } + None +} + +fn main() {} diff --git a/tests/ui/manual_find_fixable.stderr b/tests/ui/manual_find_fixable.stderr new file mode 100644 index 00000000000..dbc4ff69a74 --- /dev/null +++ b/tests/ui/manual_find_fixable.stderr @@ -0,0 +1,142 @@ +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:11:5 + | +LL | / for &v in ARRAY { +LL | | if v == n { +LL | | return Some(v); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `ARRAY.iter().find(|&&v| v == n).copied()` + | + = note: `-D clippy::manual-find` implied by `-D warnings` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:20:5 + | +LL | / for (a, _) in arr { +LL | | if a % 2 == 0 { +LL | | return Some(a); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().map(|(a, _)| a).find(|&a| a % 2 == 0)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:33:5 + | +LL | / for el in arr { +LL | | if el.name.len() == 10 { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().find(|el| el.name.len() == 10)` + | + = note: you may need to dereference some variables + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:43:5 + | +LL | / for Tuple(a, _) in arr { +LL | | if a >= 3 { +LL | | return Some(a); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().map(|Tuple(a, _)| a).find(|&a| a >= 3)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:58:5 + | +LL | / for el in arr { +LL | | if el.should_keep() { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().find(|el| el.should_keep())` + | + = note: you may need to dereference some variables + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:68:5 + | +LL | / for el in arr { +LL | | if f(el) == 20 { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().find(|&el| f(el) == 20)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:78:5 + | +LL | / for &el in arr.values() { +LL | | if f(el) { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.values().find(|&&el| f(el)).copied()` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:87:5 + | +LL | / for el in arr { +LL | | if el.is_true { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().find(|el| el.is_true)` + | + = note: you may need to dereference some variables + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:117:5 + | +LL | / for (_, &x) in v { +LL | | if x > 10 { +LL | | return Some(x); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `v.into_iter().map(|(_, &x)| x).find(|&x| x > 10)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:126:5 + | +LL | / for &(_, &x) in v { +LL | | if x > 10 { +LL | | return Some(x); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `v.iter().map(|&(_, &x)| x).find(|&x| x > 10)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:135:5 + | +LL | / for x in arr { +LL | | if x >= 5 { +LL | | return Some(x); +LL | | } +LL | | } +LL | | return None; + | |________________^ help: replace with an iterator: `arr.into_iter().find(|&x| x >= 5)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:190:9 + | +LL | / for x in arr { +LL | | if x < 1 { +LL | | return Some(x); +LL | | } +LL | | } +LL | | None + | |____________^ help: replace with an iterator: `arr.into_iter().find(|&x| x < 1)` + +error: aborting due to 12 previous errors + diff --git a/tests/ui/manual_non_exhaustive_enum.rs b/tests/ui/manual_non_exhaustive_enum.rs index f23c6d69b4c..03b2433f666 100644 --- a/tests/ui/manual_non_exhaustive_enum.rs +++ b/tests/ui/manual_non_exhaustive_enum.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![warn(clippy::manual_non_exhaustive)] #![allow(unused)] @@ -75,4 +76,12 @@ fn foo(x: &mut UsedHidden) { } } +#[expect(clippy::manual_non_exhaustive)] +enum ExpectLint { + A, + B, + #[doc(hidden)] + _C, +} + fn main() {} diff --git a/tests/ui/manual_non_exhaustive_enum.stderr b/tests/ui/manual_non_exhaustive_enum.stderr index 317a45d2cbd..144fe86df55 100644 --- a/tests/ui/manual_non_exhaustive_enum.stderr +++ b/tests/ui/manual_non_exhaustive_enum.stderr @@ -1,5 +1,5 @@ error: this seems like a manual implementation of the non-exhaustive pattern - --> $DIR/manual_non_exhaustive_enum.rs:4:1 + --> $DIR/manual_non_exhaustive_enum.rs:5:1 | LL | enum E { | ^----- @@ -15,13 +15,13 @@ LL | | } | = note: `-D clippy::manual-non-exhaustive` implied by `-D warnings` help: remove this variant - --> $DIR/manual_non_exhaustive_enum.rs:8:5 + --> $DIR/manual_non_exhaustive_enum.rs:9:5 | LL | _C, | ^^ error: this seems like a manual implementation of the non-exhaustive pattern - --> $DIR/manual_non_exhaustive_enum.rs:13:1 + --> $DIR/manual_non_exhaustive_enum.rs:14:1 | LL | / enum Ep { LL | | A, @@ -32,7 +32,7 @@ LL | | } | |_^ | help: remove this variant - --> $DIR/manual_non_exhaustive_enum.rs:17:5 + --> $DIR/manual_non_exhaustive_enum.rs:18:5 | LL | _C, | ^^ diff --git a/tests/ui/manual_rem_euclid.fixed b/tests/ui/manual_rem_euclid.fixed new file mode 100644 index 00000000000..5601c96c10b --- /dev/null +++ b/tests/ui/manual_rem_euclid.fixed @@ -0,0 +1,55 @@ +// run-rustfix +// aux-build:macro_rules.rs + +#![warn(clippy::manual_rem_euclid)] + +#[macro_use] +extern crate macro_rules; + +macro_rules! internal_rem_euclid { + () => { + let value: i32 = 5; + let _: i32 = value.rem_euclid(4); + }; +} + +fn main() { + let value: i32 = 5; + + let _: i32 = value.rem_euclid(4); + let _: i32 = value.rem_euclid(4); + let _: i32 = value.rem_euclid(4); + let _: i32 = value.rem_euclid(4); + let _: i32 = 1 + value.rem_euclid(4); + + let _: i32 = (3 + value % 4) % 4; + let _: i32 = (-4 + value % -4) % -4; + let _: i32 = ((5 % 4) + 4) % 4; + + // Make sure the lint does not trigger if it would cause an error, like with an ambiguous + // integer type + let not_annotated = 24; + let _ = ((not_annotated % 4) + 4) % 4; + let inferred: _ = 24; + let _ = ((inferred % 4) + 4) % 4; + + // For lint to apply the constant must always be on the RHS of the previous value for % + let _: i32 = 4 % ((value % 4) + 4); + let _: i32 = ((4 % value) + 4) % 4; + + // Lint in internal macros + internal_rem_euclid!(); + + // Do not lint in external macros + manual_rem_euclid!(); +} + +// Should lint for params too +pub fn rem_euclid_4(num: i32) -> i32 { + num.rem_euclid(4) +} + +// Constant version came later, should still lint +pub const fn const_rem_euclid_4(num: i32) -> i32 { + num.rem_euclid(4) +} diff --git a/tests/ui/manual_rem_euclid.rs b/tests/ui/manual_rem_euclid.rs new file mode 100644 index 00000000000..52135be26b7 --- /dev/null +++ b/tests/ui/manual_rem_euclid.rs @@ -0,0 +1,55 @@ +// run-rustfix +// aux-build:macro_rules.rs + +#![warn(clippy::manual_rem_euclid)] + +#[macro_use] +extern crate macro_rules; + +macro_rules! internal_rem_euclid { + () => { + let value: i32 = 5; + let _: i32 = ((value % 4) + 4) % 4; + }; +} + +fn main() { + let value: i32 = 5; + + let _: i32 = ((value % 4) + 4) % 4; + let _: i32 = (4 + (value % 4)) % 4; + let _: i32 = (value % 4 + 4) % 4; + let _: i32 = (4 + value % 4) % 4; + let _: i32 = 1 + (4 + value % 4) % 4; + + let _: i32 = (3 + value % 4) % 4; + let _: i32 = (-4 + value % -4) % -4; + let _: i32 = ((5 % 4) + 4) % 4; + + // Make sure the lint does not trigger if it would cause an error, like with an ambiguous + // integer type + let not_annotated = 24; + let _ = ((not_annotated % 4) + 4) % 4; + let inferred: _ = 24; + let _ = ((inferred % 4) + 4) % 4; + + // For lint to apply the constant must always be on the RHS of the previous value for % + let _: i32 = 4 % ((value % 4) + 4); + let _: i32 = ((4 % value) + 4) % 4; + + // Lint in internal macros + internal_rem_euclid!(); + + // Do not lint in external macros + manual_rem_euclid!(); +} + +// Should lint for params too +pub fn rem_euclid_4(num: i32) -> i32 { + ((num % 4) + 4) % 4 +} + +// Constant version came later, should still lint +pub const fn const_rem_euclid_4(num: i32) -> i32 { + ((num % 4) + 4) % 4 +} diff --git a/tests/ui/manual_rem_euclid.stderr b/tests/ui/manual_rem_euclid.stderr new file mode 100644 index 00000000000..a237fd0213c --- /dev/null +++ b/tests/ui/manual_rem_euclid.stderr @@ -0,0 +1,57 @@ +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:19:18 + | +LL | let _: i32 = ((value % 4) + 4) % 4; + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` + | + = note: `-D clippy::manual-rem-euclid` implied by `-D warnings` + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:20:18 + | +LL | let _: i32 = (4 + (value % 4)) % 4; + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:21:18 + | +LL | let _: i32 = (value % 4 + 4) % 4; + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:22:18 + | +LL | let _: i32 = (4 + value % 4) % 4; + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:23:22 + | +LL | let _: i32 = 1 + (4 + value % 4) % 4; + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:12:22 + | +LL | let _: i32 = ((value % 4) + 4) % 4; + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` +... +LL | internal_rem_euclid!(); + | ---------------------- in this macro invocation + | + = note: this error originates in the macro `internal_rem_euclid` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:49:5 + | +LL | ((num % 4) + 4) % 4 + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `num.rem_euclid(4)` + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:54:5 + | +LL | ((num % 4) + 4) % 4 + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `num.rem_euclid(4)` + +error: aborting due to 8 previous errors + diff --git a/tests/ui/manual_retain.fixed b/tests/ui/manual_retain.fixed new file mode 100644 index 00000000000..fba503a2066 --- /dev/null +++ b/tests/ui/manual_retain.fixed @@ -0,0 +1,240 @@ +// run-rustfix +#![feature(custom_inner_attributes)] +#![warn(clippy::manual_retain)] +#![allow(unused)] +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::collections::BinaryHeap; +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::VecDeque; + +fn main() { + binary_heap_retain(); + btree_set_retain(); + btree_map_retain(); + hash_set_retain(); + hash_map_retain(); + string_retain(); + vec_deque_retain(); + vec_retain(); + _msrv_153(); + _msrv_126(); + _msrv_118(); +} + +fn binary_heap_retain() { + // NOTE: Do not lint now, because binary_heap_retain is nighyly API. + // And we need to add a test case for msrv if we update this implmention. + // https://github.com/rust-lang/rust/issues/71503 + let mut heap = BinaryHeap::from([1, 2, 3]); + heap = heap.into_iter().filter(|x| x % 2 == 0).collect(); + heap = heap.iter().filter(|&x| x % 2 == 0).copied().collect(); + heap = heap.iter().filter(|&x| x % 2 == 0).cloned().collect(); + + // Do not lint, because type conversion is performed + heap = heap.into_iter().filter(|x| x % 2 == 0).collect::>(); + heap = heap.iter().filter(|&x| x % 2 == 0).copied().collect::>(); + heap = heap.iter().filter(|&x| x % 2 == 0).cloned().collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: BinaryHeap = heap.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: BinaryHeap = heap.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn btree_map_retain() { + let mut btree_map: BTreeMap = (0..8).map(|x| (x, x * 10)).collect(); + // Do lint. + btree_map.retain(|k, _| k % 2 == 0); + btree_map.retain(|_, &mut v| v % 2 == 0); + btree_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0)); + + // Do not lint. + btree_map = btree_map + .into_iter() + .filter(|(x, _)| x % 2 == 0) + .collect::>(); + + // Do not lint, because this expression is not assign. + let mut foobar: BTreeMap = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + btree_map = foobar.into_iter().filter(|(k, _)| k % 2 == 0).collect(); +} + +fn btree_set_retain() { + let mut btree_set = BTreeSet::from([1, 2, 3, 4, 5, 6]); + + // Do lint. + btree_set.retain(|x| x % 2 == 0); + btree_set.retain(|x| x % 2 == 0); + btree_set.retain(|x| x % 2 == 0); + + // Do not lint, because type conversion is performed + btree_set = btree_set + .iter() + .filter(|&x| x % 2 == 0) + .copied() + .collect::>(); + + btree_set = btree_set + .iter() + .filter(|&x| x % 2 == 0) + .cloned() + .collect::>(); + + btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect::>(); + + // Do not lint, because this expression is not assign. + let mut foobar: BTreeSet = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut bar: BTreeSet = btree_set.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn hash_map_retain() { + let mut hash_map: HashMap = (0..8).map(|x| (x, x * 10)).collect(); + // Do lint. + hash_map.retain(|k, _| k % 2 == 0); + hash_map.retain(|_, &mut v| v % 2 == 0); + hash_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0)); + + // Do not lint. + hash_map = hash_map + .into_iter() + .filter(|(x, _)| x % 2 == 0) + .collect::>(); + + // Do not lint, because this expression is not assign. + let mut foobar: HashMap = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + hash_map = foobar.into_iter().filter(|(k, _)| k % 2 == 0).collect(); +} + +fn hash_set_retain() { + let mut hash_set = HashSet::from([1, 2, 3, 4, 5, 6]); + // Do lint. + hash_set.retain(|x| x % 2 == 0); + hash_set.retain(|x| x % 2 == 0); + hash_set.retain(|x| x % 2 == 0); + + // Do not lint, because type conversion is performed + hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect::>(); + hash_set = hash_set + .iter() + .filter(|&x| x % 2 == 0) + .copied() + .collect::>(); + + hash_set = hash_set + .iter() + .filter(|&x| x % 2 == 0) + .cloned() + .collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: HashSet = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: HashSet = hash_set.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|&x| x % 2 == 0).collect(); +} + +fn string_retain() { + let mut s = String::from("foobar"); + // Do lint. + s.retain(|c| c != 'o'); + + // Do not lint, because this expression is not assign. + let mut bar: String = s.chars().filter(|&c| c != 'o').to_owned().collect(); + + // Do not lint, because it is an assignment to a different variable. + s = bar.chars().filter(|&c| c != 'o').to_owned().collect(); +} + +fn vec_retain() { + let mut vec = vec![0, 1, 2]; + // Do lint. + vec.retain(|x| x % 2 == 0); + vec.retain(|x| x % 2 == 0); + vec.retain(|x| x % 2 == 0); + + // Do not lint, because type conversion is performed + vec = vec.into_iter().filter(|x| x % 2 == 0).collect::>(); + vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect::>(); + vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: Vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: Vec = vec.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn vec_deque_retain() { + let mut vec_deque = VecDeque::new(); + vec_deque.extend(1..5); + + // Do lint. + vec_deque.retain(|x| x % 2 == 0); + vec_deque.retain(|x| x % 2 == 0); + vec_deque.retain(|x| x % 2 == 0); + + // Do not lint, because type conversion is performed + vec_deque = vec_deque + .iter() + .filter(|&x| x % 2 == 0) + .copied() + .collect::>(); + vec_deque = vec_deque + .iter() + .filter(|&x| x % 2 == 0) + .cloned() + .collect::>(); + vec_deque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: VecDeque = vec_deque.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: VecDeque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn _msrv_153() { + #![clippy::msrv = "1.52"] + let mut btree_map: BTreeMap = (0..8).map(|x| (x, x * 10)).collect(); + btree_map = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + + let mut btree_set = BTreeSet::from([1, 2, 3, 4, 5, 6]); + btree_set = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect(); +} + +fn _msrv_126() { + #![clippy::msrv = "1.25"] + let mut s = String::from("foobar"); + s = s.chars().filter(|&c| c != 'o').to_owned().collect(); +} + +fn _msrv_118() { + #![clippy::msrv = "1.17"] + let mut hash_set = HashSet::from([1, 2, 3, 4, 5, 6]); + hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect(); + let mut hash_map: HashMap = (0..8).map(|x| (x, x * 10)).collect(); + hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); +} diff --git a/tests/ui/manual_retain.rs b/tests/ui/manual_retain.rs new file mode 100644 index 00000000000..81a849fe768 --- /dev/null +++ b/tests/ui/manual_retain.rs @@ -0,0 +1,246 @@ +// run-rustfix +#![feature(custom_inner_attributes)] +#![warn(clippy::manual_retain)] +#![allow(unused)] +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::collections::BinaryHeap; +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::VecDeque; + +fn main() { + binary_heap_retain(); + btree_set_retain(); + btree_map_retain(); + hash_set_retain(); + hash_map_retain(); + string_retain(); + vec_deque_retain(); + vec_retain(); + _msrv_153(); + _msrv_126(); + _msrv_118(); +} + +fn binary_heap_retain() { + // NOTE: Do not lint now, because binary_heap_retain is nighyly API. + // And we need to add a test case for msrv if we update this implmention. + // https://github.com/rust-lang/rust/issues/71503 + let mut heap = BinaryHeap::from([1, 2, 3]); + heap = heap.into_iter().filter(|x| x % 2 == 0).collect(); + heap = heap.iter().filter(|&x| x % 2 == 0).copied().collect(); + heap = heap.iter().filter(|&x| x % 2 == 0).cloned().collect(); + + // Do not lint, because type conversion is performed + heap = heap.into_iter().filter(|x| x % 2 == 0).collect::>(); + heap = heap.iter().filter(|&x| x % 2 == 0).copied().collect::>(); + heap = heap.iter().filter(|&x| x % 2 == 0).cloned().collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: BinaryHeap = heap.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: BinaryHeap = heap.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn btree_map_retain() { + let mut btree_map: BTreeMap = (0..8).map(|x| (x, x * 10)).collect(); + // Do lint. + btree_map = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + btree_map = btree_map.into_iter().filter(|(_, v)| v % 2 == 0).collect(); + btree_map = btree_map + .into_iter() + .filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0)) + .collect(); + + // Do not lint. + btree_map = btree_map + .into_iter() + .filter(|(x, _)| x % 2 == 0) + .collect::>(); + + // Do not lint, because this expression is not assign. + let mut foobar: BTreeMap = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + btree_map = foobar.into_iter().filter(|(k, _)| k % 2 == 0).collect(); +} + +fn btree_set_retain() { + let mut btree_set = BTreeSet::from([1, 2, 3, 4, 5, 6]); + + // Do lint. + btree_set = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + btree_set = btree_set.iter().filter(|&x| x % 2 == 0).cloned().collect(); + btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because type conversion is performed + btree_set = btree_set + .iter() + .filter(|&x| x % 2 == 0) + .copied() + .collect::>(); + + btree_set = btree_set + .iter() + .filter(|&x| x % 2 == 0) + .cloned() + .collect::>(); + + btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect::>(); + + // Do not lint, because this expression is not assign. + let mut foobar: BTreeSet = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut bar: BTreeSet = btree_set.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn hash_map_retain() { + let mut hash_map: HashMap = (0..8).map(|x| (x, x * 10)).collect(); + // Do lint. + hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + hash_map = hash_map.into_iter().filter(|(_, v)| v % 2 == 0).collect(); + hash_map = hash_map + .into_iter() + .filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0)) + .collect(); + + // Do not lint. + hash_map = hash_map + .into_iter() + .filter(|(x, _)| x % 2 == 0) + .collect::>(); + + // Do not lint, because this expression is not assign. + let mut foobar: HashMap = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + hash_map = foobar.into_iter().filter(|(k, _)| k % 2 == 0).collect(); +} + +fn hash_set_retain() { + let mut hash_set = HashSet::from([1, 2, 3, 4, 5, 6]); + // Do lint. + hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect(); + hash_set = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + hash_set = hash_set.iter().filter(|&x| x % 2 == 0).cloned().collect(); + + // Do not lint, because type conversion is performed + hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect::>(); + hash_set = hash_set + .iter() + .filter(|&x| x % 2 == 0) + .copied() + .collect::>(); + + hash_set = hash_set + .iter() + .filter(|&x| x % 2 == 0) + .cloned() + .collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: HashSet = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: HashSet = hash_set.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|&x| x % 2 == 0).collect(); +} + +fn string_retain() { + let mut s = String::from("foobar"); + // Do lint. + s = s.chars().filter(|&c| c != 'o').to_owned().collect(); + + // Do not lint, because this expression is not assign. + let mut bar: String = s.chars().filter(|&c| c != 'o').to_owned().collect(); + + // Do not lint, because it is an assignment to a different variable. + s = bar.chars().filter(|&c| c != 'o').to_owned().collect(); +} + +fn vec_retain() { + let mut vec = vec![0, 1, 2]; + // Do lint. + vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect(); + vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect(); + vec = vec.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because type conversion is performed + vec = vec.into_iter().filter(|x| x % 2 == 0).collect::>(); + vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect::>(); + vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: Vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: Vec = vec.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn vec_deque_retain() { + let mut vec_deque = VecDeque::new(); + vec_deque.extend(1..5); + + // Do lint. + vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).copied().collect(); + vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).cloned().collect(); + vec_deque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because type conversion is performed + vec_deque = vec_deque + .iter() + .filter(|&x| x % 2 == 0) + .copied() + .collect::>(); + vec_deque = vec_deque + .iter() + .filter(|&x| x % 2 == 0) + .cloned() + .collect::>(); + vec_deque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: VecDeque = vec_deque.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: VecDeque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn _msrv_153() { + #![clippy::msrv = "1.52"] + let mut btree_map: BTreeMap = (0..8).map(|x| (x, x * 10)).collect(); + btree_map = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + + let mut btree_set = BTreeSet::from([1, 2, 3, 4, 5, 6]); + btree_set = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect(); +} + +fn _msrv_126() { + #![clippy::msrv = "1.25"] + let mut s = String::from("foobar"); + s = s.chars().filter(|&c| c != 'o').to_owned().collect(); +} + +fn _msrv_118() { + #![clippy::msrv = "1.17"] + let mut hash_set = HashSet::from([1, 2, 3, 4, 5, 6]); + hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect(); + let mut hash_map: HashMap = (0..8).map(|x| (x, x * 10)).collect(); + hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); +} diff --git a/tests/ui/manual_retain.stderr b/tests/ui/manual_retain.stderr new file mode 100644 index 00000000000..ec635919b48 --- /dev/null +++ b/tests/ui/manual_retain.stderr @@ -0,0 +1,124 @@ +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:52:5 + | +LL | btree_map = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_map.retain(|k, _| k % 2 == 0)` + | + = note: `-D clippy::manual-retain` implied by `-D warnings` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:53:5 + | +LL | btree_map = btree_map.into_iter().filter(|(_, v)| v % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_map.retain(|_, &mut v| v % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:54:5 + | +LL | / btree_map = btree_map +LL | | .into_iter() +LL | | .filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0)) +LL | | .collect(); + | |__________________^ help: consider calling `.retain()` instead: `btree_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0))` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:76:5 + | +LL | btree_set = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:77:5 + | +LL | btree_set = btree_set.iter().filter(|&x| x % 2 == 0).cloned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:78:5 + | +LL | btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:108:5 + | +LL | hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_map.retain(|k, _| k % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:109:5 + | +LL | hash_map = hash_map.into_iter().filter(|(_, v)| v % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_map.retain(|_, &mut v| v % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:110:5 + | +LL | / hash_map = hash_map +LL | | .into_iter() +LL | | .filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0)) +LL | | .collect(); + | |__________________^ help: consider calling `.retain()` instead: `hash_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0))` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:131:5 + | +LL | hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:132:5 + | +LL | hash_set = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:133:5 + | +LL | hash_set = hash_set.iter().filter(|&x| x % 2 == 0).cloned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:162:5 + | +LL | s = s.chars().filter(|&c| c != 'o').to_owned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `s.retain(|c| c != 'o')` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:174:5 + | +LL | vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:175:5 + | +LL | vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:176:5 + | +LL | vec = vec.into_iter().filter(|x| x % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:198:5 + | +LL | vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).copied().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:199:5 + | +LL | vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).cloned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:200:5 + | +LL | vec_deque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)` + +error: aborting due to 19 previous errors + diff --git a/tests/ui/methods.rs b/tests/ui/methods.rs index 9805097084d..1970c2eae53 100644 --- a/tests/ui/methods.rs +++ b/tests/ui/methods.rs @@ -15,6 +15,7 @@ clippy::use_self, clippy::useless_format, clippy::wrong_self_convention, + clippy::unused_async, clippy::unused_self, unused )] diff --git a/tests/ui/methods.stderr b/tests/ui/methods.stderr index 6be38b24fbd..b63672dd6fd 100644 --- a/tests/ui/methods.stderr +++ b/tests/ui/methods.stderr @@ -1,5 +1,5 @@ error: methods called `new` usually return `Self` - --> $DIR/methods.rs:103:5 + --> $DIR/methods.rs:104:5 | LL | / fn new() -> i32 { LL | | 0 @@ -9,7 +9,7 @@ LL | | } = note: `-D clippy::new-ret-no-self` implied by `-D warnings` error: called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling `.find(..)` instead - --> $DIR/methods.rs:124:13 + --> $DIR/methods.rs:125:13 | LL | let _ = v.iter().filter(|&x| { | _____________^ diff --git a/tests/ui/min_rust_version_attr.rs b/tests/ui/min_rust_version_attr.rs index f83c3e0e281..44e407bd1ab 100644 --- a/tests/ui/min_rust_version_attr.rs +++ b/tests/ui/min_rust_version_attr.rs @@ -155,6 +155,11 @@ fn cast_abs_to_unsigned() { assert_eq!(10u32, x.abs() as u32); } +fn manual_rem_euclid() { + let x: i32 = 10; + let _: i32 = ((x % 4) + 4) % 4; +} + fn main() { filter_map_next(); checked_conversion(); @@ -174,6 +179,7 @@ fn main() { int_from_bool(); err_expect(); cast_abs_to_unsigned(); + manual_rem_euclid(); } mod just_under_msrv { @@ -211,3 +217,12 @@ fn main() { } } } + +mod const_rem_euclid { + #![feature(custom_inner_attributes)] + #![clippy::msrv = "1.50.0"] + + pub const fn const_rem_euclid_4(num: i32) -> i32 { + ((num % 4) + 4) % 4 + } +} diff --git a/tests/ui/min_rust_version_attr.stderr b/tests/ui/min_rust_version_attr.stderr index de225eb740d..b1c23b539ff 100644 --- a/tests/ui/min_rust_version_attr.stderr +++ b/tests/ui/min_rust_version_attr.stderr @@ -1,12 +1,12 @@ error: stripping a prefix manually - --> $DIR/min_rust_version_attr.rs:198:24 + --> $DIR/min_rust_version_attr.rs:204:24 | LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); | ^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::manual-strip` implied by `-D warnings` note: the prefix was tested here - --> $DIR/min_rust_version_attr.rs:197:9 + --> $DIR/min_rust_version_attr.rs:203:9 | LL | if s.starts_with("hello, ") { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -17,13 +17,13 @@ LL ~ assert_eq!(.to_uppercase(), "WORLD!"); | error: stripping a prefix manually - --> $DIR/min_rust_version_attr.rs:210:24 + --> $DIR/min_rust_version_attr.rs:216:24 | LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); | ^^^^^^^^^^^^^^^^^^^^ | note: the prefix was tested here - --> $DIR/min_rust_version_attr.rs:209:9 + --> $DIR/min_rust_version_attr.rs:215:9 | LL | if s.starts_with("hello, ") { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/needless_borrow.fixed b/tests/ui/needless_borrow.fixed index e7a483c0582..cb005122436 100644 --- a/tests/ui/needless_borrow.fixed +++ b/tests/ui/needless_borrow.fixed @@ -62,7 +62,18 @@ fn main() { 0 => &mut x, _ => &mut *x, }; - + let y: &mut i32 = match 0 { + // Lint here. The type given above triggers auto-borrow. + 0 => x, + _ => &mut *x, + }; + fn ref_mut_i32(_: &mut i32) {} + ref_mut_i32(match 0 { + // Lint here. The type given above triggers auto-borrow. + 0 => x, + _ => &mut *x, + }); + // use 'x' after to make sure it's still usable in the fixed code. *x = 5; let s = String::new(); @@ -74,6 +85,36 @@ fn main() { let _ = x.0; let x = &x as *const (i32, i32); let _ = unsafe { (*x).0 }; + + // Issue #8367 + trait Foo { + fn foo(self); + } + impl Foo for &'_ () { + fn foo(self) {} + } + (&()).foo(); // Don't lint. `()` doesn't implement `Foo` + (&()).foo(); + + impl Foo for i32 { + fn foo(self) {} + } + impl Foo for &'_ i32 { + fn foo(self) {} + } + (&5).foo(); // Don't lint. `5` will call `::foo` + (&5).foo(); + + trait FooRef { + fn foo_ref(&self); + } + impl FooRef for () { + fn foo_ref(&self) {} + } + impl FooRef for &'_ () { + fn foo_ref(&self) {} + } + (&&()).foo_ref(); // Don't lint. `&()` will call `<() as FooRef>::foo_ref` } #[allow(clippy::needless_borrowed_reference)] diff --git a/tests/ui/needless_borrow.rs b/tests/ui/needless_borrow.rs index 1d6bf46405a..d636a401003 100644 --- a/tests/ui/needless_borrow.rs +++ b/tests/ui/needless_borrow.rs @@ -62,7 +62,18 @@ fn main() { 0 => &mut x, _ => &mut *x, }; - + let y: &mut i32 = match 0 { + // Lint here. The type given above triggers auto-borrow. + 0 => &mut x, + _ => &mut *x, + }; + fn ref_mut_i32(_: &mut i32) {} + ref_mut_i32(match 0 { + // Lint here. The type given above triggers auto-borrow. + 0 => &mut x, + _ => &mut *x, + }); + // use 'x' after to make sure it's still usable in the fixed code. *x = 5; let s = String::new(); @@ -74,6 +85,36 @@ fn main() { let _ = (&x).0; let x = &x as *const (i32, i32); let _ = unsafe { (&*x).0 }; + + // Issue #8367 + trait Foo { + fn foo(self); + } + impl Foo for &'_ () { + fn foo(self) {} + } + (&()).foo(); // Don't lint. `()` doesn't implement `Foo` + (&&()).foo(); + + impl Foo for i32 { + fn foo(self) {} + } + impl Foo for &'_ i32 { + fn foo(self) {} + } + (&5).foo(); // Don't lint. `5` will call `::foo` + (&&5).foo(); + + trait FooRef { + fn foo_ref(&self); + } + impl FooRef for () { + fn foo_ref(&self) {} + } + impl FooRef for &'_ () { + fn foo_ref(&self) {} + } + (&&()).foo_ref(); // Don't lint. `&()` will call `<() as FooRef>::foo_ref` } #[allow(clippy::needless_borrowed_reference)] diff --git a/tests/ui/needless_borrow.stderr b/tests/ui/needless_borrow.stderr index be59d8f546d..8a2e2b98959 100644 --- a/tests/ui/needless_borrow.stderr +++ b/tests/ui/needless_borrow.stderr @@ -84,17 +84,41 @@ error: this expression creates a reference which is immediately dereferenced by LL | let y: &mut i32 = &mut &mut x; | ^^^^^^^^^^^ help: change this to: `x` +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:67:14 + | +LL | 0 => &mut x, + | ^^^^^^ help: change this to: `x` + +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:73:14 + | +LL | 0 => &mut x, + | ^^^^^^ help: change this to: `x` + error: this expression borrows a value the compiler would automatically borrow - --> $DIR/needless_borrow.rs:74:13 + --> $DIR/needless_borrow.rs:85:13 | LL | let _ = (&x).0; | ^^^^ help: change this to: `x` error: this expression borrows a value the compiler would automatically borrow - --> $DIR/needless_borrow.rs:76:22 + --> $DIR/needless_borrow.rs:87:22 | LL | let _ = unsafe { (&*x).0 }; | ^^^^^ help: change this to: `(*x)` -error: aborting due to 16 previous errors +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:97:5 + | +LL | (&&()).foo(); + | ^^^^^^ help: change this to: `(&())` + +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:106:5 + | +LL | (&&5).foo(); + | ^^^^^ help: change this to: `(&5)` + +error: aborting due to 20 previous errors diff --git a/tests/ui/needless_borrow_pat.rs b/tests/ui/needless_borrow_pat.rs index 04b6283da3c..222e8e61799 100644 --- a/tests/ui/needless_borrow_pat.rs +++ b/tests/ui/needless_borrow_pat.rs @@ -1,7 +1,7 @@ // FIXME: run-rustfix waiting on multi-span suggestions #![warn(clippy::needless_borrow)] -#![allow(clippy::needless_borrowed_reference)] +#![allow(clippy::needless_borrowed_reference, clippy::explicit_auto_deref)] fn f1(_: &str) {} macro_rules! m1 { diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index 7c828430b78..0bc0d0011ef 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -1,5 +1,6 @@ // run-rustfix +#![feature(lint_reasons)] #![feature(let_else)] #![allow(unused)] #![allow( @@ -10,6 +11,8 @@ )] #![warn(clippy::needless_return)] +use std::cell::RefCell; + macro_rules! the_answer { () => { 42 @@ -86,17 +89,15 @@ fn test_nested_match(x: u32) { } } -fn read_line() -> String { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - return stdin.lock().lines().next().unwrap().unwrap(); +fn temporary_outlives_local() -> String { + let x = RefCell::::default(); + return x.borrow().clone(); } fn borrows_but_not_last(value: bool) -> String { if value { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - let _a = stdin.lock().lines().next().unwrap().unwrap(); + let x = RefCell::::default(); + let _a = x.borrow().clone(); String::from("test") } else { String::new() @@ -197,17 +198,15 @@ async fn async_test_void_match(x: u32) { } } -async fn async_read_line() -> String { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - return stdin.lock().lines().next().unwrap().unwrap(); +async fn async_temporary_outlives_local() -> String { + let x = RefCell::::default(); + return x.borrow().clone(); } async fn async_borrows_but_not_last(value: bool) -> String { if value { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - let _a = stdin.lock().lines().next().unwrap().unwrap(); + let x = RefCell::::default(); + let _a = x.borrow().clone(); String::from("test") } else { String::new() @@ -229,4 +228,13 @@ fn needless_return_macro() -> String { format!("Hello {}", "world!") } +fn check_expect() -> bool { + if true { + // no error! + return true; + } + #[expect(clippy::needless_return)] + return true; +} + fn main() {} diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index fe82af00e67..eb9f72e8e78 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -1,5 +1,6 @@ // run-rustfix +#![feature(lint_reasons)] #![feature(let_else)] #![allow(unused)] #![allow( @@ -10,6 +11,8 @@ )] #![warn(clippy::needless_return)] +use std::cell::RefCell; + macro_rules! the_answer { () => { 42 @@ -86,17 +89,15 @@ fn test_nested_match(x: u32) { } } -fn read_line() -> String { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - return stdin.lock().lines().next().unwrap().unwrap(); +fn temporary_outlives_local() -> String { + let x = RefCell::::default(); + return x.borrow().clone(); } fn borrows_but_not_last(value: bool) -> String { if value { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - let _a = stdin.lock().lines().next().unwrap().unwrap(); + let x = RefCell::::default(); + let _a = x.borrow().clone(); return String::from("test"); } else { return String::new(); @@ -197,17 +198,15 @@ async fn async_test_void_match(x: u32) { } } -async fn async_read_line() -> String { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - return stdin.lock().lines().next().unwrap().unwrap(); +async fn async_temporary_outlives_local() -> String { + let x = RefCell::::default(); + return x.borrow().clone(); } async fn async_borrows_but_not_last(value: bool) -> String { if value { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - let _a = stdin.lock().lines().next().unwrap().unwrap(); + let x = RefCell::::default(); + let _a = x.borrow().clone(); return String::from("test"); } else { return String::new(); @@ -229,4 +228,13 @@ fn needless_return_macro() -> String { return format!("Hello {}", "world!"); } +fn check_expect() -> bool { + if true { + // no error! + return true; + } + #[expect(clippy::needless_return)] + return true; +} + fn main() {} diff --git a/tests/ui/needless_return.stderr b/tests/ui/needless_return.stderr index 4c8be47b025..83ff0763869 100644 --- a/tests/ui/needless_return.stderr +++ b/tests/ui/needless_return.stderr @@ -1,5 +1,5 @@ error: unneeded `return` statement - --> $DIR/needless_return.rs:24:5 + --> $DIR/needless_return.rs:27:5 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` @@ -7,217 +7,217 @@ LL | return true; = note: `-D clippy::needless-return` implied by `-D warnings` error: unneeded `return` statement - --> $DIR/needless_return.rs:28:5 + --> $DIR/needless_return.rs:31:5 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:33:9 + --> $DIR/needless_return.rs:36:9 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:35:9 + --> $DIR/needless_return.rs:38:9 | LL | return false; | ^^^^^^^^^^^^^ help: remove `return`: `false` error: unneeded `return` statement - --> $DIR/needless_return.rs:41:17 + --> $DIR/needless_return.rs:44:17 | LL | true => return false, | ^^^^^^^^^^^^ help: remove `return`: `false` error: unneeded `return` statement - --> $DIR/needless_return.rs:43:13 + --> $DIR/needless_return.rs:46:13 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:50:9 + --> $DIR/needless_return.rs:53:9 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:52:16 + --> $DIR/needless_return.rs:55:16 | LL | let _ = || return true; | ^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:56:5 + --> $DIR/needless_return.rs:59:5 | LL | return the_answer!(); | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `the_answer!()` error: unneeded `return` statement - --> $DIR/needless_return.rs:60:5 + --> $DIR/needless_return.rs:63:5 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:65:9 + --> $DIR/needless_return.rs:68:9 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:67:9 + --> $DIR/needless_return.rs:70:9 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:74:14 + --> $DIR/needless_return.rs:77:14 | LL | _ => return, | ^^^^^^ help: replace `return` with a unit value: `()` error: unneeded `return` statement - --> $DIR/needless_return.rs:83:13 + --> $DIR/needless_return.rs:86:13 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:85:14 + --> $DIR/needless_return.rs:88:14 | LL | _ => return, | ^^^^^^ help: replace `return` with a unit value: `()` error: unneeded `return` statement - --> $DIR/needless_return.rs:100:9 + --> $DIR/needless_return.rs:101:9 | LL | return String::from("test"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::from("test")` error: unneeded `return` statement - --> $DIR/needless_return.rs:102:9 + --> $DIR/needless_return.rs:103:9 | LL | return String::new(); | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::new()` error: unneeded `return` statement - --> $DIR/needless_return.rs:124:32 + --> $DIR/needless_return.rs:125:32 | LL | bar.unwrap_or_else(|_| return) | ^^^^^^ help: replace `return` with an empty block: `{}` error: unneeded `return` statement - --> $DIR/needless_return.rs:129:13 + --> $DIR/needless_return.rs:130:13 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:131:20 + --> $DIR/needless_return.rs:132:20 | LL | let _ = || return; | ^^^^^^ help: replace `return` with an empty block: `{}` error: unneeded `return` statement - --> $DIR/needless_return.rs:137:32 + --> $DIR/needless_return.rs:138:32 | LL | res.unwrap_or_else(|_| return Foo) | ^^^^^^^^^^ help: remove `return`: `Foo` error: unneeded `return` statement - --> $DIR/needless_return.rs:146:5 + --> $DIR/needless_return.rs:147:5 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:150:5 + --> $DIR/needless_return.rs:151:5 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:155:9 + --> $DIR/needless_return.rs:156:9 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:157:9 + --> $DIR/needless_return.rs:158:9 | LL | return false; | ^^^^^^^^^^^^^ help: remove `return`: `false` error: unneeded `return` statement - --> $DIR/needless_return.rs:163:17 + --> $DIR/needless_return.rs:164:17 | LL | true => return false, | ^^^^^^^^^^^^ help: remove `return`: `false` error: unneeded `return` statement - --> $DIR/needless_return.rs:165:13 + --> $DIR/needless_return.rs:166:13 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:172:9 + --> $DIR/needless_return.rs:173:9 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:174:16 + --> $DIR/needless_return.rs:175:16 | LL | let _ = || return true; | ^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:178:5 + --> $DIR/needless_return.rs:179:5 | LL | return the_answer!(); | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `the_answer!()` error: unneeded `return` statement - --> $DIR/needless_return.rs:182:5 + --> $DIR/needless_return.rs:183:5 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:187:9 + --> $DIR/needless_return.rs:188:9 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:189:9 + --> $DIR/needless_return.rs:190:9 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:196:14 + --> $DIR/needless_return.rs:197:14 | LL | _ => return, | ^^^^^^ help: replace `return` with a unit value: `()` error: unneeded `return` statement - --> $DIR/needless_return.rs:211:9 + --> $DIR/needless_return.rs:210:9 | LL | return String::from("test"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::from("test")` error: unneeded `return` statement - --> $DIR/needless_return.rs:213:9 + --> $DIR/needless_return.rs:212:9 | LL | return String::new(); | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::new()` error: unneeded `return` statement - --> $DIR/needless_return.rs:229:5 + --> $DIR/needless_return.rs:228:5 | LL | return format!("Hello {}", "world!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `format!("Hello {}", "world!")` diff --git a/tests/ui/neg_multiply.fixed b/tests/ui/neg_multiply.fixed index 35af9d6ae31..58ab9e85678 100644 --- a/tests/ui/neg_multiply.fixed +++ b/tests/ui/neg_multiply.fixed @@ -38,6 +38,9 @@ fn main() { 0xcafe | -0xff00; + -(3_usize as i32); + -(3_usize as i32); + -1 * -1; // should be ok X * -1; // should be ok diff --git a/tests/ui/neg_multiply.rs b/tests/ui/neg_multiply.rs index 7dbdb0906ce..581290dc72e 100644 --- a/tests/ui/neg_multiply.rs +++ b/tests/ui/neg_multiply.rs @@ -38,6 +38,9 @@ fn main() { 0xcafe | 0xff00 * -1; + 3_usize as i32 * -1; + (3_usize as i32) * -1; + -1 * -1; // should be ok X * -1; // should be ok diff --git a/tests/ui/neg_multiply.stderr b/tests/ui/neg_multiply.stderr index dbf8fb36938..388ef29eb85 100644 --- a/tests/ui/neg_multiply.stderr +++ b/tests/ui/neg_multiply.stderr @@ -36,5 +36,17 @@ error: this multiplication by -1 can be written more succinctly LL | 0xcafe | 0xff00 * -1; | ^^^^^^^^^^^ help: consider using: `-0xff00` -error: aborting due to 6 previous errors +error: this multiplication by -1 can be written more succinctly + --> $DIR/neg_multiply.rs:41:5 + | +LL | 3_usize as i32 * -1; + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `-(3_usize as i32)` + +error: this multiplication by -1 can be written more succinctly + --> $DIR/neg_multiply.rs:42:5 + | +LL | (3_usize as i32) * -1; + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `-(3_usize as i32)` + +error: aborting due to 8 previous errors diff --git a/tests/ui/nonminimal_bool.rs b/tests/ui/nonminimal_bool.rs index fa5743c1155..24ae62bb058 100644 --- a/tests/ui/nonminimal_bool.rs +++ b/tests/ui/nonminimal_bool.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![allow(unused, clippy::diverging_sub_expression)] #![warn(clippy::nonminimal_bool)] @@ -50,3 +51,9 @@ fn f(_i: u32, _j: u32) -> u32 { if i != j && f(i, j) != 0 || i == j && f(i, j) != 1 {} } + +fn check_expect() { + let a: bool = unimplemented!(); + #[expect(clippy::nonminimal_bool)] + let _ = !!a; +} diff --git a/tests/ui/nonminimal_bool.stderr b/tests/ui/nonminimal_bool.stderr index bb93cbbd5e1..fc6a5ce1dc2 100644 --- a/tests/ui/nonminimal_bool.stderr +++ b/tests/ui/nonminimal_bool.stderr @@ -1,5 +1,5 @@ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:10:13 + --> $DIR/nonminimal_bool.rs:11:13 | LL | let _ = !true; | ^^^^^ help: try: `false` @@ -7,43 +7,43 @@ LL | let _ = !true; = note: `-D clippy::nonminimal-bool` implied by `-D warnings` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:11:13 + --> $DIR/nonminimal_bool.rs:12:13 | LL | let _ = !false; | ^^^^^^ help: try: `true` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:12:13 + --> $DIR/nonminimal_bool.rs:13:13 | LL | let _ = !!a; | ^^^ help: try: `a` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:13:13 + --> $DIR/nonminimal_bool.rs:14:13 | LL | let _ = false || a; | ^^^^^^^^^^ help: try: `a` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:17:13 + --> $DIR/nonminimal_bool.rs:18:13 | LL | let _ = !(!a && b); | ^^^^^^^^^^ help: try: `a || !b` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:18:13 + --> $DIR/nonminimal_bool.rs:19:13 | LL | let _ = !(!a || b); | ^^^^^^^^^^ help: try: `a && !b` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:19:13 + --> $DIR/nonminimal_bool.rs:20:13 | LL | let _ = !a && !(b && c); | ^^^^^^^^^^^^^^^ help: try: `!(a || b && c)` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:27:13 + --> $DIR/nonminimal_bool.rs:28:13 | LL | let _ = a == b && c == 5 && a == b; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -56,7 +56,7 @@ LL | let _ = a == b && c == 5; | ~~~~~~~~~~~~~~~~ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:28:13 + --> $DIR/nonminimal_bool.rs:29:13 | LL | let _ = a == b || c == 5 || a == b; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -69,7 +69,7 @@ LL | let _ = a == b || c == 5; | ~~~~~~~~~~~~~~~~ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:29:13 + --> $DIR/nonminimal_bool.rs:30:13 | LL | let _ = a == b && c == 5 && b == a; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -82,7 +82,7 @@ LL | let _ = a == b && c == 5; | ~~~~~~~~~~~~~~~~ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:30:13 + --> $DIR/nonminimal_bool.rs:31:13 | LL | let _ = a != b || !(a != b || c == d); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -95,7 +95,7 @@ LL | let _ = a != b || c != d; | ~~~~~~~~~~~~~~~~ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:31:13 + --> $DIR/nonminimal_bool.rs:32:13 | LL | let _ = a != b && !(a != b && c == d); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/ptr_arg.rs b/tests/ui/ptr_arg.rs index 814bbc7af71..fd15001e540 100644 --- a/tests/ui/ptr_arg.rs +++ b/tests/ui/ptr_arg.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![allow(unused, clippy::many_single_char_names, clippy::redundant_clone)] #![warn(clippy::ptr_arg)] @@ -109,9 +110,12 @@ fn allowed( #[allow(clippy::ptr_arg)] _s: &String, #[allow(clippy::ptr_arg)] _p: &PathBuf, #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, + #[expect(clippy::ptr_arg)] _expect: &Cow<[i32]>, ) { } + fn some_allowed(#[allow(clippy::ptr_arg)] _v: &Vec, _s: &String) {} + struct S; impl S { fn allowed( @@ -119,6 +123,7 @@ fn allowed( #[allow(clippy::ptr_arg)] _s: &String, #[allow(clippy::ptr_arg)] _p: &PathBuf, #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, + #[expect(clippy::ptr_arg)] _expect: &Cow<[i32]>, ) { } } @@ -129,6 +134,7 @@ fn allowed( #[allow(clippy::ptr_arg)] _s: &String, #[allow(clippy::ptr_arg)] _p: &PathBuf, #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, + #[expect(clippy::ptr_arg)] _expect: &Cow<[i32]>, ) { } } diff --git a/tests/ui/ptr_arg.stderr b/tests/ui/ptr_arg.stderr index 7ec4a566ff3..d64b5f454a5 100644 --- a/tests/ui/ptr_arg.stderr +++ b/tests/ui/ptr_arg.stderr @@ -1,5 +1,5 @@ error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:7:14 + --> $DIR/ptr_arg.rs:8:14 | LL | fn do_vec(x: &Vec) { | ^^^^^^^^^ help: change this to: `&[i64]` @@ -7,43 +7,43 @@ LL | fn do_vec(x: &Vec) { = note: `-D clippy::ptr-arg` implied by `-D warnings` error: writing `&mut Vec` instead of `&mut [_]` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:11:18 + --> $DIR/ptr_arg.rs:12:18 | LL | fn do_vec_mut(x: &mut Vec) { | ^^^^^^^^^^^^^ help: change this to: `&mut [i64]` error: writing `&String` instead of `&str` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:15:14 + --> $DIR/ptr_arg.rs:16:14 | LL | fn do_str(x: &String) { | ^^^^^^^ help: change this to: `&str` error: writing `&mut String` instead of `&mut str` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:19:18 + --> $DIR/ptr_arg.rs:20:18 | LL | fn do_str_mut(x: &mut String) { | ^^^^^^^^^^^ help: change this to: `&mut str` error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:23:15 + --> $DIR/ptr_arg.rs:24:15 | LL | fn do_path(x: &PathBuf) { | ^^^^^^^^ help: change this to: `&Path` error: writing `&mut PathBuf` instead of `&mut Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:27:19 + --> $DIR/ptr_arg.rs:28:19 | LL | fn do_path_mut(x: &mut PathBuf) { | ^^^^^^^^^^^^ help: change this to: `&mut Path` error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:35:18 + --> $DIR/ptr_arg.rs:36:18 | LL | fn do_vec(x: &Vec); | ^^^^^^^^^ help: change this to: `&[i64]` error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:48:14 + --> $DIR/ptr_arg.rs:49:14 | LL | fn cloned(x: &Vec) -> Vec { | ^^^^^^^^ @@ -60,7 +60,7 @@ LL ~ x.to_owned() | error: writing `&String` instead of `&str` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:57:18 + --> $DIR/ptr_arg.rs:58:18 | LL | fn str_cloned(x: &String) -> String { | ^^^^^^^ @@ -76,7 +76,7 @@ LL ~ x.to_owned() | error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:65:19 + --> $DIR/ptr_arg.rs:66:19 | LL | fn path_cloned(x: &PathBuf) -> PathBuf { | ^^^^^^^^ @@ -92,7 +92,7 @@ LL ~ x.to_path_buf() | error: writing `&String` instead of `&str` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:73:44 + --> $DIR/ptr_arg.rs:74:44 | LL | fn false_positive_capacity(x: &Vec, y: &String) { | ^^^^^^^ @@ -106,13 +106,19 @@ LL ~ let c = y; | error: using a reference to `Cow` is not recommended - --> $DIR/ptr_arg.rs:87:25 + --> $DIR/ptr_arg.rs:88:25 | LL | fn test_cow_with_ref(c: &Cow<[i32]>) {} | ^^^^^^^^^^^ help: change this to: `&[i32]` +error: writing `&String` instead of `&str` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:117:66 + | +LL | fn some_allowed(#[allow(clippy::ptr_arg)] _v: &Vec, _s: &String) {} + | ^^^^^^^ help: change this to: `&str` + error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:140:21 + --> $DIR/ptr_arg.rs:146:21 | LL | fn foo_vec(vec: &Vec) { | ^^^^^^^^ @@ -125,7 +131,7 @@ LL ~ let _ = vec.to_owned().clone(); | error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:145:23 + --> $DIR/ptr_arg.rs:151:23 | LL | fn foo_path(path: &PathBuf) { | ^^^^^^^^ @@ -138,7 +144,7 @@ LL ~ let _ = path.to_path_buf().clone(); | error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:150:21 + --> $DIR/ptr_arg.rs:156:21 | LL | fn foo_str(str: &PathBuf) { | ^^^^^^^^ @@ -151,10 +157,10 @@ LL ~ let _ = str.to_path_buf().clone(); | error: writing `&mut Vec` instead of `&mut [_]` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:156:29 + --> $DIR/ptr_arg.rs:162:29 | LL | fn mut_vec_slice_methods(v: &mut Vec) { | ^^^^^^^^^^^^^ help: change this to: `&mut [u32]` -error: aborting due to 16 previous errors +error: aborting due to 17 previous errors diff --git a/tests/ui/redundant_clone.fixed b/tests/ui/redundant_clone.fixed index 1525f6a93df..da52c0acf93 100644 --- a/tests/ui/redundant_clone.fixed +++ b/tests/ui/redundant_clone.fixed @@ -1,6 +1,7 @@ // run-rustfix // rustfix-only-machine-applicable +#![feature(lint_reasons)] #![allow(clippy::implicit_clone, clippy::drop_non_drop)] use std::ffi::OsString; use std::path::Path; @@ -29,6 +30,10 @@ fn main() { #[allow(clippy::redundant_clone)] let _s = String::new().to_string(); + // Check that lint level works + #[expect(clippy::redundant_clone)] + let _s = String::new().to_string(); + let tup = (String::from("foo"),); let _t = tup.0; diff --git a/tests/ui/redundant_clone.rs b/tests/ui/redundant_clone.rs index 2f82aefd928..5867d019dbb 100644 --- a/tests/ui/redundant_clone.rs +++ b/tests/ui/redundant_clone.rs @@ -1,6 +1,7 @@ // run-rustfix // rustfix-only-machine-applicable +#![feature(lint_reasons)] #![allow(clippy::implicit_clone, clippy::drop_non_drop)] use std::ffi::OsString; use std::path::Path; @@ -29,6 +30,10 @@ fn main() { #[allow(clippy::redundant_clone)] let _s = String::new().to_string(); + // Check that lint level works + #[expect(clippy::redundant_clone)] + let _s = String::new().to_string(); + let tup = (String::from("foo"),); let _t = tup.0.clone(); diff --git a/tests/ui/redundant_clone.stderr b/tests/ui/redundant_clone.stderr index 9f59017b261..aa1dd7cbb45 100644 --- a/tests/ui/redundant_clone.stderr +++ b/tests/ui/redundant_clone.stderr @@ -1,180 +1,180 @@ error: redundant clone - --> $DIR/redundant_clone.rs:9:42 + --> $DIR/redundant_clone.rs:10:42 | LL | let _s = ["lorem", "ipsum"].join(" ").to_string(); | ^^^^^^^^^^^^ help: remove this | = note: `-D clippy::redundant-clone` implied by `-D warnings` note: this value is dropped without further use - --> $DIR/redundant_clone.rs:9:14 + --> $DIR/redundant_clone.rs:10:14 | LL | let _s = ["lorem", "ipsum"].join(" ").to_string(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:12:15 + --> $DIR/redundant_clone.rs:13:15 | LL | let _s = s.clone(); | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:12:14 + --> $DIR/redundant_clone.rs:13:14 | LL | let _s = s.clone(); | ^ error: redundant clone - --> $DIR/redundant_clone.rs:15:15 + --> $DIR/redundant_clone.rs:16:15 | LL | let _s = s.to_string(); | ^^^^^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:15:14 + --> $DIR/redundant_clone.rs:16:14 | LL | let _s = s.to_string(); | ^ error: redundant clone - --> $DIR/redundant_clone.rs:18:15 + --> $DIR/redundant_clone.rs:19:15 | LL | let _s = s.to_owned(); | ^^^^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:18:14 + --> $DIR/redundant_clone.rs:19:14 | LL | let _s = s.to_owned(); | ^ error: redundant clone - --> $DIR/redundant_clone.rs:20:42 + --> $DIR/redundant_clone.rs:21:42 | LL | let _s = Path::new("/a/b/").join("c").to_owned(); | ^^^^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:20:14 + --> $DIR/redundant_clone.rs:21:14 | LL | let _s = Path::new("/a/b/").join("c").to_owned(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:22:42 + --> $DIR/redundant_clone.rs:23:42 | LL | let _s = Path::new("/a/b/").join("c").to_path_buf(); | ^^^^^^^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:22:14 + --> $DIR/redundant_clone.rs:23:14 | LL | let _s = Path::new("/a/b/").join("c").to_path_buf(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:24:29 + --> $DIR/redundant_clone.rs:25:29 | LL | let _s = OsString::new().to_owned(); | ^^^^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:24:14 + --> $DIR/redundant_clone.rs:25:14 | LL | let _s = OsString::new().to_owned(); | ^^^^^^^^^^^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:26:29 + --> $DIR/redundant_clone.rs:27:29 | LL | let _s = OsString::new().to_os_string(); | ^^^^^^^^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:26:14 + --> $DIR/redundant_clone.rs:27:14 | LL | let _s = OsString::new().to_os_string(); | ^^^^^^^^^^^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:33:19 + --> $DIR/redundant_clone.rs:38:19 | LL | let _t = tup.0.clone(); | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:33:14 + --> $DIR/redundant_clone.rs:38:14 | LL | let _t = tup.0.clone(); | ^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:65:25 + --> $DIR/redundant_clone.rs:70:25 | LL | if b { (a.clone(), a.clone()) } else { (Alpha, a) } | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:65:24 + --> $DIR/redundant_clone.rs:70:24 | LL | if b { (a.clone(), a.clone()) } else { (Alpha, a) } | ^ error: redundant clone - --> $DIR/redundant_clone.rs:122:15 + --> $DIR/redundant_clone.rs:127:15 | LL | let _s = s.clone(); | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:122:14 + --> $DIR/redundant_clone.rs:127:14 | LL | let _s = s.clone(); | ^ error: redundant clone - --> $DIR/redundant_clone.rs:123:15 + --> $DIR/redundant_clone.rs:128:15 | LL | let _t = t.clone(); | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:123:14 + --> $DIR/redundant_clone.rs:128:14 | LL | let _t = t.clone(); | ^ error: redundant clone - --> $DIR/redundant_clone.rs:133:19 + --> $DIR/redundant_clone.rs:138:19 | LL | let _f = f.clone(); | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:133:18 + --> $DIR/redundant_clone.rs:138:18 | LL | let _f = f.clone(); | ^ error: redundant clone - --> $DIR/redundant_clone.rs:145:14 + --> $DIR/redundant_clone.rs:150:14 | LL | let y = x.clone().join("matthias"); | ^^^^^^^^ help: remove this | note: cloned value is neither consumed nor mutated - --> $DIR/redundant_clone.rs:145:13 + --> $DIR/redundant_clone.rs:150:13 | LL | let y = x.clone().join("matthias"); | ^^^^^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:199:11 + --> $DIR/redundant_clone.rs:204:11 | LL | foo(&x.clone(), move || { | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:199:10 + --> $DIR/redundant_clone.rs:204:10 | LL | foo(&x.clone(), move || { | ^ diff --git a/tests/ui/ref_binding_to_reference.rs b/tests/ui/ref_binding_to_reference.rs index 570ef406e4a..c8d0e56b197 100644 --- a/tests/ui/ref_binding_to_reference.rs +++ b/tests/ui/ref_binding_to_reference.rs @@ -2,7 +2,7 @@ #![feature(lint_reasons)] #![warn(clippy::ref_binding_to_reference)] -#![allow(clippy::needless_borrowed_reference)] +#![allow(clippy::needless_borrowed_reference, clippy::explicit_auto_deref)] fn f1(_: &str) {} macro_rules! m2 { diff --git a/tests/ui/search_is_some_fixable_none.fixed b/tests/ui/search_is_some_fixable_none.fixed index 6831fb2cf59..5190c5304c7 100644 --- a/tests/ui/search_is_some_fixable_none.fixed +++ b/tests/ui/search_is_some_fixable_none.fixed @@ -1,5 +1,5 @@ // run-rustfix -#![allow(dead_code)] +#![allow(dead_code, clippy::explicit_auto_deref)] #![warn(clippy::search_is_some)] fn main() { diff --git a/tests/ui/search_is_some_fixable_none.rs b/tests/ui/search_is_some_fixable_none.rs index 767518ab0c0..310d87333a9 100644 --- a/tests/ui/search_is_some_fixable_none.rs +++ b/tests/ui/search_is_some_fixable_none.rs @@ -1,5 +1,5 @@ // run-rustfix -#![allow(dead_code)] +#![allow(dead_code, clippy::explicit_auto_deref)] #![warn(clippy::search_is_some)] fn main() { diff --git a/tests/ui/search_is_some_fixable_some.fixed b/tests/ui/search_is_some_fixable_some.fixed index 7c940a2b069..5a2aee465d1 100644 --- a/tests/ui/search_is_some_fixable_some.fixed +++ b/tests/ui/search_is_some_fixable_some.fixed @@ -1,5 +1,5 @@ // run-rustfix -#![allow(dead_code)] +#![allow(dead_code, clippy::explicit_auto_deref)] #![warn(clippy::search_is_some)] fn main() { diff --git a/tests/ui/search_is_some_fixable_some.rs b/tests/ui/search_is_some_fixable_some.rs index 77fd52e4ce7..0e98ae18a21 100644 --- a/tests/ui/search_is_some_fixable_some.rs +++ b/tests/ui/search_is_some_fixable_some.rs @@ -1,5 +1,5 @@ // run-rustfix -#![allow(dead_code)] +#![allow(dead_code, clippy::explicit_auto_deref)] #![warn(clippy::search_is_some)] fn main() { diff --git a/tests/ui/should_impl_trait/corner_cases.rs b/tests/ui/should_impl_trait/corner_cases.rs index 1ccb0a1d167..50999c6f219 100644 --- a/tests/ui/should_impl_trait/corner_cases.rs +++ b/tests/ui/should_impl_trait/corner_cases.rs @@ -8,7 +8,8 @@ clippy::missing_safety_doc, clippy::wrong_self_convention, clippy::missing_panics_doc, - clippy::return_self_not_must_use + clippy::return_self_not_must_use, + clippy::unused_async )] use std::ops::Mul; diff --git a/tests/ui/significant_drop_in_scrutinee.rs b/tests/ui/significant_drop_in_scrutinee.rs index 4347610f393..185e5009b60 100644 --- a/tests/ui/significant_drop_in_scrutinee.rs +++ b/tests/ui/significant_drop_in_scrutinee.rs @@ -64,6 +64,19 @@ fn should_trigger_lint_with_mutex_guard_in_match_scrutinee() { }; } +fn should_not_trigger_lint_with_mutex_guard_in_match_scrutinee_when_lint_allowed() { + let mutex = Mutex::new(State {}); + + // Lint should not be triggered because it is "allowed" below. + #[allow(clippy::significant_drop_in_scrutinee)] + match mutex.lock().unwrap().foo() { + true => { + mutex.lock().unwrap().bar(); + }, + false => {}, + }; +} + fn should_not_trigger_lint_for_insignificant_drop() { // Should not trigger lint because there are no temporaries whose drops have a significant // side effect. @@ -591,4 +604,20 @@ fn should_trigger_lint_for_read_write_lock_for_loop() { } } +fn do_bar(mutex: &Mutex) { + mutex.lock().unwrap().bar(); +} + +fn should_trigger_lint_without_significant_drop_in_arm() { + let mutex = Mutex::new(State {}); + + // Should trigger lint because the lifetime of the temporary MutexGuard is surprising because it + // is preserved until the end of the match, but there is no clear indication that this is the + // case. + match mutex.lock().unwrap().foo() { + true => do_bar(&mutex), + false => {}, + }; +} + fn main() {} diff --git a/tests/ui/significant_drop_in_scrutinee.stderr b/tests/ui/significant_drop_in_scrutinee.stderr index 5ce95204416..88ea6bce25b 100644 --- a/tests/ui/significant_drop_in_scrutinee.stderr +++ b/tests/ui/significant_drop_in_scrutinee.stderr @@ -1,174 +1,290 @@ -error: temporary with significant drop in match scrutinee +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression --> $DIR/significant_drop_in_scrutinee.rs:59:11 | LL | match mutex.lock().unwrap().foo() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | true => { +LL | mutex.lock().unwrap().bar(); + | --------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here | = note: `-D clippy::significant-drop-in-scrutinee` implied by `-D warnings` + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex.lock().unwrap().foo(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:132:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:145:11 | LL | match s.lock_m().get_the_value() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | println!("{}", s.lock_m().get_the_value()); + | ---------- another value with significant `Drop` created here +... +LL | } + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = s.lock_m().get_the_value(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:153:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:166:11 | LL | match s.lock_m_m().get_the_value() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | println!("{}", s.lock_m().get_the_value()); + | ---------- another value with significant `Drop` created here +... +LL | } + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = s.lock_m_m().get_the_value(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:201:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:214:11 | LL | match counter.temp_increment().len() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = counter.temp_increment().len(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:224:16 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:237:16 | LL | match (mutex1.lock().unwrap().s.len(), true) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex1.lock().unwrap().s.len(); LL ~ match (value, true) { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:233:22 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:246:22 | LL | match (true, mutex1.lock().unwrap().s.len(), true) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex1.lock().unwrap().s.len(); LL ~ match (true, value, true) { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:243:16 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:256:16 | LL | match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex1.lock().unwrap().s.len(); LL ~ match (value, true, mutex2.lock().unwrap().s.len()) { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:243:54 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:256:54 | LL | match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex2.lock().unwrap().s.len(); LL ~ match (mutex1.lock().unwrap().s.len(), true, value) { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:254:15 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:267:15 | LL | match mutex3.lock().unwrap().s.as_str() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:264:22 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:277:22 | LL | match (true, mutex3.lock().unwrap().s.as_str()) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:283:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:296:11 | LL | match mutex.lock().unwrap().s.len() > 1 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | true => { +LL | mutex.lock().unwrap().s.len(); + | --------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex.lock().unwrap().s.len() > 1; LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:290:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:303:11 | LL | match 1 < mutex.lock().unwrap().s.len() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | true => { +LL | mutex.lock().unwrap().s.len(); + | --------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = 1 < mutex.lock().unwrap().s.len(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:308:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:321:11 | LL | match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mutex1.lock().unwrap().s.len(), + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len() + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:319:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:332:11 | LL | match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mutex1.lock().unwrap().s.len(), + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len() + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:354:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:367:11 | LL | match get_mutex_guard().s.len() > 1 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | true => { +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = get_mutex_guard().s.len() > 1; LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:371:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:384:11 | LL | match match i { | ___________^ @@ -179,7 +295,14 @@ LL | | .s LL | | .len() LL | | > 1 | |___________^ +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = match i { @@ -192,8 +315,8 @@ LL + > 1; LL ~ match value | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:397:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:410:11 | LL | match if i > 1 { | ___________^ @@ -204,7 +327,14 @@ LL | | mutex2.lock().unwrap() LL | | .len() LL | | > 1 | |___________^ +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = if i > 1 { @@ -218,83 +348,150 @@ LL + > 1; LL ~ match value | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:451:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:464:11 | LL | match s.lock().deref().deref() { | ^^^^^^^^^^^^^^^^^^^^^^^^ +LL | 0 | 1 => println!("Value was less than 2"), +LL | _ => println!("Value is {}", s.lock().deref()), + | ---------------- another value with significant `Drop` created here +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match and create a copy | LL ~ let value = *s.lock().deref().deref(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:479:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:492:11 | LL | match s.lock().deref().deref() { | ^^^^^^^^^^^^^^^^^^^^^^^^ +LL | matcher => println!("Value is {}", s.lock().deref()), + | ---------------- another value with significant `Drop` created here +LL | _ => println!("Value was not a match"), +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:498:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:511:11 | LL | match mutex.lock().unwrap().i = i { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | _ => { +LL | println!("{}", mutex.lock().unwrap().i); + | --------------------- another value with significant `Drop` created here +LL | }, +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ mutex.lock().unwrap().i = i; LL ~ match () { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:504:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:517:11 | LL | match i = mutex.lock().unwrap().i { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | _ => { +LL | println!("{}", mutex.lock().unwrap().i); + | --------------------- another value with significant `Drop` created here +LL | }, +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ i = mutex.lock().unwrap().i; LL ~ match () { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:510:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:523:11 | LL | match mutex.lock().unwrap().i += 1 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | _ => { +LL | println!("{}", mutex.lock().unwrap().i); + | --------------------- another value with significant `Drop` created here +LL | }, +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ mutex.lock().unwrap().i += 1; LL ~ match () { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:516:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:529:11 | LL | match i += mutex.lock().unwrap().i { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | _ => { +LL | println!("{}", mutex.lock().unwrap().i); + | --------------------- another value with significant `Drop` created here +LL | }, +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ i += mutex.lock().unwrap().i; LL ~ match () { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:579:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:592:11 | LL | match rwlock.read().unwrap().to_number() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior -error: temporary with significant drop in for loop - --> $DIR/significant_drop_in_scrutinee.rs:589:14 +error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression + --> $DIR/significant_drop_in_scrutinee.rs:602:14 | LL | for s in rwlock.read().unwrap().iter() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | println!("{}", s); +LL | } + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior -error: aborting due to 25 previous errors +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:617:11 + | +LL | match mutex.lock().unwrap().foo() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap().foo(); +LL ~ match value { + | + +error: aborting due to 26 previous errors diff --git a/tests/ui/single_match.stderr b/tests/ui/single_match.stderr index 318faf25717..4d2b9ec5f90 100644 --- a/tests/ui/single_match.stderr +++ b/tests/ui/single_match.stderr @@ -38,6 +38,15 @@ LL | | _ => {}, LL | | }; | |_____^ help: try this: `if let (2..=3, 7..=9) = z { dummy() }` +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:54:5 + | +LL | / match x { +LL | | Some(y) => dummy(), +LL | | None => (), +LL | | }; + | |_____^ help: try this: `if let Some(y) = x { dummy() }` + error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` --> $DIR/single_match.rs:59:5 | @@ -146,5 +155,5 @@ LL | | (..) => {}, LL | | } | |_____^ help: try this: `if let (.., Some(E::V), _) = (Some(42), Some(E::V), Some(42)) {}` -error: aborting due to 15 previous errors +error: aborting due to 16 previous errors diff --git a/tests/ui/single_match_else.rs b/tests/ui/single_match_else.rs index 82387f3d80b..70d6febb71f 100644 --- a/tests/ui/single_match_else.rs +++ b/tests/ui/single_match_else.rs @@ -97,4 +97,23 @@ fn main() { return; }, } + + // lint here + use std::convert::Infallible; + match Result::::Ok(1) { + Ok(a) => println!("${:?}", a), + Err(_) => { + println!("else block"); + return; + } + } + + use std::borrow::Cow; + match Cow::from("moo") { + Cow::Owned(a) => println!("${:?}", a), + Cow::Borrowed(_) => { + println!("else block"); + return; + } + } } diff --git a/tests/ui/single_match_else.stderr b/tests/ui/single_match_else.stderr index 7756c6f204e..38fd9c6a678 100644 --- a/tests/ui/single_match_else.stderr +++ b/tests/ui/single_match_else.stderr @@ -20,5 +20,85 @@ LL + None LL ~ }; | -error: aborting due to previous error +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:84:5 + | +LL | / match Some(1) { +LL | | Some(a) => println!("${:?}", a), +LL | | None => { +LL | | println!("else block"); +LL | | return +LL | | }, +LL | | } + | |_____^ + | +help: try this + | +LL ~ if let Some(a) = Some(1) { println!("${:?}", a) } else { +LL + println!("else block"); +LL + return +LL + } + | + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:93:5 + | +LL | / match Some(1) { +LL | | Some(a) => println!("${:?}", a), +LL | | None => { +LL | | println!("else block"); +LL | | return; +LL | | }, +LL | | } + | |_____^ + | +help: try this + | +LL ~ if let Some(a) = Some(1) { println!("${:?}", a) } else { +LL + println!("else block"); +LL + return; +LL + } + | + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:103:5 + | +LL | / match Result::::Ok(1) { +LL | | Ok(a) => println!("${:?}", a), +LL | | Err(_) => { +LL | | println!("else block"); +LL | | return; +LL | | } +LL | | } + | |_____^ + | +help: try this + | +LL ~ if let Ok(a) = Result::::Ok(1) { println!("${:?}", a) } else { +LL + println!("else block"); +LL + return; +LL + } + | + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:112:5 + | +LL | / match Cow::from("moo") { +LL | | Cow::Owned(a) => println!("${:?}", a), +LL | | Cow::Borrowed(_) => { +LL | | println!("else block"); +LL | | return; +LL | | } +LL | | } + | |_____^ + | +help: try this + | +LL ~ if let Cow::Owned(a) = Cow::from("moo") { println!("${:?}", a) } else { +LL + println!("else block"); +LL + return; +LL + } + | + +error: aborting due to 5 previous errors diff --git a/tests/ui/slow_vector_initialization.rs b/tests/ui/slow_vector_initialization.rs index 7e3d357ae50..16be9f6d203 100644 --- a/tests/ui/slow_vector_initialization.rs +++ b/tests/ui/slow_vector_initialization.rs @@ -19,6 +19,9 @@ fn extend_vector() { // Extend with mismatching expression should not be warned let mut vec3 = Vec::with_capacity(24322); vec3.extend(repeat(0).take(2)); + + let mut vec4 = Vec::with_capacity(len); + vec4.extend(repeat(0).take(vec4.capacity())); } fn mixed_extend_resize_vector() { @@ -48,6 +51,9 @@ fn resize_vector() { let mut vec3 = Vec::with_capacity(len - 10); vec3.resize(len - 10, 0); + let mut vec4 = Vec::with_capacity(len); + vec4.resize(vec4.capacity(), 0); + // Reinitialization should be warned vec1 = Vec::with_capacity(10); vec1.resize(10, 0); diff --git a/tests/ui/slow_vector_initialization.stderr b/tests/ui/slow_vector_initialization.stderr index 5d2788ec260..cb3ce3e95a7 100644 --- a/tests/ui/slow_vector_initialization.stderr +++ b/tests/ui/slow_vector_initialization.stderr @@ -17,7 +17,15 @@ LL | vec2.extend(repeat(0).take(len - 10)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:31:5 + --> $DIR/slow_vector_initialization.rs:24:5 + | +LL | let mut vec4 = Vec::with_capacity(len); + | ----------------------- help: consider replace allocation with: `vec![0; len]` +LL | vec4.extend(repeat(0).take(vec4.capacity())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:34:5 | LL | let mut resized_vec = Vec::with_capacity(30); | ---------------------- help: consider replace allocation with: `vec![0; 30]` @@ -25,7 +33,7 @@ LL | resized_vec.resize(30, 0); | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:34:5 + --> $DIR/slow_vector_initialization.rs:37:5 | LL | let mut extend_vec = Vec::with_capacity(30); | ---------------------- help: consider replace allocation with: `vec![0; 30]` @@ -33,7 +41,7 @@ LL | extend_vec.extend(repeat(0).take(30)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:41:5 + --> $DIR/slow_vector_initialization.rs:44:5 | LL | let mut vec1 = Vec::with_capacity(len); | ----------------------- help: consider replace allocation with: `vec![0; len]` @@ -41,7 +49,7 @@ LL | vec1.resize(len, 0); | ^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:49:5 + --> $DIR/slow_vector_initialization.rs:52:5 | LL | let mut vec3 = Vec::with_capacity(len - 10); | ---------------------------- help: consider replace allocation with: `vec![0; len - 10]` @@ -49,12 +57,20 @@ LL | vec3.resize(len - 10, 0); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:53:5 + --> $DIR/slow_vector_initialization.rs:55:5 + | +LL | let mut vec4 = Vec::with_capacity(len); + | ----------------------- help: consider replace allocation with: `vec![0; len]` +LL | vec4.resize(vec4.capacity(), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:59:5 | LL | vec1 = Vec::with_capacity(10); | ---------------------- help: consider replace allocation with: `vec![0; 10]` LL | vec1.resize(10, 0); | ^^^^^^^^^^^^^^^^^^ -error: aborting due to 7 previous errors +error: aborting due to 9 previous errors diff --git a/tests/ui/transmute_ptr_to_ref.fixed b/tests/ui/transmute_ptr_to_ref.fixed new file mode 100644 index 00000000000..e5fe9133f97 --- /dev/null +++ b/tests/ui/transmute_ptr_to_ref.fixed @@ -0,0 +1,78 @@ +// run-rustfix + +#![feature(custom_inner_attributes)] +#![warn(clippy::transmute_ptr_to_ref)] +#![allow(clippy::match_single_binding)] + +unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { + let _: &T = &*p; + let _: &T = &*p; + + let _: &mut T = &mut *m; + let _: &mut T = &mut *m; + + let _: &T = &*m; + let _: &T = &*m; + + let _: &mut T = &mut *(p as *mut T); + let _ = &mut *(p as *mut T); + + let _: &T = &*(o as *const T); + let _: &T = &*(o as *const T); + + let _: &mut T = &mut *(om as *mut T); + let _: &mut T = &mut *(om as *mut T); + + let _: &T = &*(om as *const T); + let _: &T = &*(om as *const T); +} + +fn _issue1231() { + struct Foo<'a, T> { + bar: &'a T, + } + + let raw = 42 as *const i32; + let _: &Foo = unsafe { &*raw.cast::>() }; + + let _: &Foo<&u8> = unsafe { &*raw.cast::>() }; + + type Bar<'a> = &'a u8; + let raw = 42 as *const i32; + unsafe { &*(raw as *const u8) }; +} + +unsafe fn _issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 { + match 0 { + 0 => &*x.cast::<&u32>(), + 1 => &*y.cast::<&u32>(), + 2 => &*x.cast::<&'b u32>(), + _ => &*y.cast::<&'b u32>(), + } +} + +unsafe fn _meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { + #![clippy::msrv = "1.38"] + let a = 0u32; + let a = &a as *const u32; + let _: &u32 = &*a; + let _: &u32 = &*a.cast::(); + match 0 { + 0 => &*x.cast::<&u32>(), + _ => &*x.cast::<&'b u32>(), + } +} + +unsafe fn _under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { + #![clippy::msrv = "1.37"] + let a = 0u32; + let a = &a as *const u32; + let _: &u32 = &*a; + let _: &u32 = &*(a as *const u32); + match 0 { + 0 => &*(x as *const () as *const &u32), + _ => &*(x as *const () as *const &'b u32), + } +} + +fn main() {} diff --git a/tests/ui/transmute_ptr_to_ref.rs b/tests/ui/transmute_ptr_to_ref.rs index ba35c6adc4d..fe49cdc324f 100644 --- a/tests/ui/transmute_ptr_to_ref.rs +++ b/tests/ui/transmute_ptr_to_ref.rs @@ -1,4 +1,8 @@ +// run-rustfix + +#![feature(custom_inner_attributes)] #![warn(clippy::transmute_ptr_to_ref)] +#![allow(clippy::match_single_binding)] unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { let _: &T = std::mem::transmute(p); @@ -23,7 +27,7 @@ unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { let _: &T = &*(om as *const T); } -fn issue1231() { +fn _issue1231() { struct Foo<'a, T> { bar: &'a T, } @@ -38,4 +42,37 @@ struct Foo<'a, T> { unsafe { std::mem::transmute::<_, Bar>(raw) }; } +unsafe fn _issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 { + match 0 { + 0 => std::mem::transmute(x), + 1 => std::mem::transmute(y), + 2 => std::mem::transmute::<_, &&'b u32>(x), + _ => std::mem::transmute::<_, &&'b u32>(y), + } +} + +unsafe fn _meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { + #![clippy::msrv = "1.38"] + let a = 0u32; + let a = &a as *const u32; + let _: &u32 = std::mem::transmute(a); + let _: &u32 = std::mem::transmute::<_, &u32>(a); + match 0 { + 0 => std::mem::transmute(x), + _ => std::mem::transmute::<_, &&'b u32>(x), + } +} + +unsafe fn _under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { + #![clippy::msrv = "1.37"] + let a = 0u32; + let a = &a as *const u32; + let _: &u32 = std::mem::transmute(a); + let _: &u32 = std::mem::transmute::<_, &u32>(a); + match 0 { + 0 => std::mem::transmute(x), + _ => std::mem::transmute::<_, &&'b u32>(x), + } +} + fn main() {} diff --git a/tests/ui/transmute_ptr_to_ref.stderr b/tests/ui/transmute_ptr_to_ref.stderr index df0598a58cd..2993e5e7b0c 100644 --- a/tests/ui/transmute_ptr_to_ref.stderr +++ b/tests/ui/transmute_ptr_to_ref.stderr @@ -1,5 +1,5 @@ error: transmute from a pointer type (`*const T`) to a reference type (`&T`) - --> $DIR/transmute_ptr_to_ref.rs:4:17 + --> $DIR/transmute_ptr_to_ref.rs:8:17 | LL | let _: &T = std::mem::transmute(p); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*p` @@ -7,58 +7,130 @@ LL | let _: &T = std::mem::transmute(p); = note: `-D clippy::transmute-ptr-to-ref` implied by `-D warnings` error: transmute from a pointer type (`*mut T`) to a reference type (`&mut T`) - --> $DIR/transmute_ptr_to_ref.rs:7:21 + --> $DIR/transmute_ptr_to_ref.rs:11:21 | LL | let _: &mut T = std::mem::transmute(m); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *m` error: transmute from a pointer type (`*mut T`) to a reference type (`&T`) - --> $DIR/transmute_ptr_to_ref.rs:10:17 + --> $DIR/transmute_ptr_to_ref.rs:14:17 | LL | let _: &T = std::mem::transmute(m); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*m` error: transmute from a pointer type (`*mut T`) to a reference type (`&mut T`) - --> $DIR/transmute_ptr_to_ref.rs:13:21 + --> $DIR/transmute_ptr_to_ref.rs:17:21 | LL | let _: &mut T = std::mem::transmute(p as *mut T); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *(p as *mut T)` error: transmute from a pointer type (`*const U`) to a reference type (`&T`) - --> $DIR/transmute_ptr_to_ref.rs:16:17 + --> $DIR/transmute_ptr_to_ref.rs:20:17 | LL | let _: &T = std::mem::transmute(o); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(o as *const T)` error: transmute from a pointer type (`*mut U`) to a reference type (`&mut T`) - --> $DIR/transmute_ptr_to_ref.rs:19:21 + --> $DIR/transmute_ptr_to_ref.rs:23:21 | LL | let _: &mut T = std::mem::transmute(om); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *(om as *mut T)` error: transmute from a pointer type (`*mut U`) to a reference type (`&T`) - --> $DIR/transmute_ptr_to_ref.rs:22:17 + --> $DIR/transmute_ptr_to_ref.rs:26:17 | LL | let _: &T = std::mem::transmute(om); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(om as *const T)` -error: transmute from a pointer type (`*const i32`) to a reference type (`&issue1231::Foo`) - --> $DIR/transmute_ptr_to_ref.rs:32:32 +error: transmute from a pointer type (`*const i32`) to a reference type (`&_issue1231::Foo`) + --> $DIR/transmute_ptr_to_ref.rs:36:32 | LL | let _: &Foo = unsafe { std::mem::transmute::<_, &Foo<_>>(raw) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(raw as *const Foo<_>)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*raw.cast::>()` -error: transmute from a pointer type (`*const i32`) to a reference type (`&issue1231::Foo<&u8>`) - --> $DIR/transmute_ptr_to_ref.rs:34:33 +error: transmute from a pointer type (`*const i32`) to a reference type (`&_issue1231::Foo<&u8>`) + --> $DIR/transmute_ptr_to_ref.rs:38:33 | LL | let _: &Foo<&u8> = unsafe { std::mem::transmute::<_, &Foo<&_>>(raw) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(raw as *const Foo<&_>)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*raw.cast::>()` error: transmute from a pointer type (`*const i32`) to a reference type (`&u8`) - --> $DIR/transmute_ptr_to_ref.rs:38:14 + --> $DIR/transmute_ptr_to_ref.rs:42:14 | LL | unsafe { std::mem::transmute::<_, Bar>(raw) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(raw as *const u8)` -error: aborting due to 10 previous errors +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:47:14 + | +LL | 0 => std::mem::transmute(x), + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*x.cast::<&u32>()` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:48:14 + | +LL | 1 => std::mem::transmute(y), + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*y.cast::<&u32>()` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:49:14 + | +LL | 2 => std::mem::transmute::<_, &&'b u32>(x), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*x.cast::<&'b u32>()` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:50:14 + | +LL | _ => std::mem::transmute::<_, &&'b u32>(y), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*y.cast::<&'b u32>()` + +error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`) + --> $DIR/transmute_ptr_to_ref.rs:58:19 + | +LL | let _: &u32 = std::mem::transmute(a); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*a` + +error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`) + --> $DIR/transmute_ptr_to_ref.rs:59:19 + | +LL | let _: &u32 = std::mem::transmute::<_, &u32>(a); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*a.cast::()` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:61:14 + | +LL | 0 => std::mem::transmute(x), + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*x.cast::<&u32>()` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:62:14 + | +LL | _ => std::mem::transmute::<_, &&'b u32>(x), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*x.cast::<&'b u32>()` + +error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`) + --> $DIR/transmute_ptr_to_ref.rs:70:19 + | +LL | let _: &u32 = std::mem::transmute(a); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*a` + +error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`) + --> $DIR/transmute_ptr_to_ref.rs:71:19 + | +LL | let _: &u32 = std::mem::transmute::<_, &u32>(a); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a as *const u32)` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:73:14 + | +LL | 0 => std::mem::transmute(x), + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(x as *const () as *const &u32)` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:74:14 + | +LL | _ => std::mem::transmute::<_, &&'b u32>(x), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(x as *const () as *const &'b u32)` + +error: aborting due to 22 previous errors diff --git a/tests/ui/trivially_copy_pass_by_ref.rs b/tests/ui/trivially_copy_pass_by_ref.rs index ea3dce17081..8f78f16a0a1 100644 --- a/tests/ui/trivially_copy_pass_by_ref.rs +++ b/tests/ui/trivially_copy_pass_by_ref.rs @@ -113,6 +113,44 @@ fn foo(x: &i32) { } } +fn _ref_to_opt_ref_implicit(x: &u32) -> Option<&u32> { + Some(x) +} + +#[allow(clippy::needless_lifetimes)] +fn _ref_to_opt_ref_explicit<'a>(x: &'a u32) -> Option<&'a u32> { + Some(x) +} + +fn _with_constraint<'a, 'b: 'a>(x: &'b u32, y: &'a u32) -> &'a u32 { + if true { x } else { y } +} + +async fn _async_implicit(x: &u32) -> &u32 { + x +} + +#[allow(clippy::needless_lifetimes)] +async fn _async_explicit<'a>(x: &'a u32) -> &'a u32 { + x +} + +fn _unrelated_lifetimes<'a, 'b>(_x: &'a u32, y: &'b u32) -> &'b u32 { + y +} + +fn _return_ptr(x: &u32) -> *const u32 { + x +} + +fn _return_field_ptr(x: &(u32, u32)) -> *const u32 { + &x.0 +} + +fn _return_field_ptr_addr_of(x: &(u32, u32)) -> *const u32 { + core::ptr::addr_of!(x.0) +} + fn main() { let (mut foo, bar) = (Foo(0), Bar([0; 24])); let (mut a, b, c, x, y, z) = (0, 0, Bar([0; 24]), 0, Foo(0), 0); diff --git a/tests/ui/trivially_copy_pass_by_ref.stderr b/tests/ui/trivially_copy_pass_by_ref.stderr index a88d35f3ea5..66ecb3d8e77 100644 --- a/tests/ui/trivially_copy_pass_by_ref.stderr +++ b/tests/ui/trivially_copy_pass_by_ref.stderr @@ -106,5 +106,11 @@ error: this argument (N byte) is passed by reference, but would be more efficien LL | fn foo(x: &i32) { | ^^^^ help: consider passing by value instead: `i32` -error: aborting due to 17 previous errors +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:138:37 + | +LL | fn _unrelated_lifetimes<'a, 'b>(_x: &'a u32, y: &'b u32) -> &'b u32 { + | ^^^^^^^ help: consider passing by value instead: `u32` + +error: aborting due to 18 previous errors diff --git a/tests/ui/unused_async.rs b/tests/ui/unused_async.rs index 2a3a506a57b..4ca7f29b34c 100644 --- a/tests/ui/unused_async.rs +++ b/tests/ui/unused_async.rs @@ -1,5 +1,8 @@ #![warn(clippy::unused_async)] +use std::future::Future; +use std::pin::Pin; + async fn foo() -> i32 { 4 } @@ -8,6 +11,37 @@ async fn bar() -> i32 { foo().await } +struct S; + +impl S { + async fn unused(&self) -> i32 { + 1 + } + + async fn used(&self) -> i32 { + self.unused().await + } +} + +trait AsyncTrait { + fn trait_method() -> Pin>>; +} + +macro_rules! async_trait_impl { + () => { + impl AsyncTrait for S { + fn trait_method() -> Pin>> { + async fn unused() -> i32 { + 5 + } + + Box::pin(unused()) + } + } + }; +} +async_trait_impl!(); + fn main() { foo(); bar(); diff --git a/tests/ui/unused_async.stderr b/tests/ui/unused_async.stderr index cc6096d65d9..8b8ad065a4c 100644 --- a/tests/ui/unused_async.stderr +++ b/tests/ui/unused_async.stderr @@ -1,5 +1,5 @@ error: unused `async` for function with no await statements - --> $DIR/unused_async.rs:3:1 + --> $DIR/unused_async.rs:6:1 | LL | / async fn foo() -> i32 { LL | | 4 @@ -9,5 +9,15 @@ LL | | } = note: `-D clippy::unused-async` implied by `-D warnings` = help: consider removing the `async` from this function -error: aborting due to previous error +error: unused `async` for function with no await statements + --> $DIR/unused_async.rs:17:5 + | +LL | / async fn unused(&self) -> i32 { +LL | | 1 +LL | | } + | |_____^ + | + = help: consider removing the `async` from this function + +error: aborting due to 2 previous errors diff --git a/tests/ui/useless_asref.fixed b/tests/ui/useless_asref.fixed index e431661d180..90cb8945e77 100644 --- a/tests/ui/useless_asref.fixed +++ b/tests/ui/useless_asref.fixed @@ -1,6 +1,7 @@ // run-rustfix #![deny(clippy::useless_asref)] +#![allow(clippy::explicit_auto_deref)] use std::fmt::Debug; diff --git a/tests/ui/useless_asref.rs b/tests/ui/useless_asref.rs index 6ae931d7aa4..cb9f8ae5909 100644 --- a/tests/ui/useless_asref.rs +++ b/tests/ui/useless_asref.rs @@ -1,6 +1,7 @@ // run-rustfix #![deny(clippy::useless_asref)] +#![allow(clippy::explicit_auto_deref)] use std::fmt::Debug; diff --git a/tests/ui/useless_asref.stderr b/tests/ui/useless_asref.stderr index 5876b54aca8..b21c67bb364 100644 --- a/tests/ui/useless_asref.stderr +++ b/tests/ui/useless_asref.stderr @@ -1,5 +1,5 @@ error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:43:18 + --> $DIR/useless_asref.rs:44:18 | LL | foo_rstr(rstr.as_ref()); | ^^^^^^^^^^^^^ help: try this: `rstr` @@ -11,61 +11,61 @@ LL | #![deny(clippy::useless_asref)] | ^^^^^^^^^^^^^^^^^^^^^ error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:45:20 + --> $DIR/useless_asref.rs:46:20 | LL | foo_rslice(rslice.as_ref()); | ^^^^^^^^^^^^^^^ help: try this: `rslice` error: this call to `as_mut` does nothing - --> $DIR/useless_asref.rs:49:21 + --> $DIR/useless_asref.rs:50:21 | LL | foo_mrslice(mrslice.as_mut()); | ^^^^^^^^^^^^^^^^ help: try this: `mrslice` error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:51:20 + --> $DIR/useless_asref.rs:52:20 | LL | foo_rslice(mrslice.as_ref()); | ^^^^^^^^^^^^^^^^ help: try this: `mrslice` error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:58:20 + --> $DIR/useless_asref.rs:59:20 | LL | foo_rslice(rrrrrslice.as_ref()); | ^^^^^^^^^^^^^^^^^^^ help: try this: `rrrrrslice` error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:60:18 + --> $DIR/useless_asref.rs:61:18 | LL | foo_rstr(rrrrrstr.as_ref()); | ^^^^^^^^^^^^^^^^^ help: try this: `rrrrrstr` error: this call to `as_mut` does nothing - --> $DIR/useless_asref.rs:65:21 + --> $DIR/useless_asref.rs:66:21 | LL | foo_mrslice(mrrrrrslice.as_mut()); | ^^^^^^^^^^^^^^^^^^^^ help: try this: `mrrrrrslice` error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:67:20 + --> $DIR/useless_asref.rs:68:20 | LL | foo_rslice(mrrrrrslice.as_ref()); | ^^^^^^^^^^^^^^^^^^^^ help: try this: `mrrrrrslice` error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:71:16 + --> $DIR/useless_asref.rs:72:16 | LL | foo_rrrrmr((&&&&MoreRef).as_ref()); | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `(&&&&MoreRef)` error: this call to `as_mut` does nothing - --> $DIR/useless_asref.rs:121:13 + --> $DIR/useless_asref.rs:122:13 | LL | foo_mrt(mrt.as_mut()); | ^^^^^^^^^^^^ help: try this: `mrt` error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:123:12 + --> $DIR/useless_asref.rs:124:12 | LL | foo_rt(mrt.as_ref()); | ^^^^^^^^^^^^ help: try this: `mrt` diff --git a/tests/ui/while_let_loop.rs b/tests/ui/while_let_loop.rs index 3ce699f551b..c42e2a79a9b 100644 --- a/tests/ui/while_let_loop.rs +++ b/tests/ui/while_let_loop.rs @@ -117,3 +117,29 @@ fn issue1948() { } }; } + +fn issue_7913(m: &std::sync::Mutex>) { + // Don't lint. The lock shouldn't be held while printing. + loop { + let x = if let Some(x) = m.lock().unwrap().pop() { + x + } else { + break; + }; + + println!("{}", x); + } +} + +fn issue_5715(mut m: core::cell::RefCell>) { + // Don't lint. The temporary from `borrow_mut` must be dropped before overwriting the `RefCell`. + loop { + let x = if let &mut Some(x) = &mut *m.borrow_mut() { + x + } else { + break; + }; + + m = core::cell::RefCell::new(Some(x + 1)); + } +} diff --git a/tests/ui/while_let_on_iterator.fixed b/tests/ui/while_let_on_iterator.fixed index cb8892a3f00..e9ff64431e1 100644 --- a/tests/ui/while_let_on_iterator.fixed +++ b/tests/ui/while_let_on_iterator.fixed @@ -6,7 +6,8 @@ unreachable_code, unused_mut, dead_code, - clippy::equatable_if_let + clippy::equatable_if_let, + clippy::manual_find )] fn base() { diff --git a/tests/ui/while_let_on_iterator.rs b/tests/ui/while_let_on_iterator.rs index d9157184495..03da39526b2 100644 --- a/tests/ui/while_let_on_iterator.rs +++ b/tests/ui/while_let_on_iterator.rs @@ -6,7 +6,8 @@ unreachable_code, unused_mut, dead_code, - clippy::equatable_if_let + clippy::equatable_if_let, + clippy::manual_find )] fn base() { diff --git a/tests/ui/while_let_on_iterator.stderr b/tests/ui/while_let_on_iterator.stderr index fb2b0f2467c..42859243855 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:14:5 + --> $DIR/while_let_on_iterator.rs:15:5 | LL | while let Option::Some(x) = iter.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in iter` @@ -7,85 +7,85 @@ 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:19:5 + --> $DIR/while_let_on_iterator.rs:20: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:24:5 + --> $DIR/while_let_on_iterator.rs:25: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:100:9 + --> $DIR/while_let_on_iterator.rs:101: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:107:9 + --> $DIR/while_let_on_iterator.rs:108: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:120:9 + --> $DIR/while_let_on_iterator.rs:121: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:140:9 + --> $DIR/while_let_on_iterator.rs:141: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:197:9 + --> $DIR/while_let_on_iterator.rs:198: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:208:5 + --> $DIR/while_let_on_iterator.rs:209: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:210:9 + --> $DIR/while_let_on_iterator.rs:211: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:219:9 + --> $DIR/while_let_on_iterator.rs:220: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:228:9 + --> $DIR/while_let_on_iterator.rs:229: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:245:9 + --> $DIR/while_let_on_iterator.rs:246: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:260:13 + --> $DIR/while_let_on_iterator.rs:261: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:261:20 + --> $DIR/while_let_on_iterator.rs:262:20 | LL | if i < 3 || i > 7 { | ^^^^^^^^^^^^^^ help: use: `!(3..=7).contains(&i)` @@ -93,49 +93,49 @@ LL | if i < 3 || i > 7 { = 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:292:13 + --> $DIR/while_let_on_iterator.rs:293: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:321:5 + --> $DIR/while_let_on_iterator.rs:322: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:333:9 + --> $DIR/while_let_on_iterator.rs:334: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:347:5 + --> $DIR/while_let_on_iterator.rs:348: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:358:5 + --> $DIR/while_let_on_iterator.rs:359: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:393:5 + --> $DIR/while_let_on_iterator.rs:394: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:400:5 + --> $DIR/while_let_on_iterator.rs:401: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:407:5 + --> $DIR/while_let_on_iterator.rs:408:5 | LL | while let Some(..) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in it` diff --git a/tests/ui/zero_div_zero.rs b/tests/ui/zero_div_zero.rs index ed3a9208fc3..968c58f40ae 100644 --- a/tests/ui/zero_div_zero.rs +++ b/tests/ui/zero_div_zero.rs @@ -1,4 +1,4 @@ -#[allow(unused_variables)] +#[allow(unused_variables, clippy::eq_op)] #[warn(clippy::zero_divided_by_zero)] fn main() { let nan = 0.0 / 0.0; diff --git a/tests/ui/zero_div_zero.stderr b/tests/ui/zero_div_zero.stderr index 0931dd32e7a..86563542e06 100644 --- a/tests/ui/zero_div_zero.stderr +++ b/tests/ui/zero_div_zero.stderr @@ -1,11 +1,3 @@ -error: equal expressions as operands to `/` - --> $DIR/zero_div_zero.rs:4:15 - | -LL | let nan = 0.0 / 0.0; - | ^^^^^^^^^ - | - = note: `#[deny(clippy::eq_op)]` on by default - error: constant division of `0.0` with `0.0` will always result in NaN --> $DIR/zero_div_zero.rs:4:15 | @@ -15,12 +7,6 @@ LL | let nan = 0.0 / 0.0; = note: `-D clippy::zero-divided-by-zero` implied by `-D warnings` = help: consider using `f64::NAN` if you would like a constant representing NaN -error: equal expressions as operands to `/` - --> $DIR/zero_div_zero.rs:5:19 - | -LL | let f64_nan = 0.0 / 0.0f64; - | ^^^^^^^^^^^^ - error: constant division of `0.0` with `0.0` will always result in NaN --> $DIR/zero_div_zero.rs:5:19 | @@ -29,12 +15,6 @@ LL | let f64_nan = 0.0 / 0.0f64; | = help: consider using `f64::NAN` if you would like a constant representing NaN -error: equal expressions as operands to `/` - --> $DIR/zero_div_zero.rs:6:25 - | -LL | let other_f64_nan = 0.0f64 / 0.0; - | ^^^^^^^^^^^^ - error: constant division of `0.0` with `0.0` will always result in NaN --> $DIR/zero_div_zero.rs:6:25 | @@ -43,12 +23,6 @@ LL | let other_f64_nan = 0.0f64 / 0.0; | = help: consider using `f64::NAN` if you would like a constant representing NaN -error: equal expressions as operands to `/` - --> $DIR/zero_div_zero.rs:7:28 - | -LL | let one_more_f64_nan = 0.0f64 / 0.0f64; - | ^^^^^^^^^^^^^^^ - error: constant division of `0.0` with `0.0` will always result in NaN --> $DIR/zero_div_zero.rs:7:28 | @@ -57,5 +31,5 @@ LL | let one_more_f64_nan = 0.0f64 / 0.0f64; | = help: consider using `f64::NAN` if you would like a constant representing NaN -error: aborting due to 8 previous errors +error: aborting due to 4 previous errors diff --git a/util/gh-pages/index.html b/util/gh-pages/index.html index 4999cce7511..6183089911b 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index.html @@ -10,6 +10,7 @@ Otherwise, have a great day =^.^= + Clippy Lints From a131ceab664702ee264f5144d597403e2e653cb0 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 30 Jun 2022 11:37:48 +0200 Subject: [PATCH 05/84] Make sure bors success depends on metadata_collection --- .github/workflows/clippy_bors.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index b8ea424ef34..97453303cd6 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 From 6c61f7106f2de290df22047cdb15de30a140543e Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Thu, 30 Jun 2022 10:13:54 +0000 Subject: [PATCH 06/84] Uncomment test for #8734 --- tests/ui/map_flatten_fixable.fixed | 29 +++++++++++++-------------- tests/ui/map_flatten_fixable.rs | 30 ++++++++++++++-------------- tests/ui/map_flatten_fixable.stderr | 31 ++++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 31 deletions(-) diff --git a/tests/ui/map_flatten_fixable.fixed b/tests/ui/map_flatten_fixable.fixed index e9b41354c58..9097a6b9967 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 diff --git a/tests/ui/map_flatten_fixable.rs b/tests/ui/map_flatten_fixable.rs index 4345c6eee74..3916263404a 100644 --- a/tests/ui/map_flatten_fixable.rs +++ b/tests/ui/map_flatten_fixable.rs @@ -34,21 +34,21 @@ fn option_id(x: i8) -> Option { } 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 diff --git a/tests/ui/map_flatten_fixable.stderr b/tests/ui/map_flatten_fixable.stderr index f3b82ad08d0..afaf6af66b2 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 | @@ -66,5 +95,5 @@ LL + // whitespace beforehand is important as well LL ~ }); | -error: aborting due to 8 previous errors +error: aborting due to 9 previous errors From b7051077c9d7e73704c4ea83f38f2a2e9f2003c1 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Thu, 30 Jun 2022 17:45:34 +0200 Subject: [PATCH 07/84] Fix false-positive in `equatable_if_let` --- clippy_lints/src/equatable_if_let.rs | 4 +++- tests/ui/auxiliary/macro_rules.rs | 5 +++++ tests/ui/equatable_if_let.fixed | 6 ++++++ tests/ui/equatable_if_let.rs | 6 ++++++ tests/ui/equatable_if_let.stderr | 22 +++++++++++----------- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/equatable_if_let.rs b/clippy_lints/src/equatable_if_let.rs index ef1216358dd..fdfb821ac78 100644 --- a/clippy_lints/src/equatable_if_let.rs +++ b/clippy_lints/src/equatable_if_let.rs @@ -4,7 +4,8 @@ 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/tests/ui/auxiliary/macro_rules.rs b/tests/ui/auxiliary/macro_rules.rs index 5bd2c2799f0..83a0af6b87a 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/equatable_if_let.fixed b/tests/ui/equatable_if_let.fixed index 47bf25e409b..687efdada6e 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 d498bca2455..8c467d14d2a 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 @@ macro_rules! m1 { 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 760ff88f448..9c4c3cc3682 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)` From c5053267d2d5c189f7df620bf59e12340df1cab6 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Thu, 30 Jun 2022 12:45:48 -0400 Subject: [PATCH 08/84] Add `dev deprecate` to the development basics --- book/src/development/basics.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/book/src/development/basics.md b/book/src/development/basics.md index 78c429ea013..d3a11a31db8 100644 --- a/book/src/development/basics.md +++ b/book/src/development/basics.md @@ -91,6 +91,8 @@ 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 From a2b8a67777500139ba36d07d21f7e543e9c0f65c Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Fri, 1 Jul 2022 00:14:38 +0500 Subject: [PATCH 09/84] Fix broken hyperlink --- clippy_lints/src/methods/mod.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 9bb7bb7a7ab..cc1530f0ed0 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -2196,12 +2196,9 @@ 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. From a5b70a4c1df9d009823ec067e7544ce28ec29c2f Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Fri, 1 Jul 2022 12:08:28 +0000 Subject: [PATCH 10/84] Fix allow attributes in let_unit_value --- clippy_lints/src/unit_types/let_unit_value.rs | 17 +++++++------- clippy_lints/src/unit_types/mod.rs | 6 ++--- tests/ui/let_unit.fixed | 12 +++++++++- tests/ui/let_unit.rs | 12 +++++++++- tests/ui/let_unit.stderr | 22 +++++++++---------- 5 files changed, 44 insertions(+), 25 deletions(-) diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index 27678c8ba3c..17d60a40dce 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -4,18 +4,17 @@ 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, Local, PatKind}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{self, Ty, TypeFoldable, TypeSuperFoldable, TypeVisitor}; 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(cx: &LateContext<'_>, local: &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) { @@ -29,7 +28,7 @@ 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| { diag.span_suggestion( @@ -45,15 +44,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 ); } diff --git a/clippy_lints/src/unit_types/mod.rs b/clippy_lints/src/unit_types/mod.rs index a9e2073dec2..e57d4fc3b89 100644 --- a/clippy_lints/src/unit_types/mod.rs +++ b/clippy_lints/src/unit_types/mod.rs @@ -3,7 +3,7 @@ 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}; @@ -99,8 +99,8 @@ 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); + fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) { + let_unit_value::check(cx, local); } fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { diff --git a/tests/ui/let_unit.fixed b/tests/ui/let_unit.fixed index e72b7462325..18c2672f880 100644 --- a/tests/ui/let_unit.fixed +++ b/tests/ui/let_unit.fixed @@ -1,8 +1,9 @@ // run-rustfix +#![feature(lint_reasons)] #![warn(clippy::let_unit_value)] #![allow(clippy::no_effect)] -#![allow(unused_variables)] +#![allow(unused)] macro_rules! let_and_return { ($n:expr) => {{ @@ -113,3 +114,12 @@ fn _returns_generic() { Some(_) => (), }; } + +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 47ee0a76724..c9c4582a956 100644 --- a/tests/ui/let_unit.rs +++ b/tests/ui/let_unit.rs @@ -1,8 +1,9 @@ // run-rustfix +#![feature(lint_reasons)] #![warn(clippy::let_unit_value)] #![allow(clippy::no_effect)] -#![allow(unused_variables)] +#![allow(unused)] macro_rules! let_and_return { ($n:expr) => {{ @@ -113,3 +114,12 @@ fn f4(mut x: Vec) -> T { Some(_) => (), }; } + +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 45bf67acdb7..47a2a6e3cc1 100644 --- a/tests/ui/let_unit.stderr +++ b/tests/ui/let_unit.stderr @@ -1,5 +1,5 @@ error: this let-binding has unit value - --> $DIR/let_unit.rs:14:5 + --> $DIR/let_unit.rs:15:5 | LL | let _x = println!("x"); | ^^^^^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `println!("x");` @@ -7,13 +7,13 @@ LL | let _x = println!("x"); = note: `-D clippy::let-unit-value` implied by `-D warnings` error: this let-binding has unit value - --> $DIR/let_unit.rs:18:9 + --> $DIR/let_unit.rs:19:9 | LL | let _a = (); | ^^^^^^^^^^^^ help: omit the `let` binding: `();` error: this let-binding has unit value - --> $DIR/let_unit.rs:53:5 + --> $DIR/let_unit.rs:54:5 | LL | / let _ = v LL | | .into_iter() @@ -36,7 +36,7 @@ LL + .unwrap(); | error: this let-binding has unit value - --> $DIR/let_unit.rs:80:5 + --> $DIR/let_unit.rs:81:5 | LL | let x: () = f(); // Lint. | ^^^^-^^^^^^^^^^^ @@ -44,7 +44,7 @@ LL | let x: () = f(); // Lint. | help: use a wild (`_`) binding: `_` error: this let-binding has unit value - --> $DIR/let_unit.rs:83:5 + --> $DIR/let_unit.rs:84:5 | LL | let x: () = f2(0i32); // Lint. | ^^^^-^^^^^^^^^^^^^^^^ @@ -52,31 +52,31 @@ LL | let x: () = f2(0i32); // Lint. | help: use a wild (`_`) binding: `_` error: this let-binding has unit value - --> $DIR/let_unit.rs:85:5 + --> $DIR/let_unit.rs:86:5 | LL | let _: () = f3(()); // Lint | ^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f3(());` error: this let-binding has unit value - --> $DIR/let_unit.rs:86:5 + --> $DIR/let_unit.rs:87:5 | LL | let x: () = f3(()); // Lint | ^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f3(());` error: this let-binding has unit value - --> $DIR/let_unit.rs:88:5 + --> $DIR/let_unit.rs:89: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 + --> $DIR/let_unit.rs:90: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:99:5 | LL | let x: () = if true { f() } else { f2(0) }; // Lint | ^^^^-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -84,7 +84,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:110:5 | LL | / let _: () = match Some(0) { LL | | None => f2(1), From 1988375a25e8e8f61429cd40fa21c22a421b1df8 Mon Sep 17 00:00:00 2001 From: Takayuki Nakata Date: Fri, 1 Jul 2022 21:30:59 +0900 Subject: [PATCH 11/84] Fix some links --- CHANGELOG.md | 6 +++--- book/src/development/basics.md | 4 ++-- clippy_utils/src/ty.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb1402baf9f..71e498c301b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # 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 @@ -1577,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 @@ -3426,7 +3426,7 @@ 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 diff --git a/book/src/development/basics.md b/book/src/development/basics.md index d3a11a31db8..732206acf61 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) diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 6ca36eed4e6..b7da1abd373 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>, From 5de85902facc559e1e35205883b394cbbe8ceec4 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Tue, 28 Jun 2022 13:15:30 -0500 Subject: [PATCH 12/84] Factor out hir::Node::Binding --- clippy_lints/src/escape.rs | 16 +++++----------- clippy_lints/src/explicit_write.rs | 2 +- clippy_lints/src/loops/mut_range_bound.rs | 2 +- clippy_lints/src/loops/same_item_push.rs | 2 +- clippy_lints/src/manual_rem_euclid.rs | 3 +-- clippy_lints/src/methods/iter_skip_next.rs | 2 +- clippy_utils/src/lib.rs | 2 +- 7 files changed, 11 insertions(+), 18 deletions(-) diff --git a/clippy_lints/src/escape.rs b/clippy_lints/src/escape.rs index 7a65b849a66..1ac7bfba06b 100644 --- a/clippy_lints/src/escape.rs +++ b/clippy_lints/src/escape.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_hir; use clippy_utils::ty::contains_ty; use rustc_hir::intravisit; -use rustc_hir::{self, AssocItemKind, Body, FnDecl, HirId, HirIdSet, Impl, ItemKind, Node}; +use rustc_hir::{self, AssocItemKind, Body, FnDecl, HirId, HirIdSet, Impl, ItemKind, Node, Pat, PatKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::FakeReadCause; @@ -132,7 +132,10 @@ fn check_fn( // TODO: Replace with Map::is_argument(..) when it's fixed fn is_argument(map: rustc_middle::hir::map::Map<'_>, id: HirId) -> bool { match map.find(id) { - Some(Node::Binding(_)) => (), + Some(Node::Pat(Pat { + kind: PatKind::Binding(..), + .. + })) => (), _ => return false, } @@ -144,15 +147,6 @@ fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) { if cmt.place.projections.is_empty() { if let PlaceBase::Local(lid) = cmt.place.base { self.set.remove(&lid); - let map = &self.cx.tcx.hir(); - if let Some(Node::Binding(_)) = map.find(cmt.hir_id) { - if self.set.contains(&lid) { - // let y = x where x is known - // remove x, insert y - self.set.insert(cmt.hir_id); - self.set.remove(&lid); - } - } } } } diff --git a/clippy_lints/src/explicit_write.rs b/clippy_lints/src/explicit_write.rs index 12d636cf410..5bf4313b41a 100644 --- a/clippy_lints/src/explicit_write.rs +++ b/clippy_lints/src/explicit_write.rs @@ -125,7 +125,7 @@ fn look_in_block<'tcx, 'hir>(cx: &LateContext<'tcx>, kind: &'tcx ExprKind<'hir>) // Find id of the local that expr_end_of_block resolves to if let ExprKind::Path(QPath::Resolved(None, expr_path)) = expr_end_of_block.kind; if let Res::Local(expr_res) = expr_path.res; - if let Some(Node::Binding(res_pat)) = cx.tcx.hir().find(expr_res); + if let Some(Node::Pat(res_pat)) = cx.tcx.hir().find(expr_res); // Find id of the local we found in the block if let PatKind::Binding(BindingAnnotation::Unannotated, local_hir_id, _ident, None) = local.pat.kind; diff --git a/clippy_lints/src/loops/mut_range_bound.rs b/clippy_lints/src/loops/mut_range_bound.rs index d20df830455..aedf3810b23 100644 --- a/clippy_lints/src/loops/mut_range_bound.rs +++ b/clippy_lints/src/loops/mut_range_bound.rs @@ -43,7 +43,7 @@ fn mut_warn_with_span(cx: &LateContext<'_>, span: Option) { fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option { if_chain! { if let Some(hir_id) = path_to_local(bound); - if let Node::Binding(pat) = cx.tcx.hir().get(hir_id); + if let Node::Pat(pat) = cx.tcx.hir().get(hir_id); if let PatKind::Binding(BindingAnnotation::Mutable, ..) = pat.kind; then { return Some(hir_id); diff --git a/clippy_lints/src/loops/same_item_push.rs b/clippy_lints/src/loops/same_item_push.rs index e048d744fc3..1439f1f4c75 100644 --- a/clippy_lints/src/loops/same_item_push.rs +++ b/clippy_lints/src/loops/same_item_push.rs @@ -63,7 +63,7 @@ fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) { Res::Local(hir_id) => { let node = cx.tcx.hir().get(hir_id); if_chain! { - if let Node::Binding(pat) = node; + if let Node::Pat(pat) = node; if let PatKind::Binding(bind_ann, ..) = pat.kind; if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable); let parent_node = cx.tcx.hir().get_parent_node(hir_id); diff --git a/clippy_lints/src/manual_rem_euclid.rs b/clippy_lints/src/manual_rem_euclid.rs index b5698965fc3..2ce9d0e77c1 100644 --- a/clippy_lints/src/manual_rem_euclid.rs +++ b/clippy_lints/src/manual_rem_euclid.rs @@ -71,8 +71,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { && 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(Node::Binding(_)) = cx.tcx.hir().find(hir_id) { + && let Some(hir_id) = path_to_local(expr3) { // 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/methods/iter_skip_next.rs b/clippy_lints/src/methods/iter_skip_next.rs index f5410c7fd7f..43e9451f7d3 100644 --- a/clippy_lints/src/methods/iter_skip_next.rs +++ b/clippy_lints/src/methods/iter_skip_next.rs @@ -22,7 +22,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr |diag| { if_chain! { if let Some(id) = path_to_local(recv); - if let Node::Binding(pat) = cx.tcx.hir().get(id); + if let Node::Pat(pat) = cx.tcx.hir().get(id); if let PatKind::Binding(ann, _, _, _) = pat.kind; if ann != BindingAnnotation::Mutable; then { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 9fa28e137f9..5cfd02232de 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -183,7 +183,7 @@ pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> { let hir = cx.tcx.hir(); if_chain! { - if let Some(Node::Binding(pat)) = hir.find(hir_id); + if let Some(Node::Pat(pat)) = hir.find(hir_id); if matches!(pat.kind, PatKind::Binding(BindingAnnotation::Unannotated, ..)); let parent = hir.get_parent_node(hir_id); if let Some(Node::Local(local)) = hir.find(parent); From d5e33d3dedf1e15309fe15c49867de39431967c4 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Sun, 13 Feb 2022 16:27:59 +0100 Subject: [PATCH 13/84] Shorten def_span for more items. --- clippy_lints/src/operators/numeric_arithmetic.rs | 9 +++++---- tests/ui/crashes/ice-6252.stderr | 2 +- tests/ui/derive_hash_xor_eq.stderr | 16 ++++------------ tests/ui/derive_ord_xor_partial_ord.stderr | 16 ++++------------ tests/ui/needless_pass_by_value.stderr | 8 ++++---- 5 files changed, 18 insertions(+), 33 deletions(-) diff --git a/clippy_lints/src/operators/numeric_arithmetic.rs b/clippy_lints/src/operators/numeric_arithmetic.rs index 82f454d02f7..b6097710dc6 100644 --- a/clippy_lints/src/operators/numeric_arithmetic.rs +++ b/clippy_lints/src/operators/numeric_arithmetic.rs @@ -96,11 +96,12 @@ pub fn expr_post(&mut self, id: hir::HirId) { } pub fn enter_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) { - let body_owner = cx.tcx.hir().body_owner_def_id(body.id()); + let body_owner = cx.tcx.hir().body_owner(body.id()); + let body_owner_def_id = cx.tcx.hir().local_def_id(body_owner); - match cx.tcx.hir().body_owner_kind(body_owner) { + match cx.tcx.hir().body_owner_kind(body_owner_def_id) { hir::BodyOwnerKind::Static(_) | hir::BodyOwnerKind::Const => { - let body_span = cx.tcx.def_span(body_owner); + let body_span = cx.tcx.hir().span_with_body(body_owner); if let Some(span) = self.const_span { if span.contains(body_span) { @@ -115,7 +116,7 @@ pub fn enter_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) { pub fn 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); + let body_span = cx.tcx.hir().span_with_body(body_owner); if let Some(span) = self.const_span { if span.contains(body_span) { diff --git a/tests/ui/crashes/ice-6252.stderr b/tests/ui/crashes/ice-6252.stderr index d930d486fde..a6a767483ed 100644 --- a/tests/ui/crashes/ice-6252.stderr +++ b/tests/ui/crashes/ice-6252.stderr @@ -25,7 +25,7 @@ error[E0046]: not all trait items implemented, missing: `VAL` --> $DIR/ice-6252.rs:10:1 | LL | const VAL: T; - | ------------- `VAL` from trait + | ------------ `VAL` from trait ... LL | impl TypeVal for Multiply where N: TypeVal {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `VAL` in implementation diff --git a/tests/ui/derive_hash_xor_eq.stderr b/tests/ui/derive_hash_xor_eq.stderr index e5184bd1407..2a4abb0c519 100644 --- a/tests/ui/derive_hash_xor_eq.stderr +++ b/tests/ui/derive_hash_xor_eq.stderr @@ -8,12 +8,8 @@ LL | #[derive(Hash)] note: `PartialEq` implemented here --> $DIR/derive_hash_xor_eq.rs:15:1 | -LL | / impl PartialEq for Bar { -LL | | fn eq(&self, _: &Bar) -> bool { -LL | | true -LL | | } -LL | | } - | |_^ +LL | impl PartialEq for Bar { + | ^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the derive macro `Hash` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `Hash` but have implemented `PartialEq` explicitly @@ -25,12 +21,8 @@ LL | #[derive(Hash)] note: `PartialEq` implemented here --> $DIR/derive_hash_xor_eq.rs:24:1 | -LL | / impl PartialEq for Baz { -LL | | fn eq(&self, _: &Baz) -> bool { -LL | | true -LL | | } -LL | | } - | |_^ +LL | impl PartialEq for Baz { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the derive macro `Hash` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are implementing `Hash` explicitly but have derived `PartialEq` diff --git a/tests/ui/derive_ord_xor_partial_ord.stderr b/tests/ui/derive_ord_xor_partial_ord.stderr index 32896c99dad..baf8341aba9 100644 --- a/tests/ui/derive_ord_xor_partial_ord.stderr +++ b/tests/ui/derive_ord_xor_partial_ord.stderr @@ -8,12 +8,8 @@ LL | #[derive(Ord, PartialEq, Eq)] note: `PartialOrd` implemented here --> $DIR/derive_ord_xor_partial_ord.rs:24:1 | -LL | / impl PartialOrd for DeriveOrd { -LL | | fn partial_cmp(&self, other: &Self) -> Option { -LL | | Some(other.cmp(self)) -LL | | } -LL | | } - | |_^ +LL | impl PartialOrd for DeriveOrd { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the derive macro `Ord` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `Ord` but have implemented `PartialOrd` explicitly @@ -25,12 +21,8 @@ LL | #[derive(Ord, PartialEq, Eq)] note: `PartialOrd` implemented here --> $DIR/derive_ord_xor_partial_ord.rs:33:1 | -LL | / impl PartialOrd for DeriveOrdWithExplicitTypeVariable { -LL | | fn partial_cmp(&self, other: &Self) -> Option { -LL | | Some(other.cmp(self)) -LL | | } -LL | | } - | |_^ +LL | impl PartialOrd for DeriveOrdWithExplicitTypeVariable { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the derive macro `Ord` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are implementing `Ord` explicitly but have derived `PartialOrd` diff --git a/tests/ui/needless_pass_by_value.stderr b/tests/ui/needless_pass_by_value.stderr index d960c86a9f0..38f33c53f12 100644 --- a/tests/ui/needless_pass_by_value.stderr +++ b/tests/ui/needless_pass_by_value.stderr @@ -124,7 +124,7 @@ help: consider marking this type as `Copy` --> $DIR/needless_pass_by_value.rs:123:1 | LL | struct CopyWrapper(u32); - | ^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ error: this argument is passed by value, but not consumed in the function body --> $DIR/needless_pass_by_value.rs:131:29 @@ -136,7 +136,7 @@ help: consider marking this type as `Copy` --> $DIR/needless_pass_by_value.rs:123:1 | LL | struct CopyWrapper(u32); - | ^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ error: this argument is passed by value, but not consumed in the function body --> $DIR/needless_pass_by_value.rs:131:45 @@ -148,7 +148,7 @@ help: consider marking this type as `Copy` --> $DIR/needless_pass_by_value.rs:123:1 | LL | struct CopyWrapper(u32); - | ^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ error: this argument is passed by value, but not consumed in the function body --> $DIR/needless_pass_by_value.rs:131:61 @@ -160,7 +160,7 @@ help: consider marking this type as `Copy` --> $DIR/needless_pass_by_value.rs:123:1 | LL | struct CopyWrapper(u32); - | ^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ error: this argument is passed by value, but not consumed in the function body --> $DIR/needless_pass_by_value.rs:143:40 From a86bf81e44dd530b69b686c556069bb8f6569c64 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 1 Jul 2022 18:05:45 +0200 Subject: [PATCH 14/84] Correct lint version for `format_push_string` --- clippy_lints/src/format_push_string.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index ee15ae9f59a..de19c5ec20e 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -27,7 +27,7 @@ /// 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, "`format!(..)` appended to existing `String`" From 7350525ffbdab3ab1faf6ff5d37c8c9f367f3c65 Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Fri, 1 Jul 2022 18:31:57 +0000 Subject: [PATCH 15/84] Fix link --- util/etc/vscode-tasks.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/etc/vscode-tasks.json b/util/etc/vscode-tasks.json index a4bb26b9f90..e0074510c9f 100644 --- a/util/etc/vscode-tasks.json +++ b/util/etc/vscode-tasks.json @@ -28,7 +28,7 @@ // 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": "", }, From 3e80d3988d70847e6b870749641ea6d434ff261b Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 2 Jul 2022 00:59:58 -0400 Subject: [PATCH 16/84] Fix ICE in `dereference.rs` --- clippy_lints/src/dereference.rs | 40 +++++++++++++++++++---------- tests/ui/explicit_auto_deref.fixed | 4 +++ tests/ui/explicit_auto_deref.rs | 4 +++ tests/ui/explicit_auto_deref.stderr | 8 +++++- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 59dcc1ebf19..14e8d5940cf 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -8,7 +8,7 @@ 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, + self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, FnRetTy, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem, TraitItemKind, TyKind, UnOp, }; @@ -717,18 +717,32 @@ 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 { 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(|| Position::Callee), ExprKind::Call(func, args) => args diff --git a/tests/ui/explicit_auto_deref.fixed b/tests/ui/explicit_auto_deref.fixed index d4ff1b1566d..a650fdc1f89 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 99294a7947b..8f4f352576a 100644 --- a/tests/ui/explicit_auto_deref.rs +++ b/tests/ui/explicit_auto_deref.rs @@ -211,4 +211,8 @@ fn _f5(x: &u32) -> u32 { 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 55f956e37ae..92765307ea7 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 From 988b813649c147383bcce125c9afbb0adc681110 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 2 Jul 2022 15:32:29 -0400 Subject: [PATCH 17/84] Use correct substitutions when checking if `needless_borrow` can apply to a method receiver --- clippy_lints/src/dereference.rs | 11 ++++++++--- tests/ui/needless_borrow.fixed | 12 ++++++++++++ tests/ui/needless_borrow.rs | 12 ++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 59dcc1ebf19..d6bd43a7494 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -756,9 +756,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/tests/ui/needless_borrow.fixed b/tests/ui/needless_borrow.fixed index cb005122436..09afe2ddbbf 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 d636a401003..3ae4722a1f8 100644 --- a/tests/ui/needless_borrow.rs +++ b/tests/ui/needless_borrow.rs @@ -115,6 +115,18 @@ impl FooRef for &'_ () { 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)] From ab23b3aa8a4773dffce7942a301a3da81096670b Mon Sep 17 00:00:00 2001 From: Nixon Enraght-Moony Date: Sat, 2 Jul 2022 18:25:55 +0100 Subject: [PATCH 18/84] ast: Add span to `Extern` --- clippy_lints/src/excessive_bools.rs | 2 +- clippy_utils/src/ast_utils.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/excessive_bools.rs b/clippy_lints/src/excessive_bools.rs index f7a92bc0795..453471c8cdd 100644 --- a/clippy_lints/src/excessive_bools.rs +++ b/clippy_lints/src/excessive_bools.rs @@ -94,7 +94,7 @@ pub fn new(max_struct_bools: u64, max_fn_params_bools: u64) -> Self { fn check_fn_sig(&self, cx: &EarlyContext<'_>, fn_sig: &FnSig, span: Span) { match fn_sig.header.ext { - Extern::Implicit | Extern::Explicit(_) => return, + Extern::Implicit(_) | Extern::Explicit(_, _) => return, Extern::None => (), } diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 6487199172e..177e754ee09 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -600,8 +600,8 @@ pub fn eq_ty(l: &Ty, r: &Ty) -> bool { 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), + (None, None) | (Implicit(_), Implicit(_)) => true, + (Explicit(l,_), Explicit(r,_)) => eq_str_lit(l, r), _ => false, } } From 815a3ccd793d8aaf58517e91e89468a7f32d011c Mon Sep 17 00:00:00 2001 From: Duane Date: Sat, 2 Jul 2022 20:41:58 -0700 Subject: [PATCH 19/84] corrected README.md book link --- book/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/README.md b/book/README.md index b652194d0d1..6d67f80ff25 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. From 65655d1e1e25d11df5dd2f4570d3ec00c19e8a51 Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Sun, 3 Jul 2022 17:44:09 +0300 Subject: [PATCH 20/84] Remove trailing spaces --- util/etc/vscode-tasks.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/util/etc/vscode-tasks.json b/util/etc/vscode-tasks.json index e0074510c9f..845e33414b2 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", + "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/book/src/development/adding_lints.md#testing // // "TESTNAME": "", - }, + } }, "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" + } + ] } From 2dd5fc14da4065fa0ac7710582e7393822d40d37 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Sun, 3 Jul 2022 17:02:48 +0200 Subject: [PATCH 21/84] Correct `[clippy::version]` for 1.62 lints and add note to docs --- book/src/development/infrastructure/changelog_update.md | 3 +++ clippy_lints/src/await_holding_invalid.rs | 2 +- clippy_lints/src/casts/mod.rs | 2 +- clippy_lints/src/crate_in_macro_def.rs | 2 +- clippy_lints/src/drop_forget_ref.rs | 4 ++-- clippy_lints/src/empty_drop.rs | 2 +- clippy_lints/src/methods/mod.rs | 6 +++--- 7 files changed, 12 insertions(+), 9 deletions(-) diff --git a/book/src/development/infrastructure/changelog_update.md b/book/src/development/infrastructure/changelog_update.md index e560f4c6a3e..80a47affe30 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/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index eee5f90d178..1761360fb28 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -161,7 +161,7 @@ /// 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/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 02c2f30a4dd..af3798a0cc8 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -500,7 +500,7 @@ /// 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/crate_in_macro_def.rs b/clippy_lints/src/crate_in_macro_def.rs index 9b8a481b6ea..f6ec8fe7edc 100644 --- a/clippy_lints/src/crate_in_macro_def.rs +++ b/clippy_lints/src/crate_in_macro_def.rs @@ -43,7 +43,7 @@ /// #[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/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index 25014bfa1a5..b35f0b8ca52 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -116,7 +116,7 @@ /// 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 @@ /// 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/empty_drop.rs b/clippy_lints/src/empty_drop.rs index 325ae2356c1..ec063c0f777 100644 --- a/clippy_lints/src/empty_drop.rs +++ b/clippy_lints/src/empty_drop.rs @@ -26,7 +26,7 @@ /// ```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/methods/mod.rs b/clippy_lints/src/methods/mod.rs index cc1530f0ed0..e8a239ecc7a 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -369,7 +369,7 @@ /// 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"# @@ -2215,7 +2215,7 @@ /// 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" @@ -2235,7 +2235,7 @@ /// 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" From 9ef76e051be050d49db7842d94c7ae76821f4e00 Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Sun, 3 Jul 2022 19:03:51 +0000 Subject: [PATCH 22/84] Fixed comment --- util/etc/vscode-tasks.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/etc/vscode-tasks.json b/util/etc/vscode-tasks.json index 845e33414b2..ab98f9b4154 100644 --- a/util/etc/vscode-tasks.json +++ b/util/etc/vscode-tasks.json @@ -24,13 +24,13 @@ "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/book/src/development/adding_lints.md#testing // // "TESTNAME": "", + "RUST_BACKTRACE": "1" } }, "problemMatcher": [], From de646e10db2141fc9fffddf103611fe7d1fd9d68 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Sat, 2 Jul 2022 22:24:26 -0400 Subject: [PATCH 23/84] Add `invalid_utf8_in_unchecked` --- CHANGELOG.md | 1 + clippy_lints/src/invalid_utf8_in_unchecked.rs | 74 +++++++++++++++++++ clippy_lints/src/lib.register_all.rs | 1 + clippy_lints/src/lib.register_correctness.rs | 1 + clippy_lints/src/lib.register_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_utils/src/paths.rs | 1 + tests/ui/invalid_utf8_in_unchecked.rs | 20 +++++ tests/ui/invalid_utf8_in_unchecked.stderr | 22 ++++++ 9 files changed, 123 insertions(+) create mode 100644 clippy_lints/src/invalid_utf8_in_unchecked.rs create mode 100644 tests/ui/invalid_utf8_in_unchecked.rs create mode 100644 tests/ui/invalid_utf8_in_unchecked.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 71e498c301b..1b792736a6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3641,6 +3641,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 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 00000000000..e0a607f9a95 --- /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/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 563ad891603..da26a3f0130 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -92,6 +92,7 @@ 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 7d5e65cb27a..9975859c54f 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -29,6 +29,7 @@ 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 d3c75f8b519..ceb8470657f 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -196,6 +196,7 @@ 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, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 172fdf8c852..1604d1078ee 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -255,6 +255,7 @@ macro_rules! declare_clippy_lint { 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; @@ -913,6 +914,7 @@ 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)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 6542e77113b..05429d05d9e 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -163,6 +163,7 @@ 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/tests/ui/invalid_utf8_in_unchecked.rs b/tests/ui/invalid_utf8_in_unchecked.rs new file mode 100644 index 00000000000..3dc096d3197 --- /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 00000000000..c89cd2758ee --- /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 + From fec45930823c77acd91bfeec153b323bacfab099 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Mon, 4 Jul 2022 14:03:11 +0000 Subject: [PATCH 24/84] new_without_default: ignore const generics/lifetime params on fn new --- clippy_lints/src/new_without_default.rs | 12 +++--------- tests/ui/new_without_default.rs | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index 093ec389335..5c45ee6d94a 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -88,15 +88,9 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { // 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/tests/ui/new_without_default.rs b/tests/ui/new_without_default.rs index 538927b1805..65809023f8d 100644 --- a/tests/ui/new_without_default.rs +++ b/tests/ui/new_without_default.rs @@ -212,3 +212,17 @@ pub fn new() -> Self { } 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 + } +} From 975667945c5be3ab84a0f7fd5bd6715168285a95 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 4 Jul 2022 13:53:42 -0400 Subject: [PATCH 25/84] Fix `undocumented_unsafe_blocks` in closures --- .../src/undocumented_unsafe_blocks.rs | 18 +++++- tests/ui/undocumented_unsafe_blocks.rs | 9 ++- tests/ui/undocumented_unsafe_blocks.stderr | 62 +++++++++---------- 3 files changed, 54 insertions(+), 35 deletions(-) diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 025dd57e83a..04f16fd2161 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/tests/ui/undocumented_unsafe_blocks.rs b/tests/ui/undocumented_unsafe_blocks.rs index 33b6a82f9d2..08aee433215 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 @@ unsafe trait B {} #[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 b79949e9d06..c6a2127443b 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 () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 2ccb05487c35651ff88d0e9d97faa502ebfee46e Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Tue, 5 Jul 2022 09:43:29 +0300 Subject: [PATCH 26/84] Fix stderr for cast_size_32bit --- tests/ui/cast_size_32bit.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/cast_size_32bit.stderr b/tests/ui/cast_size_32bit.stderr index 7125f741c15..8990c3ba739 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 From 3db0e00bdc1ebd35aa9f28b83c3ed4de7ba78a25 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 5 Jul 2022 15:21:27 -0400 Subject: [PATCH 27/84] Lint `shadow_*` lints in anon const blocks --- clippy_lints/src/shadow.rs | 14 ++++++-------- tests/ui/shadow.stderr | 14 +++++++++++++- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/shadow.rs b/clippy_lints/src/shadow.rs index bf318c055da..5dcdab5b8ab 100644 --- a/clippy_lints/src/shadow.rs +++ b/clippy_lints/src/shadow.rs @@ -99,7 +99,7 @@ #[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 @@ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { 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 @@ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { 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 @@ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { 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/tests/ui/shadow.stderr b/tests/ui/shadow.stderr index 3bd41d06260..43d76094d0e 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 From 5c35569ff7344db7b9933f0716cd41933459de5f Mon Sep 17 00:00:00 2001 From: Alan Egerton Date: Fri, 17 Jun 2022 13:10:07 +0100 Subject: [PATCH 28/84] Relax constrained generics to TypeVisitable --- clippy_utils/src/ty.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 6ca36eed4e6..a426fa1b0ff 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -15,7 +15,7 @@ use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst}; use rustc_middle::ty::{ self, AdtDef, Binder, BoundRegion, DefIdTree, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, ProjectionTy, - Region, RegionKind, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitor, UintTy, VariantDef, VariantDiscr, + Region, RegionKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, UintTy, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; @@ -765,7 +765,7 @@ fn visit_region(&mut self, r: Region<'tcx>) -> ControlFlow { ControlFlow::Continue(()) } } - fn visit_binder>(&mut self, t: &Binder<'tcx, T>) -> ControlFlow { + fn visit_binder>(&mut self, t: &Binder<'tcx, T>) -> ControlFlow { self.index += 1; let res = t.super_visit_with(self); self.index -= 1; From 490c773e66d28a3b07a87af8d27d844ed6ed6efc Mon Sep 17 00:00:00 2001 From: Alan Egerton Date: Fri, 17 Jun 2022 13:15:00 +0100 Subject: [PATCH 29/84] Update TypeVisitor paths --- clippy_lints/src/dereference.rs | 2 +- clippy_lints/src/eta_reduction.rs | 2 +- clippy_lints/src/mut_key.rs | 2 +- clippy_lints/src/needless_pass_by_value.rs | 2 +- clippy_lints/src/redundant_clone.rs | 2 +- clippy_lints/src/transmute/transmute_ptr_to_ref.rs | 2 +- clippy_lints/src/transmute/useless_transmute.rs | 2 +- clippy_lints/src/types/vec_box.rs | 2 +- clippy_lints/src/unit_types/let_unit_value.rs | 2 +- clippy_lints/src/zero_sized_map_values.rs | 2 +- clippy_utils/src/lib.rs | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 59dcc1ebf19..0f4a2f79ac5 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -15,7 +15,7 @@ use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; -use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeckResults}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable, TypeckResults}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{symbol::sym, Span, Symbol}; use rustc_trait_selection::infer::InferCtxtExt; diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index a5a763c37d1..42fac550ec6 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -12,7 +12,7 @@ use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc_middle::ty::binding::BindingMode; use rustc_middle::ty::subst::Subst; -use rustc_middle::ty::{self, ClosureKind, Ty, TypeFoldable}; +use rustc_middle::ty::{self, ClosureKind, Ty, TypeVisitable}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; diff --git a/clippy_lints/src/mut_key.rs b/clippy_lints/src/mut_key.rs index 7e2531c7ca5..4db103bbc13 100644 --- a/clippy_lints/src/mut_key.rs +++ b/clippy_lints/src/mut_key.rs @@ -2,7 +2,7 @@ use clippy_utils::trait_ref_of_method; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TypeFoldable; +use rustc_middle::ty::TypeVisitable; use rustc_middle::ty::{Adt, Array, Ref, Slice, Tuple, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 8b273aca7d0..0cbef1c95fe 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -13,7 +13,7 @@ use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::FakeReadCause; -use rustc_middle::ty::{self, TypeFoldable}; +use rustc_middle::ty::{self, TypeVisitable}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::kw; use rustc_span::{sym, Span}; diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index 3b11cbc3760..6d0b9a0f03f 100644 --- a/clippy_lints/src/redundant_clone.rs +++ b/clippy_lints/src/redundant_clone.rs @@ -14,7 +14,7 @@ visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor as _}, Mutability, }; -use rustc_middle::ty::{self, fold::TypeVisitor, Ty}; +use rustc_middle::ty::{self, visit::TypeVisitor, Ty}; use rustc_mir_dataflow::{Analysis, AnalysisDomain, CallReturnPlaces, GenKill, GenKillAnalysis, ResultsCursor}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::{BytePos, Span}; diff --git a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs index 3ed5d5c6950..5eb03275b8e 100644 --- a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs +++ b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs @@ -5,7 +5,7 @@ use rustc_errors::Applicability; use rustc_hir::{self as hir, Expr, GenericArg, Mutability, Path, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty, TypeFoldable}; +use rustc_middle::ty::{self, Ty, TypeVisitable}; use rustc_semver::RustcVersion; /// Checks for `transmute_ptr_to_ref` lint. diff --git a/clippy_lints/src/transmute/useless_transmute.rs b/clippy_lints/src/transmute/useless_transmute.rs index 8ea985a8984..8122cd716e0 100644 --- a/clippy_lints/src/transmute/useless_transmute.rs +++ b/clippy_lints/src/transmute/useless_transmute.rs @@ -4,7 +4,7 @@ use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty, TypeFoldable}; +use rustc_middle::ty::{self, Ty, TypeVisitable}; /// Checks for `useless_transmute` lint. /// Returns `true` if it's triggered, otherwise returns `false`. diff --git a/clippy_lints/src/types/vec_box.rs b/clippy_lints/src/types/vec_box.rs index c632f822544..b2f536ca781 100644 --- a/clippy_lints/src/types/vec_box.rs +++ b/clippy_lints/src/types/vec_box.rs @@ -6,7 +6,7 @@ use rustc_hir::{self as hir, def_id::DefId, GenericArg, QPath, TyKind}; use rustc_lint::LateContext; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::TypeFoldable; +use rustc_middle::ty::TypeVisitable; use rustc_span::symbol::sym; use rustc_typeck::hir_ty_to_ty; diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index 27678c8ba3c..cf509455aad 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -7,7 +7,7 @@ use rustc_hir::{Expr, ExprKind, PatKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, Ty, TypeFoldable, TypeSuperFoldable, TypeVisitor}; +use rustc_middle::ty::{self, Ty, TypeVisitable, TypeSuperVisitable, TypeVisitor}; use super::LET_UNIT_VALUE; diff --git a/clippy_lints/src/zero_sized_map_values.rs b/clippy_lints/src/zero_sized_map_values.rs index 70b0560e676..8dc43c0e294 100644 --- a/clippy_lints/src/zero_sized_map_values.rs +++ b/clippy_lints/src/zero_sized_map_values.rs @@ -4,7 +4,7 @@ use rustc_hir::{self as hir, HirId, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::layout::LayoutOf as _; -use rustc_middle::ty::{Adt, Ty, TypeFoldable}; +use rustc_middle::ty::{Adt, Ty, TypeVisitable}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; use rustc_typeck::hir_ty_to_ty; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 5cfd02232de..1b32f0aaeb8 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -93,7 +93,7 @@ ArraySimplifiedType, BoolSimplifiedType, CharSimplifiedType, FloatSimplifiedType, IntSimplifiedType, PtrSimplifiedType, SliceSimplifiedType, StrSimplifiedType, UintSimplifiedType, }; -use rustc_middle::ty::{layout::IntegerExt, BorrowKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeFoldable, UpvarCapture}; +use rustc_middle::ty::{layout::IntegerExt, BorrowKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeVisitable, UpvarCapture}; use rustc_middle::ty::{FloatTy, IntTy, UintTy}; use rustc_semver::RustcVersion; use rustc_session::Session; From 528308b5aa544454e6c90f6d051c3adeb1a46e1b Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sat, 2 Jul 2022 15:00:24 -0700 Subject: [PATCH 30/84] Extend unnecessary_lazy_eval to cover `bool::then` -> `bool::then_some` --- clippy_lints/src/methods/mod.rs | 1 + .../src/methods/unnecessary_lazy_eval.rs | 7 +- tests/ui/unnecessary_lazy_eval.fixed | 2 + tests/ui/unnecessary_lazy_eval.rs | 2 + tests/ui/unnecessary_lazy_eval.stderr | 76 ++++++++++--------- 5 files changed, 52 insertions(+), 36 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index cc1530f0ed0..36389d798ed 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -2737,6 +2737,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { } }, ("take", []) => needless_option_take::check(cx, expr, recv), + ("then", [arg]) => 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/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs index 865f6d0318e..a9c641b4606 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 { 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/tests/ui/unnecessary_lazy_eval.fixed b/tests/ui/unnecessary_lazy_eval.fixed index 65fcdc43061..eed81796883 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 206080ed69a..1588db79b38 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 7e4dd7730e7..83dc7fd832c 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 From ebff7206bcc4c740be9016dc7d2e10cf421463f9 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Wed, 6 Jul 2022 00:51:40 -0700 Subject: [PATCH 31/84] Add MSRV check for `bool::then_some` --- clippy_lints/src/methods/mod.rs | 7 ++++++- clippy_utils/src/msrvs.rs | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 36389d798ed..15d98098f33 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -2737,7 +2737,12 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { } }, ("take", []) => needless_option_take::check(cx, expr, recv), - ("then", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some"), + ("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_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index b09c929f76e..9e238c6f1ac 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 } From f84d9bcbb1a11a55596ae72dbcdecf0db9fe0fcc Mon Sep 17 00:00:00 2001 From: flip1995 Date: Sun, 3 Jul 2022 17:35:24 +0200 Subject: [PATCH 32/84] Build the Clippy book as part of x.py doc --- book/src/README.md | 2 +- book/src/development/adding_lints.md | 33 ++++++++++--------- book/src/development/basics.md | 2 +- .../development/common_tools_writing_lints.md | 2 +- book/src/usage.md | 2 +- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/book/src/README.md b/book/src/README.md index d941f8b65e8..6248d588a89 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -1,7 +1,7 @@ # Clippy [![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test%22+event%3Apush+branch%3Aauto) -[![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](#license) +[![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](https://github.com/rust-lang/rust-clippy#license) A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md index 3da07fcb968..d06297f2e07 100644 --- a/book/src/development/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -13,7 +13,6 @@ because that's clearly a non-descriptive name. - [Testing](#testing) - [Cargo lints](#cargo-lints) - [Rustfix tests](#rustfix-tests) - - [Edition 2018 tests](#edition-2018-tests) - [Testing manually](#testing-manually) - [Lint declaration](#lint-declaration) - [Lint registration](#lint-registration) @@ -402,9 +401,8 @@ need to ensure that the MSRV configured for the project is >= the MSRV of the required Rust feature. If multiple features are required, just use the one with a lower MSRV. -First, add an MSRV alias for the required feature in -[`clippy_utils::msrvs`](/clippy_utils/src/msrvs.rs). This can be accessed later -as `msrvs::STR_STRIP_PREFIX`, for example. +First, add an MSRV alias for the required feature in [`clippy_utils::msrvs`]. +This can be accessed later as `msrvs::STR_STRIP_PREFIX`, for example. ```rust msrv_aliases! { @@ -468,6 +466,8 @@ define_Conf! { } ``` +[`clippy_utils::msrvs`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/msrvs/index.html + ## Author lint If you have trouble implementing your lint, there is also the internal `author` @@ -583,8 +583,7 @@ the workspace directory. Adding a configuration to a lint can be useful for thresholds or to constrain some behavior that can be seen as a false positive for some users. Adding a configuration is done in the following steps: -1. Adding a new configuration entry to - [clippy_lints::utils::conf](/clippy_lints/src/utils/conf.rs) like this: +1. Adding a new configuration entry to [`clippy_lints::utils::conf`] like this: ```rust /// Lint: LINT_NAME. @@ -635,9 +634,9 @@ for some users. Adding a configuration is done in the following steps: ``` 3. Passing the configuration value to the lint impl struct: - First find the struct construction in the [clippy_lints lib - file](/clippy_lints/src/lib.rs). The configuration value is now cloned or - copied into a local value that is then passed to the impl struct like this: + First find the struct construction in the [`clippy_lints` lib file]. The + configuration value is now cloned or copied into a local value that is then + passed to the impl struct like this: ```rust // Default generated registration: @@ -653,12 +652,16 @@ for some users. Adding a configuration is done in the following steps: 4. Adding tests: 1. The default configured value can be tested like any normal lint in - [`tests/ui`](/tests/ui). - 2. The configuration itself will be tested separately in - [`tests/ui-toml`](/tests/ui-toml). Simply add a new subfolder with a - fitting name. This folder contains a `clippy.toml` file with the - configuration value and a rust file that should be linted by Clippy. The - test can otherwise be written as usual. + [`tests/ui`]. + 2. The configuration itself will be tested separately in [`tests/ui-toml`]. + Simply add a new subfolder with a fitting name. This folder contains a + `clippy.toml` file with the configuration value and a rust file that + should be linted by Clippy. The test can otherwise be written as usual. + +[`clippy_lints::utils::conf`]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/utils/conf.rs +[`clippy_lints` lib file]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/lib.rs +[`tests/ui`]: https://github.com/rust-lang/rust-clippy/blob/master/tests/ui +[`tests/ui-toml`]: https://github.com/rust-lang/rust-clippy/blob/master/tests/ui-toml ## Cheat Sheet diff --git a/book/src/development/basics.md b/book/src/development/basics.md index 78c429ea013..605897ff49c 100644 --- a/book/src/development/basics.md +++ b/book/src/development/basics.md @@ -98,7 +98,7 @@ cargo dev setup intellij ``` More about intellij command usage and reasons -[here](../CONTRIBUTING.md#intellij-rust) +[here](https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md#intellij-rust) ## lintcheck diff --git a/book/src/development/common_tools_writing_lints.md b/book/src/development/common_tools_writing_lints.md index e1ed89262f6..15e00c7d7ce 100644 --- a/book/src/development/common_tools_writing_lints.md +++ b/book/src/development/common_tools_writing_lints.md @@ -276,4 +276,4 @@ functions to deal with macros: [LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html [TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html [pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty -[paths]: ../clippy_utils/src/paths.rs +[paths]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/paths/index.html diff --git a/book/src/usage.md b/book/src/usage.md index 337680aa313..5d858e0da46 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -148,4 +148,4 @@ clippy-driver --edition 2018 -Cpanic=abort foo.rs > that are not optimized as expected, for example. [Installation]: installation.md -[CI]: continuous_integration +[CI]: continuous_integration/index.md From b7230d4f44e974c6639980a2e44e8d7523b6c90c Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Wed, 6 Jul 2022 00:52:53 -0700 Subject: [PATCH 33/84] Dogfood fixes to use `bool::then_some` --- clippy_dev/src/update_lints.rs | 2 +- clippy_lints/src/dereference.rs | 4 +++- clippy_lints/src/entry.rs | 2 +- clippy_lints/src/inherent_impl.rs | 2 +- clippy_lints/src/manual_non_exhaustive.rs | 2 +- clippy_lints/src/matches/manual_map.rs | 2 +- clippy_lints/src/matches/match_same_arms.rs | 4 ++-- clippy_lints/src/matches/mod.rs | 4 ++-- clippy_lints/src/methods/manual_str_repeat.rs | 2 +- clippy_lints/src/option_if_let_else.rs | 2 +- clippy_lints/src/ptr.rs | 2 +- clippy_lints/src/swap_ptr_to_ref.rs | 2 +- clippy_lints/src/utils/internal_lints/metadata_collector.rs | 2 +- clippy_lints/src/write.rs | 2 +- clippy_utils/src/lib.rs | 2 +- clippy_utils/src/source.rs | 2 +- 16 files changed, 20 insertions(+), 18 deletions(-) diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 2e0659f42d7..c089f4d8ce4 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -553,7 +553,7 @@ fn is_ident_char(c: u8) -> bool { pos = m.end(); } result.push_str(&contents[pos..]); - edited.then(|| result) + edited.then_some(result) } fn round_to_fifty(count: usize) -> usize { diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 59dcc1ebf19..6a35044d459 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -730,7 +730,9 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, & Position::DerefStable(precedence) }) }, - ExprKind::Call(func, _) if func.hir_id == child_id => (child_id == e.hir_id).then(|| Position::Callee), + ExprKind::Call(func, _) if func.hir_id == child_id => { + (child_id == e.hir_id).then_some(Position::Callee) + }, ExprKind::Call(func, args) => args .iter() .position(|arg| arg.hir_id == child_id) diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index 27743a0ebec..4e3ae4c9614 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/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index 6a031a627df..c5abcc46254 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/manual_non_exhaustive.rs b/clippy_lints/src/manual_non_exhaustive.rs index 4278e98dc91..2b04475c7a9 100644 --- a/clippy_lints/src/manual_non_exhaustive.rs +++ b/clippy_lints/src/manual_non_exhaustive.rs @@ -161,7 +161,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { (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/matches/manual_map.rs b/clippy_lints/src/matches/manual_map.rs index 542905a2d76..8f98b43b9e5 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_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index 15513de7d86..61d28b15066 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) }) diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index b2a873ef582..5c996bc33f3 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1061,7 +1061,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(); @@ -1095,7 +1095,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/methods/manual_str_repeat.rs b/clippy_lints/src/methods/manual_str_repeat.rs index 68a75667914..46d2fc493f8 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/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index ea5a8f0858b..44f153cffac 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 25b73918c0a..8bacc6f6b32 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -501,7 +501,7 @@ fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Optio .iter() .filter_map(get_rptr_lm) .filter(|&(lt, _, _)| lt.name == out.name) - .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/swap_ptr_to_ref.rs b/clippy_lints/src/swap_ptr_to_ref.rs index 75d3b040c96..3cbbda80f3a 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/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 6518e0a6ea0..3010fc0223c 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -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 { diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index 67b2bc8c3f3..08b88947520 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -515,7 +515,7 @@ fn parse_fmt_string(&self, cx: &EarlyContext<'_>, str_lit: &StrLit) -> Option) { 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 diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index f88a92fb11c..1197fe914de 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. From e34ee2484b26f8ac7dfd9685ddfc1658e1b4475b Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Wed, 6 Jul 2022 10:47:29 +0900 Subject: [PATCH 34/84] fix miri-opt tests --- tests/ui/crashes/ice-6252.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/crashes/ice-6252.stderr b/tests/ui/crashes/ice-6252.stderr index a6a767483ed..a1e37e7317b 100644 --- a/tests/ui/crashes/ice-6252.stderr +++ b/tests/ui/crashes/ice-6252.stderr @@ -28,7 +28,7 @@ LL | const VAL: T; | ------------ `VAL` from trait ... LL | impl TypeVal for Multiply where N: TypeVal {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `VAL` in implementation + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `VAL` in implementation error: constant expression depends on a generic parameter --> $DIR/ice-6252.rs:13:9 From 0d443d17eb17450da66a76c015933f30734e99d8 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Sun, 26 Jun 2022 00:00:47 +0200 Subject: [PATCH 35/84] Fix `#[expect]` and `#[allow]` for `clippy::duplicate_mod` --- clippy_lints/src/duplicate_mod.rs | 22 +++++++++++++++++-- tests/ui-cargo/duplicate_mod/fail/src/d.rs | 0 tests/ui-cargo/duplicate_mod/fail/src/main.rs | 13 +++++++++++ .../duplicate_mod/fail/src/main.stderr | 19 ++++++++++++---- 4 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 tests/ui-cargo/duplicate_mod/fail/src/d.rs diff --git a/clippy_lints/src/duplicate_mod.rs b/clippy_lints/src/duplicate_mod.rs index c6c7b959d4f..4f49bb879f5 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}; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext, Level}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{FileName, Span}; use std::collections::BTreeMap; @@ -49,6 +49,7 @@ struct Modules { local_path: PathBuf, spans: Vec, + lint_levels: Vec, } #[derive(Default)] @@ -70,13 +71,30 @@ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { let modules = self.modules.entry(absolute_path).or_insert(Modules { local_path, spans: Vec::new(), + lint_levels: Vec::new(), }); modules.spans.push(item.span_with_attributes()); + modules.lint_levels.push(cx.get_lint_level(DUPLICATE_MOD)); } } fn check_crate_post(&mut self, cx: &EarlyContext<'_>, _: &Crate) { - for Modules { local_path, spans } 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); + } + + (!matches!(lvl, Level::Allow | Level::Expect(_))).then_some(*span) + }) + .collect(); + if spans.len() < 2 { continue; } diff --git a/tests/ui-cargo/duplicate_mod/fail/src/d.rs b/tests/ui-cargo/duplicate_mod/fail/src/d.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/ui-cargo/duplicate_mod/fail/src/main.rs b/tests/ui-cargo/duplicate_mod/fail/src/main.rs index 79b343da247..99ca538b6e4 100644 --- a/tests/ui-cargo/duplicate_mod/fail/src/main.rs +++ b/tests/ui-cargo/duplicate_mod/fail/src/main.rs @@ -1,3 +1,5 @@ +#[feature(lint_reasons)] + mod a; mod b; @@ -13,4 +15,15 @@ mod from_other_module; mod other_module; +mod d; +#[path = "d.rs"] +mod d2; +#[path = "d.rs"] +#[expect(clippy::duplicate_mod)] +mod d3; +#[path = "d.rs"] +#[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 00d7739c8a2..61df1ad5d50 100644 --- a/tests/ui-cargo/duplicate_mod/fail/src/main.stderr +++ b/tests/ui-cargo/duplicate_mod/fail/src/main.stderr @@ -1,5 +1,5 @@ error: file is loaded as a module multiple times: `$DIR/b.rs` - --> $DIR/main.rs:3:1 + --> $DIR/main.rs:5:1 | LL | mod b; | ^^^^^^ first loaded here @@ -11,7 +11,7 @@ LL | | mod b2; = help: replace all but one `mod` item with `use` items error: file is loaded as a module multiple times: `$DIR/c.rs` - --> $DIR/main.rs:7:1 + --> $DIR/main.rs:9:1 | LL | mod c; | ^^^^^^ first loaded here @@ -25,7 +25,7 @@ LL | | mod c3; = 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:13:1 + --> $DIR/main.rs:15:1 | LL | mod from_other_module; | ^^^^^^^^^^^^^^^^^^^^^^ first loaded here @@ -38,5 +38,16 @@ LL | | mod m; | = help: replace all but one `mod` item with `use` items -error: aborting due to 3 previous errors +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 From cac25cde182ced8146e9f2b33e99bda31d2c70f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C4=85sko?= Date: Tue, 5 Jul 2022 00:00:00 +0000 Subject: [PATCH 36/84] Move `predecessors` from Body to BasicBlocks --- clippy_lints/src/redundant_clone.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index 6d0b9a0f03f..eddca604575 100644 --- a/clippy_lints/src/redundant_clone.rs +++ b/clippy_lints/src/redundant_clone.rs @@ -161,7 +161,7 @@ fn check_fn( // `arg` is a reference as it is `.deref()`ed in the previous block. // Look into the predecessor block and find out the source of deref. - let ps = &mir.predecessors()[bb]; + let ps = &mir.basic_blocks.predecessors()[bb]; if ps.len() != 1 { continue; } From 347d999b97a6be3cc45d72697238cb7b63960b07 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Mon, 27 Jun 2022 07:45:35 +0200 Subject: [PATCH 37/84] Shorten span for closures. --- tests/ui/crashes/ice-6251.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/crashes/ice-6251.stderr b/tests/ui/crashes/ice-6251.stderr index 77a3c2ba4ad..8da2965c635 100644 --- a/tests/ui/crashes/ice-6251.stderr +++ b/tests/ui/crashes/ice-6251.stderr @@ -33,7 +33,7 @@ LL | fn bug() -> impl Iterator { | ^^^^^^^^^^^ expected `usize`, found closure | = note: expected type `usize` - found closure `[closure@$DIR/ice-6251.rs:4:44: 4:55]` + found closure `[closure@$DIR/ice-6251.rs:4:44: 4:53]` error: aborting due to 4 previous errors From f2bef55389e3e053cab50d91a674739d8a1ca036 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Thu, 7 Jul 2022 11:59:55 +0200 Subject: [PATCH 38/84] trait_bounds: rework two loops into one the two loops did practically the same, only the type were different (&& vs &), so I used `copied` to convert `&&` and chained them together. Instead of parsing the trait info manually, I use the already provided method `get_trait_info_from_bound`. Also, instead of using manual string writing, I used `join` by `itertools`. --- clippy_lints/src/trait_bounds.rs | 36 +++++++++++--------------------- 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 71957572f2e..76b739c48f1 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -3,6 +3,7 @@ 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; @@ -14,7 +15,6 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::Span; -use std::fmt::Write as _; declare_clippy_lint! { /// ### What it does @@ -178,30 +178,18 @@ impl Eq for SpanlessTy<'_, '_> {} ); 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, From ead2c4f1222c7c9023f02bf6dceaa49eb987c716 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Thu, 7 Jul 2022 12:24:17 +0200 Subject: [PATCH 39/84] fix incorrect suggestion for maybe trait bounds --- clippy_lints/src/trait_bounds.rs | 19 ++++++++++++++----- tests/ui/type_repetition_in_bounds.rs | 12 ++++++++++++ tests/ui/type_repetition_in_bounds.stderr | 18 +++++++++++++++++- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 76b739c48f1..2741179074b 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -9,12 +9,12 @@ 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, + GenericBound, Generics, Item, ItemKind, Node, Path, PathSegment, PredicateOrigin, QPath, TraitBoundModifier, + TraitItem, Ty, TyKind, WherePredicate, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::Span; +use rustc_span::{BytePos, Span}; declare_clippy_lint! { /// ### What it does @@ -242,8 +242,17 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) { } 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 } diff --git a/tests/ui/type_repetition_in_bounds.rs b/tests/ui/type_repetition_in_bounds.rs index d11432f9046..2eca1f4701c 100644 --- a/tests/ui/type_repetition_in_bounds.rs +++ b/tests/ui/type_repetition_in_bounds.rs @@ -79,6 +79,18 @@ struct Foo 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 148c19c7d07..1d88714814d 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 From 6293da231abe89341faf97e2042a28cadd06c918 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Thu, 7 Jul 2022 10:46:22 +0000 Subject: [PATCH 40/84] `UnsafeCell` now has no niches, ever. --- clippy_lints/src/non_copy_const.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index a1ef32ae608..6bce5fbd4c1 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -148,7 +148,7 @@ fn inner<'tcx>(cx: &LateContext<'tcx>, val: mir::ConstantKind<'tcx>) -> bool { match val.ty().kind() { // the fact that we have to dig into every structs to search enums // leads us to the point checking `UnsafeCell` directly is the only option. - ty::Adt(ty_def, ..) if Some(ty_def.did()) == cx.tcx.lang_items().unsafe_cell_type() => true, + ty::Adt(ty_def, ..) if ty_def.is_unsafe_cell() => true, ty::Array(..) | ty::Adt(..) | ty::Tuple(..) => { let val = cx.tcx.destructure_mir_constant(cx.param_env, val); val.fields.iter().any(|field| inner(cx, *field)) From 307b8cd82537b5ac46da2f04cced4e4362835757 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Mon, 6 Jun 2022 15:09:37 +0000 Subject: [PATCH 41/84] Lint simple expressions in manual_filter_map, manual_find_map --- clippy_lints/src/methods/filter_map.rs | 107 +++++++++---------- 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 +++++++++++++++++++++++-- 7 files changed, 440 insertions(+), 73 deletions(-) diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 58c3e52e138..b694a5a7948 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -8,6 +8,7 @@ use rustc_hir::def::Res; use rustc_hir::{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/tests/ui/manual_filter_map.fixed b/tests/ui/manual_filter_map.fixed index de0d8614889..4936dc9b2e0 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 bd6516f038b..8c67e827b4c 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 465f1b19110..6e5bbe8f2aa 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 d69b6c1dcf3..54302beceff 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 1c4e18e31c8..afcc1825a9a 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 9dea42b7686..c1ac499f7c6 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 From 3388787615ce3535f8c18d25c12643b3021a407c Mon Sep 17 00:00:00 2001 From: Andrea Nall Date: Thu, 7 Jul 2022 19:30:37 +0000 Subject: [PATCH 42/84] Add test for and fix rust-lang/rust-clippy#9131 This lint seems to have been broken by #98446 --- clippy_lints/src/misc.rs | 2 +- tests/ui/used_underscore_binding.rs | 6 ++++++ tests/ui/used_underscore_binding.stderr | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index df2430ced6b..be7df08d89f 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, _)) + matches!(data.kind, ExpnKind::Macro(MacroKind::Attr|MacroKind::Derive, _)) } else { false } diff --git a/tests/ui/used_underscore_binding.rs b/tests/ui/used_underscore_binding.rs index 21d66d5df79..d20977d55d2 100644 --- a/tests/ui/used_underscore_binding.rs +++ b/tests/ui/used_underscore_binding.rs @@ -44,6 +44,12 @@ fn in_struct_field() { s._underscore_field += 1; } +/// Tests that we do not lint if the struct field is used in code created with derive. +#[derive(Clone, Debug)] +pub struct UnderscoreInStruct { + _foo: u32, +} + /// Tests that we do not lint if the underscore is not a prefix fn non_prefix_underscore(some_foo: u32) -> u32 { some_foo + 1 diff --git a/tests/ui/used_underscore_binding.stderr b/tests/ui/used_underscore_binding.stderr index 790b849210c..61a9161d212 100644 --- a/tests/ui/used_underscore_binding.stderr +++ b/tests/ui/used_underscore_binding.stderr @@ -31,7 +31,7 @@ LL | s._underscore_field += 1; | ^^^^^^^^^^^^^^^^^^^ error: used binding `_i` which is prefixed with an underscore. A leading underscore signals that a binding will not be used - --> $DIR/used_underscore_binding.rs:99:16 + --> $DIR/used_underscore_binding.rs:105:16 | LL | uses_i(_i); | ^^ From 782b484b799d638ee391b31dc322370432753722 Mon Sep 17 00:00:00 2001 From: Andrea Nall Date: Mon, 4 Jul 2022 13:25:31 -0500 Subject: [PATCH 43/84] Fix ICE in sugg::DerefDelegate with (named) closures rustc comiler internals helpfully tell us how to fix the issue: to get the signature of a closure, use `substs.as_closure().sig()` not `fn_sig()` Fixes ICE in #9041 --- clippy_utils/src/sugg.rs | 30 +++++++++++++++------ clippy_utils/src/ty.rs | 4 +++ tests/ui/crashes/ice-9041.rs | 8 ++++++ tests/ui/crashes/ice-9041.stderr | 10 +++++++ 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 +++++++++++++++++- 7 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 tests/ui/crashes/ice-9041.rs create mode 100644 tests/ui/crashes/ice-9041.stderr diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index aa119539b1b..d0d056886cb 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_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 @@ pub fn finish(&mut self) -> String { /// 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 b7da1abd373..cb3d91c0919 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -565,6 +565,9 @@ pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option(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 @@ -573,6 +576,7 @@ fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> 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::Opaque(id, _) => ty_sig(cx, cx.tcx.type_of(id)), ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig)), ty::Dynamic(bounds, _) => { let lang_items = cx.tcx.lang_items(); diff --git a/tests/ui/crashes/ice-9041.rs b/tests/ui/crashes/ice-9041.rs new file mode 100644 index 00000000000..55cc9bc99a0 --- /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 00000000000..f5038f0a848 --- /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/search_is_some_fixable_some.fixed b/tests/ui/search_is_some_fixable_some.fixed index 5a2aee465d1..385a9986aba 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 0e98ae18a21..67e190ee378 100644 --- a/tests/ui/search_is_some_fixable_some.rs +++ b/tests/ui/search_is_some_fixable_some.rs @@ -219,3 +219,33 @@ struct FieldProjection<'a> { 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 8b424f18ef5..c5c3c92c918 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 From 196174ddad8cac276db8d44c22c7739f614a0fbc Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Fri, 17 Jun 2022 22:21:42 -0400 Subject: [PATCH 44/84] Changes to `let_unit_value` * View through locals in `let_unit_value` when determining if inference is required * Don't remove typed let bindings for more functions --- clippy_lints/src/unit_types/let_unit_value.rs | 127 +++++++++++++----- clippy_lints/src/unit_types/mod.rs | 4 +- clippy_utils/src/visitors.rs | 46 +++++++ tests/ui/let_unit.fixed | 62 ++++++++- tests/ui/let_unit.rs | 62 ++++++++- tests/ui/let_unit.stderr | 38 +++--- 6 files changed, 266 insertions(+), 73 deletions(-) diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index 17d60a40dce..c98f9e01a8d 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -1,29 +1,24 @@ 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, Local, PatKind}; +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, TypeFoldable, TypeSuperFoldable, TypeVisitor}; +use rustc_middle::ty; use super::LET_UNIT_VALUE; -pub(super) fn check(cx: &LateContext<'_>, local: &Local<'_>) { +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(), 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 local.ty.is_some() && expr_needs_inferred_result(cx, init) { if !matches!(local.pat.kind, PatKind::Wild) { span_lint_and_then( cx, @@ -62,48 +57,106 @@ pub(super) fn check(cx: &LateContext<'_>, local: &Local<'_>) { } } -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 e57d4fc3b89..6aa86a57c9b 100644 --- a/clippy_lints/src/unit_types/mod.rs +++ b/clippy_lints/src/unit_types/mod.rs @@ -98,8 +98,8 @@ declare_lint_pass!(UnitTypes => [LET_UNIT_VALUE, UNIT_CMP, UNIT_ARG]); -impl LateLintPass<'_> for UnitTypes { - fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) { +impl<'tcx> LateLintPass<'tcx> for UnitTypes { + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { let_unit_value::check(cx, local); } diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index 68cfa8c1aa8..e89a46b8538 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -617,3 +617,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/tests/ui/let_unit.fixed b/tests/ui/let_unit.fixed index 18c2672f880..3d2a9635d75 100644 --- a/tests/ui/let_unit.fixed +++ b/tests/ui/let_unit.fixed @@ -2,8 +2,7 @@ #![feature(lint_reasons)] #![warn(clippy::let_unit_value)] -#![allow(clippy::no_effect)] -#![allow(unused)] +#![allow(unused, clippy::no_effect, clippy::needless_late_init, path_statements)] macro_rules! let_and_return { ($n:expr) => {{ @@ -73,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 @@ -86,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 _: () = { @@ -113,6 +116,53 @@ 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; + } + } } fn attributes() { diff --git a/tests/ui/let_unit.rs b/tests/ui/let_unit.rs index c9c4582a956..625927f76f1 100644 --- a/tests/ui/let_unit.rs +++ b/tests/ui/let_unit.rs @@ -2,8 +2,7 @@ #![feature(lint_reasons)] #![warn(clippy::let_unit_value)] -#![allow(clippy::no_effect)] -#![allow(unused)] +#![allow(unused, clippy::no_effect, clippy::needless_late_init, path_statements)] macro_rules! let_and_return { ($n:expr) => {{ @@ -73,8 +72,8 @@ fn f2(_: T) -> U { 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 @@ -86,8 +85,12 @@ fn f4(mut x: Vec) -> T { 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 _: () = { @@ -113,6 +116,53 @@ fn f4(mut x: Vec) -> T { 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; + } + } } fn attributes() { diff --git a/tests/ui/let_unit.stderr b/tests/ui/let_unit.stderr index 47a2a6e3cc1..49da74ca7e1 100644 --- a/tests/ui/let_unit.stderr +++ b/tests/ui/let_unit.stderr @@ -1,5 +1,5 @@ error: this let-binding has unit value - --> $DIR/let_unit.rs:15:5 + --> $DIR/let_unit.rs:14:5 | LL | let _x = println!("x"); | ^^^^^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `println!("x");` @@ -7,13 +7,13 @@ LL | let _x = println!("x"); = note: `-D clippy::let-unit-value` implied by `-D warnings` error: this let-binding has unit value - --> $DIR/let_unit.rs:19:9 + --> $DIR/let_unit.rs:18:9 | LL | let _a = (); | ^^^^^^^^^^^^ help: omit the `let` binding: `();` error: this let-binding has unit value - --> $DIR/let_unit.rs:54:5 + --> $DIR/let_unit.rs:53:5 | LL | / let _ = v LL | | .into_iter() @@ -36,7 +36,7 @@ LL + .unwrap(); | error: this let-binding has unit value - --> $DIR/let_unit.rs:81:5 + --> $DIR/let_unit.rs:80:5 | LL | let x: () = f(); // Lint. | ^^^^-^^^^^^^^^^^ @@ -44,7 +44,7 @@ LL | let x: () = f(); // Lint. | help: use a wild (`_`) binding: `_` error: this let-binding has unit value - --> $DIR/let_unit.rs:84:5 + --> $DIR/let_unit.rs:83:5 | LL | let x: () = f2(0i32); // Lint. | ^^^^-^^^^^^^^^^^^^^^^ @@ -52,31 +52,19 @@ LL | let x: () = f2(0i32); // Lint. | help: use a wild (`_`) binding: `_` error: this let-binding has unit value - --> $DIR/let_unit.rs:86:5 + --> $DIR/let_unit.rs:85:5 | LL | let _: () = f3(()); // Lint | ^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f3(());` error: this let-binding has unit value - --> $DIR/let_unit.rs:87:5 + --> $DIR/let_unit.rs:86:5 | LL | let x: () = f3(()); // Lint | ^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f3(());` error: this let-binding has unit value - --> $DIR/let_unit.rs:89:5 - | -LL | let _: () = f4(vec![()]); // Lint - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f4(vec![()]);` - -error: this let-binding has unit value - --> $DIR/let_unit.rs:90: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:99: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:110: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 From 2872b7e0a48e6ae41570804afa88bd12bf5f507a Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 27 Jun 2022 12:48:38 -0400 Subject: [PATCH 45/84] Allow `let () = ..` as type inference for `let_unit_value` --- clippy_lints/src/unit_types/let_unit_value.rs | 19 +++++++++++-------- tests/ui/let_unit.fixed | 2 ++ tests/ui/let_unit.rs | 2 ++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index c98f9e01a8d..aec028d5c48 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -18,20 +18,23 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { && !in_external_macro(cx.sess(), local.span) && cx.typeck_results().pat_ty(local.pat).is_unit() { - if local.ty.is_some() && expr_needs_inferred_result(cx, init) { - 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, 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 + ); }, ); } diff --git a/tests/ui/let_unit.fixed b/tests/ui/let_unit.fixed index 3d2a9635d75..6343cff0f7f 100644 --- a/tests/ui/let_unit.fixed +++ b/tests/ui/let_unit.fixed @@ -163,6 +163,8 @@ fn _returns_generic() { let _: () = opt; } } + + let () = f(); } fn attributes() { diff --git a/tests/ui/let_unit.rs b/tests/ui/let_unit.rs index 625927f76f1..c9bb2849f5c 100644 --- a/tests/ui/let_unit.rs +++ b/tests/ui/let_unit.rs @@ -163,6 +163,8 @@ fn f5(x: bool) -> Option { let _: () = opt; } } + + let () = f(); } fn attributes() { From 9fa12def3c231f3b60f53d68c3787b8a82376843 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 7 Jul 2022 10:45:20 -0400 Subject: [PATCH 46/84] Improve `while_let_on_iterator` suggestion inside an `FnOnce` closure --- .../src/loops/while_let_on_iterator.rs | 19 +++-- clippy_utils/src/lib.rs | 60 +++++++++++--- clippy_utils/src/ty.rs | 48 ++++++++++-- tests/ui/while_let_on_iterator.fixed | 46 ++++++++++- tests/ui/while_let_on_iterator.rs | 46 ++++++++++- tests/ui/while_let_on_iterator.stderr | 78 +++++++++++-------- 6 files changed, 240 insertions(+), 57 deletions(-) diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index a5715975066..c18151a44e2 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::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::{def::Res, 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 @@ struct AfterLoopVisitor<'a, 'b, 'tcx> { 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 @@ struct NestedLoopVisitor<'a, 'b, 'tcx> { 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 visit_expr(&mut self, e: &'tcx 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_utils/src/lib.rs b/clippy_utils/src/lib.rs index 0e739303683..0d31203f3f7 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -93,7 +93,9 @@ ArraySimplifiedType, BoolSimplifiedType, CharSimplifiedType, FloatSimplifiedType, IntSimplifiedType, PtrSimplifiedType, SliceSimplifiedType, StrSimplifiedType, UintSimplifiedType, }; -use rustc_middle::ty::{layout::IntegerExt, BorrowKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeFoldable, UpvarCapture}; +use rustc_middle::ty::{ + layout::IntegerExt, BorrowKind, ClosureKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeFoldable, UpvarCapture, +}; use rustc_middle::ty::{FloatTy, IntTy, UintTy}; use rustc_semver::RustcVersion; use rustc_session::Session; @@ -105,7 +107,7 @@ 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 { @@ -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/ty.rs b/clippy_utils/src/ty.rs index cb3d91c0919..2bfe550c895 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -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 @@ pub fn input(self, i: usize) -> Option>> { /// 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,16 +549,20 @@ pub fn input_with_hir(self, i: usize) -> Option<(Option<&'tcx hir::Ty<'tcx>>, Bi /// 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()) } @@ -575,9 +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::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)), + ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig, None)), ty::Dynamic(bounds, _) => { let lang_items = cx.tcx.lang_items(); match bounds.principal() { @@ -793,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/tests/ui/while_let_on_iterator.fixed b/tests/ui/while_let_on_iterator.fixed index e9ff64431e1..c57c4673634 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 03da39526b2..8b9a2dbcce3 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 @@ impl> S { 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 42859243855..3236765e1db 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 From d0b8f7508c584f74ed668b1bf665657894c63a4a Mon Sep 17 00:00:00 2001 From: bors Date: Fri, 8 Jul 2022 16:12:00 +0000 Subject: [PATCH 47/84] Simplify if let statements fixes: #8288 --- changelog: Allowing [`qustion_mark`] lint to check `if let` expressions that immediatly return unwrapped value --- clippy_lints/src/question_mark.rs | 296 ++++++++++++++++++------------ tests/ui/needless_match.fixed | 1 + tests/ui/needless_match.rs | 1 + tests/ui/needless_match.stderr | 4 +- tests/ui/question_mark.fixed | 73 +++++--- tests/ui/question_mark.rs | 77 +++++--- tests/ui/question_mark.stderr | 52 ++++-- 7 files changed, 323 insertions(+), 181 deletions(-) diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 899568a933f..f0155ed6051 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_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/tests/ui/needless_match.fixed b/tests/ui/needless_match.fixed index b997e5316cf..0c9178fb85e 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 90482775a1e..f66f01d7cca 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 2d679631c6f..5bc79800a1a 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/question_mark.fixed b/tests/ui/question_mark.fixed index 13ce0f32d4b..c4c9c821433 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 60590fd9311..cdbc7b1606f 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 8d782b71dd6..1b6cd524b2f 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 From d251bd96e7d4681ed9be9c13bc7539c17aa58e85 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Fri, 8 Jul 2022 12:54:20 -0400 Subject: [PATCH 48/84] Add `for_each_expr` --- clippy_utils/src/visitors.rs | 70 +++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index 68cfa8c1aa8..3c8a678c439 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -5,7 +5,7 @@ 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::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 whe 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. From 4e60134ec31c4424259f992489615b3fd9614253 Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Fri, 8 Jul 2022 22:07:44 +0300 Subject: [PATCH 49/84] Fix example header --- clippy_lints/src/octal_escapes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/octal_escapes.rs b/clippy_lints/src/octal_escapes.rs index 3ab4b6c4f6f..6ad6837f0e3 100644 --- a/clippy_lints/src/octal_escapes.rs +++ b/clippy_lints/src/octal_escapes.rs @@ -31,7 +31,7 @@ /// 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 From d053a3dae0dd7cf1cd22b7ff6d2b0c0200036766 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Mon, 9 May 2022 15:18:53 -0700 Subject: [PATCH 50/84] add opt in attribute for stable-in-unstable items --- clippy_utils/src/qualify_min_const_fn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 498dcbb8900..0e19b0296f6 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -353,7 +353,7 @@ fn check_terminator<'a, 'tcx>( fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: Option) -> bool { tcx.is_const_fn(def_id) && tcx.lookup_const_stability(def_id).map_or(true, |const_stab| { - if let rustc_attr::StabilityLevel::Stable { since } = const_stab.level { + if let rustc_attr::StabilityLevel::Stable { since, .. } = const_stab.level { // Checking MSRV is manually necessary because `rustc` has no such concept. This entire // function could be removed if `rustc` provided a MSRV-aware version of `is_const_fn`. // as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262. From 55563f9ce1cea79a473954d70dea620822514c39 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Fri, 8 Jul 2022 15:29:23 -0400 Subject: [PATCH 51/84] Fixes for `branches_sharing_code` * Don't suggest moving modifications to locals used in any of the condition expressions * Don't suggest moving anything after a local with a significant drop --- clippy_lints/src/copies.rs | 64 +++++++++++++++++-- clippy_utils/src/lib.rs | 2 +- .../branches_sharing_code/false_positives.rs | 54 ++++++++++++++++ 3 files changed, 113 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 1deff9684a1..0e3d9317590 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_utils/src/lib.rs b/clippy_utils/src/lib.rs index 0e739303683..fd0c6869929 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -890,7 +890,7 @@ fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind { 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(..) => { diff --git a/tests/ui/branches_sharing_code/false_positives.rs b/tests/ui/branches_sharing_code/false_positives.rs index 06448200951..5e3a1a29693 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"); + } + } } From 901588432539a420563e5c1512b40bc8f3cc264e Mon Sep 17 00:00:00 2001 From: alex-semenyuk Date: Sat, 9 Jul 2022 13:00:24 +0300 Subject: [PATCH 52/84] Fix small mistakes --- lintcheck/Cargo.toml | 2 +- lintcheck/README.md | 2 +- lintcheck/src/main.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index 504d58b5197..254f391e4bd 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" diff --git a/lintcheck/README.md b/lintcheck/README.md index 8c169506e53..6f3d23382ce 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 dff9d27db0a..9ee25280f04 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); From 95b78799c38ee7e9b7489f265963b69405686e06 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 9 Jul 2022 08:15:13 -0400 Subject: [PATCH 53/84] Ignore the `IntoIterator::into_iter` call from for loops in `significant_drop_in_scrutinee` --- clippy_lints/src/matches/significant_drop_in_scrutinee.rs | 4 ++++ tests/ui/significant_drop_in_scrutinee.rs | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 0704a5af525..b0b15b3f54c 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/tests/ui/significant_drop_in_scrutinee.rs b/tests/ui/significant_drop_in_scrutinee.rs index 185e5009b60..84ecf1ea53e 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() {} From 97cd46fa33a36bc1edcbc8eba2c142be5da34055 Mon Sep 17 00:00:00 2001 From: alex-semenyuk Date: Sun, 10 Jul 2022 13:11:19 +0300 Subject: [PATCH 54/84] Fix span for or_fun_call --- clippy_lints/src/methods/or_fun_call.rs | 23 ++--- tests/ui/or_fun_call.fixed | 12 ++- tests/ui/or_fun_call.stderr | 106 +++++------------------- 3 files changed, 37 insertions(+), 104 deletions(-) diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 3d1208824fa..16e160e6a17 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 @@ fn check_unwrap_or_default( 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,15 @@ fn check_unwrap_or_default( || (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 - ); - + let span_replace_word = method_span.with_hi(span.hi()); span_lint_and_sugg( cx, OR_FUN_CALL, - sugg_span, + span_replace_word, &format!("use of `{}` followed by a call to `{}`", name, path), "try this", - sugg, - applicability, + format!("unwrap_or_default()"), + Applicability::MachineApplicable, ); true @@ -171,7 +162,7 @@ fn check_general_case<'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/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index 123aed40251..fdb08d953ff 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 dfe15654bc3..4c5938ab88b 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 From 783992e69389bff1890adaca4cac5967c6724b40 Mon Sep 17 00:00:00 2001 From: alex-semenyuk Date: Sun, 10 Jul 2022 13:34:58 +0300 Subject: [PATCH 55/84] Fix dogfood issue for or_fun_call --- clippy_lints/src/methods/or_fun_call.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 16e160e6a17..5a356108434 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -59,7 +59,7 @@ fn check_unwrap_or_default( span_replace_word, &format!("use of `{}` followed by a call to `{}`", name, path), "try this", - format!("unwrap_or_default()"), + "unwrap_or_default()".to_string(), Applicability::MachineApplicable, ); From 25fe4193ec9403dbb942075b782e4b91b420f2bd Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Sun, 10 Jul 2022 23:01:13 +0900 Subject: [PATCH 56/84] change applicability type to MaybeIncorrect --- .../src/loops/explicit_counter_loop.rs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/loops/explicit_counter_loop.rs b/clippy_lints/src/loops/explicit_counter_loop.rs index fc50e8addcc..32a14887d20 100644 --- a/clippy_lints/src/loops/explicit_counter_loop.rs +++ b/clippy_lints/src/loops/explicit_counter_loop.rs @@ -1,5 +1,5 @@ use super::{make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT_COUNTER_LOOP}; -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_with_applicability; use clippy_utils::{get_enclosing_block, is_integer_const}; use if_chain::if_chain; @@ -34,24 +34,29 @@ 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 int_name = match ty.map(Ty::kind) { // usize or inferred Some(ty::Uint(UintTy::Usize)) | None => { - span_lint_and_sugg( + span_lint_and_then( cx, EXPLICIT_COUNTER_LOOP, expr.span.with_hi(arg.span.hi()), &format!("the variable `{}` is used as a loop counter", name), - "consider using", - format!( - "for ({}, {}) in {}.enumerate()", - name, - snippet_with_applicability(cx, pat.span, "item", &mut applicability), - make_iterator_snippet(cx, arg, &mut applicability), - ), - applicability, + |diag| { + diag.span_suggestion( + expr.span.with_hi(arg.span.hi()), + "consider using", + format!( + "for ({}, {}) in {}.enumerate()", + name, + snippet_with_applicability(cx, pat.span, "item", &mut applicability), + make_iterator_snippet(cx, arg, &mut applicability), + ), + applicability, + ); + } ); return; } From c3c4cda50b1b1b212eb02e5d822b3fd1479dc24c Mon Sep 17 00:00:00 2001 From: alex-semenyuk Date: Mon, 11 Jul 2022 10:34:01 +0300 Subject: [PATCH 57/84] Address review --- clippy_lints/src/methods/or_fun_call.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 5a356108434..6af134019a4 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -52,11 +52,10 @@ fn check_unwrap_or_default( || (matches!(path, sym::new) && implements_default(arg, default_trait_id)); then { - let span_replace_word = method_span.with_hi(span.hi()); span_lint_and_sugg( cx, OR_FUN_CALL, - span_replace_word, + method_span.with_hi(span.hi()), &format!("use of `{}` followed by a call to `{}`", name, path), "try this", "unwrap_or_default()".to_string(), From c8d32e53a2b0a4a697343bcb8c0d515de5e2411b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Miku=C5=82a?= Date: Mon, 11 Jul 2022 17:01:46 +0200 Subject: [PATCH 58/84] Fix Clippy version in `derive_partial_eq_without_eq` lint It was first added to Rust in https://github.com/rust-lang/rust/pull/97248 which missed 1.62 just by few days. --- clippy_lints/src/derive.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 28f218a8e34..a982990e418 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -189,7 +189,7 @@ /// 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`" From 57a8f6dd9de0ef7af5ad0cc160c20fda46a8caa9 Mon Sep 17 00:00:00 2001 From: Troy Hinckley Date: Mon, 11 Jul 2022 15:49:11 -0500 Subject: [PATCH 59/84] Fix broken link in CONTRIBUTING.md --- CONTRIBUTING.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e81e7ceedcb..56cb5a1538c 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) From 9225ebd7864876771b996e60a7ddda0be5a6f623 Mon Sep 17 00:00:00 2001 From: Ding Xiang Fei Date: Thu, 2 Jun 2022 22:39:47 +0800 Subject: [PATCH 60/84] lower let-else in MIR instead --- clippy_lints/src/attrs.rs | 2 +- clippy_lints/src/copies.rs | 6 +++--- clippy_lints/src/default.rs | 2 +- clippy_lints/src/default_numeric_fallback.rs | 2 +- clippy_lints/src/entry.rs | 2 +- clippy_lints/src/explicit_write.rs | 2 +- clippy_lints/src/let_if_seq.rs | 2 +- clippy_lints/src/let_underscore.rs | 4 ++-- clippy_lints/src/loops/needless_collect.rs | 4 ++-- clippy_lints/src/loops/never_loop.rs | 2 +- clippy_lints/src/loops/utils.rs | 6 +++--- clippy_lints/src/loops/while_let_loop.rs | 2 +- clippy_lints/src/loops/while_let_on_iterator.rs | 4 ++-- clippy_lints/src/map_unit_fn.rs | 2 +- clippy_lints/src/matches/mod.rs | 12 +++++++++--- clippy_lints/src/methods/str_splitn.rs | 2 +- clippy_lints/src/misc.rs | 2 +- clippy_lints/src/mixed_read_write_in_expression.rs | 4 ++-- clippy_lints/src/mut_key.rs | 7 ++++++- clippy_lints/src/needless_late_init.rs | 4 ++-- clippy_lints/src/no_effect.rs | 3 ++- clippy_lints/src/only_used_in_recursion.rs | 4 ++-- clippy_lints/src/pattern_type_mismatch.rs | 2 +- clippy_lints/src/read_zero_byte_vec.rs | 2 +- clippy_lints/src/redundant_closure_call.rs | 2 +- clippy_lints/src/returns.rs | 7 ++----- clippy_lints/src/slow_vector_initialization.rs | 2 +- clippy_lints/src/swap.rs | 2 +- clippy_lints/src/types/mod.rs | 4 ++-- clippy_lints/src/uninit_vec.rs | 2 +- clippy_lints/src/unit_types/let_unit_value.rs | 2 +- clippy_lints/src/utils/author.rs | 2 +- clippy_lints/src/vec_init_then_push.rs | 2 +- clippy_utils/src/hir_utils.rs | 8 ++++++-- clippy_utils/src/lib.rs | 2 +- 35 files changed, 66 insertions(+), 53 deletions(-) diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index 4bcbeacf9fe..93ce3b30fb1 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -505,7 +505,7 @@ fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_ .as_ref() .map_or(false, |e| is_relevant_expr(cx, typeck_results, e)), |stmt| match &stmt.kind { - StmtKind::Local(_) => true, + StmtKind::Local(_, _) => true, StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr), StmtKind::Item(_) => false, }, diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 1deff9684a1..0b9fdb891b1 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -324,7 +324,7 @@ fn end_span(&self, b: &Block<'_>, sm: &SourceMap) -> Option { /// If the statement is a local, checks if the bound names match the expected list of names. fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool { - if let StmtKind::Local(l) = s.kind { + if let StmtKind::Local(l, _) = s.kind { let mut i = 0usize; let mut res = true; l.pat.each_binding_or_first(&mut |_, _, _, name| { @@ -349,7 +349,7 @@ fn eq_stmts( eq: &mut HirEqInterExpr<'_, '_, '_>, moved_bindings: &mut Vec<(HirId, Symbol)>, ) -> bool { - (if let StmtKind::Local(l) = stmt.kind { + (if let StmtKind::Local(l, _) = stmt.kind { let old_count = moved_bindings.len(); l.pat.each_binding_or_first(&mut |_, id, _, name| { moved_bindings.push((id, name.name)); @@ -435,7 +435,7 @@ fn scan_block_for_eq(cx: &LateContext<'_>, _conds: &[&Expr<'_>], block: &Block<' // Clear out all locals seen at the end so far. None of them can be moved. let stmts = &blocks[0].stmts; for stmt in &stmts[stmts.len() - init..=stmts.len() - offset] { - if let StmtKind::Local(l) = stmt.kind { + if let StmtKind::Local(l, _) = stmt.kind { l.pat.each_binding_or_first(&mut |_, id, _, _| { eq.locals.remove(&id); }); diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index d99a1aa2969..7fe3443858a 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -126,7 +126,7 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) { // checked and the name of the bound variable let (local, variant, binding_name, binding_type, span) = if_chain! { // only take `let ...` statements - if let StmtKind::Local(local) = stmt.kind; + if let StmtKind::Local(local, _) = stmt.kind; if let Some(expr) = local.init; if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id); if !expr.span.from_expansion(); diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index fb418a3251f..0f374d12a84 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -192,7 +192,7 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { match stmt.kind { - StmtKind::Local(local) => { + StmtKind::Local(local, _) => { if local.ty.is_some() { self.ty_bounds.push(TyBound::Any); } else { diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index 27743a0ebec..e0986b710c5 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -386,7 +386,7 @@ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { } }, StmtKind::Expr(e) => self.visit_expr(e), - StmtKind::Local(l) => { + StmtKind::Local(l, _) => { self.visit_pat(l.pat); if let Some(e) = l.init { self.allow_insert_closure &= !self.in_tail_pos; diff --git a/clippy_lints/src/explicit_write.rs b/clippy_lints/src/explicit_write.rs index 5bf4313b41a..bd1ac3371b0 100644 --- a/clippy_lints/src/explicit_write.rs +++ b/clippy_lints/src/explicit_write.rs @@ -116,7 +116,7 @@ fn look_in_block<'tcx, 'hir>(cx: &LateContext<'tcx>, kind: &'tcx ExprKind<'hir>) if_chain! { if let ExprKind::Block(block, _label @ None) = kind; if let Block { - stmts: [Stmt { kind: StmtKind::Local(local), .. }], + stmts: [Stmt { kind: StmtKind::Local(local, _), .. }], expr: Some(expr_end_of_block), rules: BlockCheckMode::DefaultBlock, .. diff --git a/clippy_lints/src/let_if_seq.rs b/clippy_lints/src/let_if_seq.rs index 56bbbbbc819..5dcb86feb76 100644 --- a/clippy_lints/src/let_if_seq.rs +++ b/clippy_lints/src/let_if_seq.rs @@ -62,7 +62,7 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { while let Some(stmt) = it.next() { if_chain! { if let Some(expr) = it.peek(); - if let hir::StmtKind::Local(local) = stmt.kind; + if let hir::StmtKind::Local(local, _) = stmt.kind; if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind; if let hir::StmtKind::Expr(if_) = expr.kind; if let hir::ExprKind::If(hir::Expr { kind: hir::ExprKind::DropTemps(cond), ..}, then, else_) = if_.kind; diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index 176787497eb..a37dfb7b715 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -2,7 +2,7 @@ use clippy_utils::ty::{is_must_use_ty, match_type}; use clippy_utils::{is_must_use_func_call, paths}; use if_chain::if_chain; -use rustc_hir::{Local, PatKind}; +use rustc_hir::{Block, Local, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::subst::GenericArgKind; @@ -109,7 +109,7 @@ ]; impl<'tcx> LateLintPass<'tcx> for LetUnderscore { - fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) { + fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>, _: Option<&Block<'_>>) { if in_external_macro(cx.tcx.sess, local.span) { return; } diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index ddaffc75188..ba0f01d9ed2 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -76,7 +76,7 @@ fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCo if let ExprKind::Block(block, _) = expr.kind { for stmt in block.stmts { if_chain! { - if let StmtKind::Local(local) = stmt.kind; + if let StmtKind::Local(local, _) = stmt.kind; if let PatKind::Binding(_, id, ..) = local.pat.kind; if let Some(init_expr) = local.init; if let ExprKind::MethodCall(method_name, &[ref iter_source], ..) = init_expr.kind; @@ -276,7 +276,7 @@ fn get_expr_and_hir_id_from_stmt<'v>(stmt: &'v Stmt<'v>) -> Option<(&'v Expr<'v> match stmt.kind { StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some((expr, None)), StmtKind::Item(..) => None, - StmtKind::Local(Local { init, pat, .. }) => { + StmtKind::Local(Local { init, pat, .. }, _) => { if let PatKind::Binding(_, hir_id, ..) = pat.kind { init.map(|init_expr| (init_expr, Some(hir_id))) } else { diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 32de20f6531..c60d5518060 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -104,7 +104,7 @@ fn never_loop_expr_seq<'a, T: Iterator>>(es: &mut T, main_lo fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> { match stmt.kind { StmtKind::Semi(e, ..) | StmtKind::Expr(e, ..) => Some(e), - StmtKind::Local(local) => local.init, + StmtKind::Local(local, _) => local.init, StmtKind::Item(..) => None, } } diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs index 4801a84eb92..661af8fe642 100644 --- a/clippy_lints/src/loops/utils.rs +++ b/clippy_lints/src/loops/utils.rs @@ -4,7 +4,7 @@ use rustc_ast::ast::{LitIntType, LitKind}; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, Visitor}; -use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt}; +use rustc_hir::{BinOpKind, Block, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty}; @@ -148,7 +148,7 @@ pub(super) fn get_result(&self) -> Option<(Symbol, Option>, &'tcx Expr< impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; - fn visit_local(&mut self, l: &'tcx Local<'_>) { + fn visit_local(&mut self, l: &'tcx Local<'_>, e: Option<&'tcx Block<'_>>) { // Look for declarations of the variable if_chain! { if l.pat.hir_id == self.var_id; @@ -166,7 +166,7 @@ fn visit_local(&mut self, l: &'tcx Local<'_>) { } } - walk_local(self, l); + walk_local(self, l, e); } fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { diff --git a/clippy_lints/src/loops/while_let_loop.rs b/clippy_lints/src/loops/while_let_loop.rs index 45af6be2653..8c352494252 100644 --- a/clippy_lints/src/loops/while_let_loop.rs +++ b/clippy_lints/src/loops/while_let_loop.rs @@ -11,7 +11,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { let (init, has_trailing_exprs) = match (loop_block.stmts, loop_block.expr) { ([stmt, stmts @ ..], expr) => { - if let StmtKind::Local(&Local { init: Some(e), .. }) | StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind { + if let StmtKind::Local(&Local { init: Some(e), .. }, None) | StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind { (e, !stmts.is_empty() || expr.is_some()) } else { return; diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index a5715975066..1abdfaac7ec 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -8,7 +8,7 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp}; +use rustc_hir::{def::Res, Block, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::adjustment::Adjust; use rustc_span::{symbol::sym, Symbol}; @@ -283,7 +283,7 @@ struct NestedLoopVisitor<'a, 'b, 'tcx> { used_after: bool, } impl<'a, 'b, 'tcx> Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> { - fn visit_local(&mut self, l: &'tcx Local<'_>) { + fn visit_local(&mut self, l: &'tcx Local<'_>, _: Option<&'tcx Block<'_>>) { if !self.after_loop { l.pat.each_binding_or_first(&mut |_, id, _, _| { if id == self.local_id { diff --git a/clippy_lints/src/map_unit_fn.rs b/clippy_lints/src/map_unit_fn.rs index 663246b4c86..3bfe5428133 100644 --- a/clippy_lints/src/map_unit_fn.rs +++ b/clippy_lints/src/map_unit_fn.rs @@ -144,7 +144,7 @@ fn reduce_unit_expression<'a>(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> // If block only contains statements, // reduce `{ X; }` to `X` or `X;` match inner_stmt.kind { - hir::StmtKind::Local(local) => Some(local.span), + hir::StmtKind::Local(local, _) => Some(local.span), hir::StmtKind::Expr(e) => Some(e.span), hir::StmtKind::Semi(..) => Some(inner_stmt.span), hir::StmtKind::Item(..) => None, diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index b2a873ef582..cc8674a2006 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1,6 +1,6 @@ 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_hir::{Arm, Block, Expr, ExprKind, Local, MatchSource, Pat}; use rustc_lexer::{tokenize, TokenKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; @@ -1040,8 +1040,14 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { } } - fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { - self.infallible_destructuring_match_linted |= infallible_destructuring_match::check(cx, local); + fn check_local( + &mut self, + cx: &LateContext<'tcx>, + local: &'tcx Local<'_>, + els: Option<&'tcx Block<'_>>, + ) { + self.infallible_destructuring_match_linted |= + els.is_none() && infallible_destructuring_match::check(cx, local); } fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs index 4ac738272d0..80dbd14b2c5 100644 --- a/clippy_lints/src/methods/str_splitn.rs +++ b/clippy_lints/src/methods/str_splitn.rs @@ -220,7 +220,7 @@ fn indirect_usage<'tcx>( init: Some(init_expr), hir_id: local_hir_id, .. - }) = stmt.kind + }, _) = stmt.kind { let mut path_to_binding = None; expr_visitor(cx, |expr| { diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index be7df08d89f..2ad7ac60b92 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -161,7 +161,7 @@ fn check_fn( fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { if_chain! { if !in_external_macro(cx.tcx.sess, stmt.span); - if let StmtKind::Local(local) = stmt.kind; + if let StmtKind::Local(local, _) = stmt.kind; if let PatKind::Binding(an, .., name, None) = local.pat.kind; if let Some(init) = local.init; if an == BindingAnnotation::Ref || an == BindingAnnotation::RefMut; diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index a2419c277e9..de993c3c0a4 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -96,7 +96,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { } fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { match stmt.kind { - StmtKind::Local(local) => { + StmtKind::Local(local, _) => { if let Local { init: Some(e), .. } = local { DivergenceVisitor { cx }.visit_expr(e); } @@ -273,7 +273,7 @@ fn check_stmt<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, stmt: &'tcx Stmt<'_>) - StmtKind::Expr(expr) | StmtKind::Semi(expr) => check_expr(vis, expr), // If the declaration is of a local variable, check its initializer // expression if it has one. Otherwise, keep going. - StmtKind::Local(local) => local + StmtKind::Local(local, _) => local .init .as_ref() .map_or(StopEarly::KeepGoing, |expr| check_expr(vis, expr)), diff --git a/clippy_lints/src/mut_key.rs b/clippy_lints/src/mut_key.rs index 4db103bbc13..251181165b0 100644 --- a/clippy_lints/src/mut_key.rs +++ b/clippy_lints/src/mut_key.rs @@ -101,7 +101,12 @@ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitIte } } - fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) { + fn check_local( + &mut self, + cx: &LateContext<'_>, + local: &hir::Local<'_>, + _: Option<&hir::Block<'_>>, + ) { if let hir::PatKind::Wild = local.pat.kind { return; } diff --git a/clippy_lints/src/needless_late_init.rs b/clippy_lints/src/needless_late_init.rs index ff2999b1f4a..fa1c09d8f90 100644 --- a/clippy_lints/src/needless_late_init.rs +++ b/clippy_lints/src/needless_late_init.rs @@ -92,7 +92,7 @@ fn contains_let(cond: &Expr<'_>) -> bool { } fn stmt_needs_ordered_drop(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool { - let StmtKind::Local(local) = stmt.kind else { return false }; + let StmtKind::Local(local, _) = stmt.kind else { return false }; !local.pat.walk_short(|pat| { if let PatKind::Binding(.., None) = pat.kind { !needs_ordered_drop(cx, cx.typeck_results().pat_ty(pat)) @@ -367,7 +367,7 @@ fn check<'tcx>( } impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit { - fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>, _: Option<&'tcx Block<'tcx>>) { let mut parents = cx.tcx.hir().parent_iter(local.hir_id); if_chain! { if let Local { diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 6598413c77e..105e145ac30 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -88,10 +88,11 @@ fn check_no_effect(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool { span_lint_hir(cx, NO_EFFECT, expr.hir_id, stmt.span, "statement with no effect"); return true; } - } else if let StmtKind::Local(local) = stmt.kind { + } else if let StmtKind::Local(local, els) = stmt.kind { if_chain! { if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id); if let Some(init) = local.init; + if els.is_none(); if !local.pat.span.from_expansion(); if has_no_effect(cx, init); if let PatKind::Binding(_, _, ident, _) = local.pat.kind; diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index 677ac998b56..c7f8f2f8d70 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -261,13 +261,13 @@ fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) { match s.kind { StmtKind::Local(Local { pat, init: Some(init), .. - }) => { + }, _) => { self.visit_pat_expr(pat, init, false); }, StmtKind::Item(_) | StmtKind::Expr(_) | StmtKind::Semi(_) => { walk_stmt(self, s); }, - StmtKind::Local(_) => {}, + StmtKind::Local(_, _) => {}, } self.ret_vars.clear(); } diff --git a/clippy_lints/src/pattern_type_mismatch.rs b/clippy_lints/src/pattern_type_mismatch.rs index a4d265111f9..83e18e20711 100644 --- a/clippy_lints/src/pattern_type_mismatch.rs +++ b/clippy_lints/src/pattern_type_mismatch.rs @@ -83,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { - if let StmtKind::Local(local) = stmt.kind { + if let StmtKind::Local(local, _) = stmt.kind { if in_external_macro(cx.sess(), local.pat.span) { return; } diff --git a/clippy_lints/src/read_zero_byte_vec.rs b/clippy_lints/src/read_zero_byte_vec.rs index 9538a810473..8316efad1ff 100644 --- a/clippy_lints/src/read_zero_byte_vec.rs +++ b/clippy_lints/src/read_zero_byte_vec.rs @@ -53,7 +53,7 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &hir::Block<'tcx>) { for (idx, stmt) in block.stmts.iter().enumerate() { if !stmt.span.from_expansion() // matches `let v = Vec::new();` - && let StmtKind::Local(local) = stmt.kind + && let StmtKind::Local(local, _) = stmt.kind && let Local { pat, init: Some(init), .. } = local && let PatKind::Binding(_, _, ident, _) = pat.kind && let Some(vec_init_kind) = get_vec_init_kind(cx, init) diff --git a/clippy_lints/src/redundant_closure_call.rs b/clippy_lints/src/redundant_closure_call.rs index 65ed798867d..48bf14d511c 100644 --- a/clippy_lints/src/redundant_closure_call.rs +++ b/clippy_lints/src/redundant_closure_call.rs @@ -133,7 +133,7 @@ fn nested_visit_map(&mut self) -> Self::Map { for w in block.stmts.windows(2) { if_chain! { - if let hir::StmtKind::Local(local) = w[0].kind; + if let hir::StmtKind::Local(local, _) = w[0].kind; if let Option::Some(t) = local.init; if let hir::ExprKind::Closure { .. } = t.kind; if let hir::PatKind::Binding(_, _, ident, _) = local.pat.kind; diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 5ae04947b82..b2ec32abb44 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -10,7 +10,6 @@ use rustc_middle::lint::in_external_macro; use rustc_middle::ty::subst::GenericArgKind; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::hygiene::DesugaringKind; use rustc_span::source_map::Span; use rustc_span::sym; @@ -83,7 +82,7 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { if_chain! { if let Some(retexpr) = block.expr; if let Some(stmt) = block.stmts.iter().last(); - if let StmtKind::Local(local) = &stmt.kind; + if let StmtKind::Local(local, _) = &stmt.kind; if local.ty.is_none(); if cx.tcx.hir().attrs(local.hir_id).is_empty(); if let Some(initexpr) = &local.init; @@ -203,9 +202,7 @@ fn check_final_expr<'tcx>( check_block_return(cx, ifblock); } if let Some(else_clause) = else_clause_opt { - if expr.span.desugaring_kind() != Some(DesugaringKind::LetElse) { - check_final_expr(cx, else_clause, None, RetReplacement::Empty); - } + check_final_expr(cx, else_clause, None, RetReplacement::Empty); } }, // a match expr, check all arms diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index 2c8aa17e80d..3d7ef747a86 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -98,7 +98,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { // Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)` if_chain! { - if let StmtKind::Local(local) = stmt.kind; + if let StmtKind::Local(local, _) = stmt.kind; if let PatKind::Binding(BindingAnnotation::Mutable, local_id, _, None) = local.pat.kind; if let Some(init) = local.init; if let Some(len_arg) = Self::is_vec_with_capacity(cx, init); diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 1885f3ca414..a8c96543c7c 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -141,7 +141,7 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) { for w in block.stmts.windows(3) { if_chain! { // let t = foo(); - if let StmtKind::Local(tmp) = w[0].kind; + if let StmtKind::Local(tmp, _) = w[0].kind; if let Some(tmp_init) = tmp.init; if let PatKind::Binding(.., ident, None) = tmp.pat.kind; diff --git a/clippy_lints/src/types/mod.rs b/clippy_lints/src/types/mod.rs index 353a6f6b899..2a7d5f2623e 100644 --- a/clippy_lints/src/types/mod.rs +++ b/clippy_lints/src/types/mod.rs @@ -12,7 +12,7 @@ use rustc_hir as hir; use rustc_hir::intravisit::FnKind; use rustc_hir::{ - Body, FnDecl, FnRetTy, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MutTy, QPath, TraitItem, + Block, Body, FnDecl, FnRetTy, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MutTy, QPath, TraitItem, TraitItemKind, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; @@ -406,7 +406,7 @@ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &TraitItem<'_>) { } } - fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) { + fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>, _: Option<&Block<'_>>) { if let Some(ty) = local.ty { self.check_ty( cx, diff --git a/clippy_lints/src/uninit_vec.rs b/clippy_lints/src/uninit_vec.rs index 9f4c5555f11..eab3b9b7b01 100644 --- a/clippy_lints/src/uninit_vec.rs +++ b/clippy_lints/src/uninit_vec.rs @@ -155,7 +155,7 @@ pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { /// or `self` expression for `Vec::reserve()`. fn extract_init_or_reserve_target<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> Option> { match stmt.kind { - StmtKind::Local(local) => { + StmtKind::Local(local, _) => { if_chain! { if let Some(init_expr) = local.init; if let PatKind::Binding(_, hir_id, _, None) = local.pat.kind; diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index cf509455aad..80e7b8de392 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -12,7 +12,7 @@ use super::LET_UNIT_VALUE; pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) { - if let StmtKind::Local(local) = stmt.kind + if let StmtKind::Local(local, _) = stmt.kind && let Some(init) = local.init && !local.pat.span.from_expansion() && !in_external_macro(cx.sess(), stmt.span) diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 2c8820eb7e1..99ac84fbaab 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -685,7 +685,7 @@ macro_rules! kind { } match stmt.value.kind { - StmtKind::Local(local) => { + StmtKind::Local(local, _) => { bind!(self, local); kind!("Local({local})"); self.option(field!(local.init), "init", |init| { diff --git a/clippy_lints/src/vec_init_then_push.rs b/clippy_lints/src/vec_init_then_push.rs index 35db45e2b0c..c71bacfa29a 100644 --- a/clippy_lints/src/vec_init_then_push.rs +++ b/clippy_lints/src/vec_init_then_push.rs @@ -155,7 +155,7 @@ fn check_block(&mut self, _: &LateContext<'tcx>, _: &'tcx Block<'tcx>) { self.searcher = None; } - fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>, _: Option<&'tcx Block<'tcx>>) { if let Some(init_expr) = local.init && let PatKind::Binding(BindingAnnotation::Mutable, id, name, None) = local.pat.kind && !in_external_macro(cx.sess(), local.span) diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 793e3cc58c2..0b5325adfed 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -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, le), &StmtKind::Local(r, re)) => { // 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 { @@ -117,6 +117,7 @@ pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool { // these only get added if the init and type is equal. both(&l.init, &r.init, |l, r| self.eq_expr(l, r)) && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r)) + && both(&le, &re, |l, r| self.eq_block(l, r)) && self.eq_pat(l.pat, r.pat) }, (&StmtKind::Expr(l), &StmtKind::Expr(r)) | (&StmtKind::Semi(l), &StmtKind::Semi(r)) => self.eq_expr(l, r), @@ -921,11 +922,14 @@ pub fn hash_stmt(&mut self, b: &Stmt<'_>) { std::mem::discriminant(&b.kind).hash(&mut self.s); match &b.kind { - StmtKind::Local(local) => { + StmtKind::Local(local, els) => { self.hash_pat(local.pat); if let Some(init) = local.init { self.hash_expr(init); } + if let Some(els) = els { + self.hash_block(els); + } }, StmtKind::Item(..) => {}, StmtKind::Expr(expr) | StmtKind::Semi(expr) => { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 1b32f0aaeb8..ac6490cfd2c 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1826,7 +1826,7 @@ pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { .. }, .. - }), + }, _), .. }), _ From af3ba22313c351dabeb7492a21074a45c482faae Mon Sep 17 00:00:00 2001 From: Ding Xiang Fei Date: Tue, 5 Jul 2022 23:31:18 +0200 Subject: [PATCH 61/84] move else block into the `Local` struct --- clippy_lints/src/attrs.rs | 2 +- clippy_lints/src/copies.rs | 6 +++--- clippy_lints/src/default.rs | 2 +- clippy_lints/src/default_numeric_fallback.rs | 2 +- clippy_lints/src/entry.rs | 2 +- clippy_lints/src/explicit_write.rs | 2 +- clippy_lints/src/let_if_seq.rs | 2 +- clippy_lints/src/let_underscore.rs | 4 ++-- clippy_lints/src/loops/needless_collect.rs | 4 ++-- clippy_lints/src/loops/never_loop.rs | 2 +- clippy_lints/src/loops/utils.rs | 6 +++--- clippy_lints/src/loops/while_let_loop.rs | 2 +- clippy_lints/src/loops/while_let_on_iterator.rs | 4 ++-- clippy_lints/src/map_unit_fn.rs | 2 +- clippy_lints/src/matches/mod.rs | 11 +++-------- clippy_lints/src/methods/str_splitn.rs | 2 +- clippy_lints/src/misc.rs | 2 +- clippy_lints/src/mixed_read_write_in_expression.rs | 4 ++-- clippy_lints/src/mut_key.rs | 7 +------ clippy_lints/src/needless_late_init.rs | 4 ++-- clippy_lints/src/no_effect.rs | 4 ++-- clippy_lints/src/only_used_in_recursion.rs | 4 ++-- clippy_lints/src/pattern_type_mismatch.rs | 2 +- clippy_lints/src/read_zero_byte_vec.rs | 2 +- clippy_lints/src/redundant_closure_call.rs | 2 +- clippy_lints/src/returns.rs | 2 +- clippy_lints/src/slow_vector_initialization.rs | 2 +- clippy_lints/src/swap.rs | 2 +- clippy_lints/src/types/mod.rs | 4 ++-- clippy_lints/src/uninit_vec.rs | 2 +- clippy_lints/src/unit_types/let_unit_value.rs | 2 +- clippy_lints/src/utils/author.rs | 2 +- clippy_lints/src/vec_init_then_push.rs | 2 +- clippy_utils/src/hir_utils.rs | 8 ++++---- clippy_utils/src/lib.rs | 2 +- 35 files changed, 52 insertions(+), 62 deletions(-) diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index 93ce3b30fb1..4bcbeacf9fe 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -505,7 +505,7 @@ fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_ .as_ref() .map_or(false, |e| is_relevant_expr(cx, typeck_results, e)), |stmt| match &stmt.kind { - StmtKind::Local(_, _) => true, + StmtKind::Local(_) => true, StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr), StmtKind::Item(_) => false, }, diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 0b9fdb891b1..1deff9684a1 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -324,7 +324,7 @@ fn end_span(&self, b: &Block<'_>, sm: &SourceMap) -> Option { /// If the statement is a local, checks if the bound names match the expected list of names. fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool { - if let StmtKind::Local(l, _) = s.kind { + if let StmtKind::Local(l) = s.kind { let mut i = 0usize; let mut res = true; l.pat.each_binding_or_first(&mut |_, _, _, name| { @@ -349,7 +349,7 @@ fn eq_stmts( eq: &mut HirEqInterExpr<'_, '_, '_>, moved_bindings: &mut Vec<(HirId, Symbol)>, ) -> bool { - (if let StmtKind::Local(l, _) = stmt.kind { + (if let StmtKind::Local(l) = stmt.kind { let old_count = moved_bindings.len(); l.pat.each_binding_or_first(&mut |_, id, _, name| { moved_bindings.push((id, name.name)); @@ -435,7 +435,7 @@ fn scan_block_for_eq(cx: &LateContext<'_>, _conds: &[&Expr<'_>], block: &Block<' // Clear out all locals seen at the end so far. None of them can be moved. let stmts = &blocks[0].stmts; for stmt in &stmts[stmts.len() - init..=stmts.len() - offset] { - if let StmtKind::Local(l, _) = stmt.kind { + if let StmtKind::Local(l) = stmt.kind { l.pat.each_binding_or_first(&mut |_, id, _, _| { eq.locals.remove(&id); }); diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 7fe3443858a..d99a1aa2969 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -126,7 +126,7 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) { // checked and the name of the bound variable let (local, variant, binding_name, binding_type, span) = if_chain! { // only take `let ...` statements - if let StmtKind::Local(local, _) = stmt.kind; + if let StmtKind::Local(local) = stmt.kind; if let Some(expr) = local.init; if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id); if !expr.span.from_expansion(); diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 0f374d12a84..fb418a3251f 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -192,7 +192,7 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { match stmt.kind { - StmtKind::Local(local, _) => { + StmtKind::Local(local) => { if local.ty.is_some() { self.ty_bounds.push(TyBound::Any); } else { diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index e0986b710c5..27743a0ebec 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -386,7 +386,7 @@ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { } }, StmtKind::Expr(e) => self.visit_expr(e), - StmtKind::Local(l, _) => { + StmtKind::Local(l) => { self.visit_pat(l.pat); if let Some(e) = l.init { self.allow_insert_closure &= !self.in_tail_pos; diff --git a/clippy_lints/src/explicit_write.rs b/clippy_lints/src/explicit_write.rs index bd1ac3371b0..5bf4313b41a 100644 --- a/clippy_lints/src/explicit_write.rs +++ b/clippy_lints/src/explicit_write.rs @@ -116,7 +116,7 @@ fn look_in_block<'tcx, 'hir>(cx: &LateContext<'tcx>, kind: &'tcx ExprKind<'hir>) if_chain! { if let ExprKind::Block(block, _label @ None) = kind; if let Block { - stmts: [Stmt { kind: StmtKind::Local(local, _), .. }], + stmts: [Stmt { kind: StmtKind::Local(local), .. }], expr: Some(expr_end_of_block), rules: BlockCheckMode::DefaultBlock, .. diff --git a/clippy_lints/src/let_if_seq.rs b/clippy_lints/src/let_if_seq.rs index 5dcb86feb76..56bbbbbc819 100644 --- a/clippy_lints/src/let_if_seq.rs +++ b/clippy_lints/src/let_if_seq.rs @@ -62,7 +62,7 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { while let Some(stmt) = it.next() { if_chain! { if let Some(expr) = it.peek(); - if let hir::StmtKind::Local(local, _) = stmt.kind; + if let hir::StmtKind::Local(local) = stmt.kind; if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind; if let hir::StmtKind::Expr(if_) = expr.kind; if let hir::ExprKind::If(hir::Expr { kind: hir::ExprKind::DropTemps(cond), ..}, then, else_) = if_.kind; diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index a37dfb7b715..176787497eb 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -2,7 +2,7 @@ use clippy_utils::ty::{is_must_use_ty, match_type}; use clippy_utils::{is_must_use_func_call, paths}; use if_chain::if_chain; -use rustc_hir::{Block, Local, PatKind}; +use rustc_hir::{Local, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::subst::GenericArgKind; @@ -109,7 +109,7 @@ ]; impl<'tcx> LateLintPass<'tcx> for LetUnderscore { - fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>, _: Option<&Block<'_>>) { + fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) { if in_external_macro(cx.tcx.sess, local.span) { return; } diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index ba0f01d9ed2..ddaffc75188 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -76,7 +76,7 @@ fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCo if let ExprKind::Block(block, _) = expr.kind { for stmt in block.stmts { if_chain! { - if let StmtKind::Local(local, _) = stmt.kind; + if let StmtKind::Local(local) = stmt.kind; if let PatKind::Binding(_, id, ..) = local.pat.kind; if let Some(init_expr) = local.init; if let ExprKind::MethodCall(method_name, &[ref iter_source], ..) = init_expr.kind; @@ -276,7 +276,7 @@ fn get_expr_and_hir_id_from_stmt<'v>(stmt: &'v Stmt<'v>) -> Option<(&'v Expr<'v> match stmt.kind { StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some((expr, None)), StmtKind::Item(..) => None, - StmtKind::Local(Local { init, pat, .. }, _) => { + StmtKind::Local(Local { init, pat, .. }) => { if let PatKind::Binding(_, hir_id, ..) = pat.kind { init.map(|init_expr| (init_expr, Some(hir_id))) } else { diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index c60d5518060..32de20f6531 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -104,7 +104,7 @@ fn never_loop_expr_seq<'a, T: Iterator>>(es: &mut T, main_lo fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> { match stmt.kind { StmtKind::Semi(e, ..) | StmtKind::Expr(e, ..) => Some(e), - StmtKind::Local(local, _) => local.init, + StmtKind::Local(local) => local.init, StmtKind::Item(..) => None, } } diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs index 661af8fe642..4801a84eb92 100644 --- a/clippy_lints/src/loops/utils.rs +++ b/clippy_lints/src/loops/utils.rs @@ -4,7 +4,7 @@ use rustc_ast::ast::{LitIntType, LitKind}; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, Visitor}; -use rustc_hir::{BinOpKind, Block, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt}; +use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty}; @@ -148,7 +148,7 @@ pub(super) fn get_result(&self) -> Option<(Symbol, Option>, &'tcx Expr< impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; - fn visit_local(&mut self, l: &'tcx Local<'_>, e: Option<&'tcx Block<'_>>) { + fn visit_local(&mut self, l: &'tcx Local<'_>) { // Look for declarations of the variable if_chain! { if l.pat.hir_id == self.var_id; @@ -166,7 +166,7 @@ fn visit_local(&mut self, l: &'tcx Local<'_>, e: Option<&'tcx Block<'_>>) { } } - walk_local(self, l, e); + walk_local(self, l); } fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { diff --git a/clippy_lints/src/loops/while_let_loop.rs b/clippy_lints/src/loops/while_let_loop.rs index 8c352494252..ca617859db4 100644 --- a/clippy_lints/src/loops/while_let_loop.rs +++ b/clippy_lints/src/loops/while_let_loop.rs @@ -11,7 +11,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { let (init, has_trailing_exprs) = match (loop_block.stmts, loop_block.expr) { ([stmt, stmts @ ..], expr) => { - if let StmtKind::Local(&Local { init: Some(e), .. }, None) | StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind { + if let StmtKind::Local(&Local { init: Some(e), els: None, .. }) | StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind { (e, !stmts.is_empty() || expr.is_some()) } else { return; diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 1abdfaac7ec..a5715975066 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -8,7 +8,7 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{def::Res, Block, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp}; +use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::adjustment::Adjust; use rustc_span::{symbol::sym, Symbol}; @@ -283,7 +283,7 @@ struct NestedLoopVisitor<'a, 'b, 'tcx> { used_after: bool, } impl<'a, 'b, 'tcx> Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> { - fn visit_local(&mut self, l: &'tcx Local<'_>, _: Option<&'tcx Block<'_>>) { + fn visit_local(&mut self, l: &'tcx Local<'_>) { if !self.after_loop { l.pat.each_binding_or_first(&mut |_, id, _, _| { if id == self.local_id { diff --git a/clippy_lints/src/map_unit_fn.rs b/clippy_lints/src/map_unit_fn.rs index 3bfe5428133..663246b4c86 100644 --- a/clippy_lints/src/map_unit_fn.rs +++ b/clippy_lints/src/map_unit_fn.rs @@ -144,7 +144,7 @@ fn reduce_unit_expression<'a>(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> // If block only contains statements, // reduce `{ X; }` to `X` or `X;` match inner_stmt.kind { - hir::StmtKind::Local(local, _) => Some(local.span), + hir::StmtKind::Local(local) => Some(local.span), hir::StmtKind::Expr(e) => Some(e.span), hir::StmtKind::Semi(..) => Some(inner_stmt.span), hir::StmtKind::Item(..) => None, diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index cc8674a2006..3077b999f4e 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1,6 +1,6 @@ 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, Block, Expr, ExprKind, Local, MatchSource, Pat}; +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; @@ -1040,14 +1040,9 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { } } - fn check_local( - &mut self, - cx: &LateContext<'tcx>, - local: &'tcx Local<'_>, - els: Option<&'tcx Block<'_>>, - ) { + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { self.infallible_destructuring_match_linted |= - els.is_none() && infallible_destructuring_match::check(cx, local); + local.els.is_none() && infallible_destructuring_match::check(cx, local); } fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs index 80dbd14b2c5..4ac738272d0 100644 --- a/clippy_lints/src/methods/str_splitn.rs +++ b/clippy_lints/src/methods/str_splitn.rs @@ -220,7 +220,7 @@ fn indirect_usage<'tcx>( init: Some(init_expr), hir_id: local_hir_id, .. - }, _) = stmt.kind + }) = stmt.kind { let mut path_to_binding = None; expr_visitor(cx, |expr| { diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index 2ad7ac60b92..be7df08d89f 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -161,7 +161,7 @@ fn check_fn( fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { if_chain! { if !in_external_macro(cx.tcx.sess, stmt.span); - if let StmtKind::Local(local, _) = stmt.kind; + if let StmtKind::Local(local) = stmt.kind; if let PatKind::Binding(an, .., name, None) = local.pat.kind; if let Some(init) = local.init; if an == BindingAnnotation::Ref || an == BindingAnnotation::RefMut; diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index de993c3c0a4..a2419c277e9 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -96,7 +96,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { } fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { match stmt.kind { - StmtKind::Local(local, _) => { + StmtKind::Local(local) => { if let Local { init: Some(e), .. } = local { DivergenceVisitor { cx }.visit_expr(e); } @@ -273,7 +273,7 @@ fn check_stmt<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, stmt: &'tcx Stmt<'_>) - StmtKind::Expr(expr) | StmtKind::Semi(expr) => check_expr(vis, expr), // If the declaration is of a local variable, check its initializer // expression if it has one. Otherwise, keep going. - StmtKind::Local(local, _) => local + StmtKind::Local(local) => local .init .as_ref() .map_or(StopEarly::KeepGoing, |expr| check_expr(vis, expr)), diff --git a/clippy_lints/src/mut_key.rs b/clippy_lints/src/mut_key.rs index 251181165b0..4db103bbc13 100644 --- a/clippy_lints/src/mut_key.rs +++ b/clippy_lints/src/mut_key.rs @@ -101,12 +101,7 @@ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitIte } } - fn check_local( - &mut self, - cx: &LateContext<'_>, - local: &hir::Local<'_>, - _: Option<&hir::Block<'_>>, - ) { + fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) { if let hir::PatKind::Wild = local.pat.kind { return; } diff --git a/clippy_lints/src/needless_late_init.rs b/clippy_lints/src/needless_late_init.rs index fa1c09d8f90..ff2999b1f4a 100644 --- a/clippy_lints/src/needless_late_init.rs +++ b/clippy_lints/src/needless_late_init.rs @@ -92,7 +92,7 @@ fn contains_let(cond: &Expr<'_>) -> bool { } fn stmt_needs_ordered_drop(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool { - let StmtKind::Local(local, _) = stmt.kind else { return false }; + let StmtKind::Local(local) = stmt.kind else { return false }; !local.pat.walk_short(|pat| { if let PatKind::Binding(.., None) = pat.kind { !needs_ordered_drop(cx, cx.typeck_results().pat_ty(pat)) @@ -367,7 +367,7 @@ fn check<'tcx>( } impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit { - fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>, _: Option<&'tcx Block<'tcx>>) { + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { let mut parents = cx.tcx.hir().parent_iter(local.hir_id); if_chain! { if let Local { diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 105e145ac30..819646bb678 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -88,11 +88,11 @@ fn check_no_effect(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool { span_lint_hir(cx, NO_EFFECT, expr.hir_id, stmt.span, "statement with no effect"); return true; } - } else if let StmtKind::Local(local, els) = stmt.kind { + } else if let StmtKind::Local(local) = stmt.kind { if_chain! { if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id); if let Some(init) = local.init; - if els.is_none(); + if local.els.is_none(); if !local.pat.span.from_expansion(); if has_no_effect(cx, init); if let PatKind::Binding(_, _, ident, _) = local.pat.kind; diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index c7f8f2f8d70..677ac998b56 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -261,13 +261,13 @@ fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) { match s.kind { StmtKind::Local(Local { pat, init: Some(init), .. - }, _) => { + }) => { self.visit_pat_expr(pat, init, false); }, StmtKind::Item(_) | StmtKind::Expr(_) | StmtKind::Semi(_) => { walk_stmt(self, s); }, - StmtKind::Local(_, _) => {}, + StmtKind::Local(_) => {}, } self.ret_vars.clear(); } diff --git a/clippy_lints/src/pattern_type_mismatch.rs b/clippy_lints/src/pattern_type_mismatch.rs index 83e18e20711..a4d265111f9 100644 --- a/clippy_lints/src/pattern_type_mismatch.rs +++ b/clippy_lints/src/pattern_type_mismatch.rs @@ -83,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { - if let StmtKind::Local(local, _) = stmt.kind { + if let StmtKind::Local(local) = stmt.kind { if in_external_macro(cx.sess(), local.pat.span) { return; } diff --git a/clippy_lints/src/read_zero_byte_vec.rs b/clippy_lints/src/read_zero_byte_vec.rs index 8316efad1ff..9538a810473 100644 --- a/clippy_lints/src/read_zero_byte_vec.rs +++ b/clippy_lints/src/read_zero_byte_vec.rs @@ -53,7 +53,7 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &hir::Block<'tcx>) { for (idx, stmt) in block.stmts.iter().enumerate() { if !stmt.span.from_expansion() // matches `let v = Vec::new();` - && let StmtKind::Local(local, _) = stmt.kind + && let StmtKind::Local(local) = stmt.kind && let Local { pat, init: Some(init), .. } = local && let PatKind::Binding(_, _, ident, _) = pat.kind && let Some(vec_init_kind) = get_vec_init_kind(cx, init) diff --git a/clippy_lints/src/redundant_closure_call.rs b/clippy_lints/src/redundant_closure_call.rs index 48bf14d511c..65ed798867d 100644 --- a/clippy_lints/src/redundant_closure_call.rs +++ b/clippy_lints/src/redundant_closure_call.rs @@ -133,7 +133,7 @@ fn nested_visit_map(&mut self) -> Self::Map { for w in block.stmts.windows(2) { if_chain! { - if let hir::StmtKind::Local(local, _) = w[0].kind; + if let hir::StmtKind::Local(local) = w[0].kind; if let Option::Some(t) = local.init; if let hir::ExprKind::Closure { .. } = t.kind; if let hir::PatKind::Binding(_, _, ident, _) = local.pat.kind; diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index b2ec32abb44..1d9a2abf706 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -82,7 +82,7 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { if_chain! { if let Some(retexpr) = block.expr; if let Some(stmt) = block.stmts.iter().last(); - if let StmtKind::Local(local, _) = &stmt.kind; + if let StmtKind::Local(local) = &stmt.kind; if local.ty.is_none(); if cx.tcx.hir().attrs(local.hir_id).is_empty(); if let Some(initexpr) = &local.init; diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index 3d7ef747a86..2c8aa17e80d 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -98,7 +98,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { // Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)` if_chain! { - if let StmtKind::Local(local, _) = stmt.kind; + if let StmtKind::Local(local) = stmt.kind; if let PatKind::Binding(BindingAnnotation::Mutable, local_id, _, None) = local.pat.kind; if let Some(init) = local.init; if let Some(len_arg) = Self::is_vec_with_capacity(cx, init); diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index a8c96543c7c..1885f3ca414 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -141,7 +141,7 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) { for w in block.stmts.windows(3) { if_chain! { // let t = foo(); - if let StmtKind::Local(tmp, _) = w[0].kind; + if let StmtKind::Local(tmp) = w[0].kind; if let Some(tmp_init) = tmp.init; if let PatKind::Binding(.., ident, None) = tmp.pat.kind; diff --git a/clippy_lints/src/types/mod.rs b/clippy_lints/src/types/mod.rs index 2a7d5f2623e..353a6f6b899 100644 --- a/clippy_lints/src/types/mod.rs +++ b/clippy_lints/src/types/mod.rs @@ -12,7 +12,7 @@ use rustc_hir as hir; use rustc_hir::intravisit::FnKind; use rustc_hir::{ - Block, Body, FnDecl, FnRetTy, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MutTy, QPath, TraitItem, + Body, FnDecl, FnRetTy, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MutTy, QPath, TraitItem, TraitItemKind, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; @@ -406,7 +406,7 @@ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &TraitItem<'_>) { } } - fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>, _: Option<&Block<'_>>) { + fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) { if let Some(ty) = local.ty { self.check_ty( cx, diff --git a/clippy_lints/src/uninit_vec.rs b/clippy_lints/src/uninit_vec.rs index eab3b9b7b01..9f4c5555f11 100644 --- a/clippy_lints/src/uninit_vec.rs +++ b/clippy_lints/src/uninit_vec.rs @@ -155,7 +155,7 @@ pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { /// or `self` expression for `Vec::reserve()`. fn extract_init_or_reserve_target<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> Option> { match stmt.kind { - StmtKind::Local(local, _) => { + StmtKind::Local(local) => { if_chain! { if let Some(init_expr) = local.init; if let PatKind::Binding(_, hir_id, _, None) = local.pat.kind; diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index 80e7b8de392..cf509455aad 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -12,7 +12,7 @@ use super::LET_UNIT_VALUE; pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) { - if let StmtKind::Local(local, _) = stmt.kind + if let StmtKind::Local(local) = stmt.kind && let Some(init) = local.init && !local.pat.span.from_expansion() && !in_external_macro(cx.sess(), stmt.span) diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 99ac84fbaab..2c8820eb7e1 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -685,7 +685,7 @@ macro_rules! kind { } match stmt.value.kind { - StmtKind::Local(local, _) => { + StmtKind::Local(local) => { bind!(self, local); kind!("Local({local})"); self.option(field!(local.init), "init", |init| { diff --git a/clippy_lints/src/vec_init_then_push.rs b/clippy_lints/src/vec_init_then_push.rs index c71bacfa29a..35db45e2b0c 100644 --- a/clippy_lints/src/vec_init_then_push.rs +++ b/clippy_lints/src/vec_init_then_push.rs @@ -155,7 +155,7 @@ fn check_block(&mut self, _: &LateContext<'tcx>, _: &'tcx Block<'tcx>) { self.searcher = None; } - fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>, _: Option<&'tcx Block<'tcx>>) { + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { if let Some(init_expr) = local.init && let PatKind::Binding(BindingAnnotation::Mutable, id, name, None) = local.pat.kind && !in_external_macro(cx.sess(), local.span) diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 0b5325adfed..942f14ddd3d 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -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, le), &StmtKind::Local(r, re)) => { + (&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 { @@ -117,7 +117,7 @@ pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool { // these only get added if the init and type is equal. both(&l.init, &r.init, |l, r| self.eq_expr(l, r)) && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r)) - && both(&le, &re, |l, r| self.eq_block(l, r)) + && both(&l.els, &r.els, |l, r| self.eq_block(l, r)) && self.eq_pat(l.pat, r.pat) }, (&StmtKind::Expr(l), &StmtKind::Expr(r)) | (&StmtKind::Semi(l), &StmtKind::Semi(r)) => self.eq_expr(l, r), @@ -922,12 +922,12 @@ pub fn hash_stmt(&mut self, b: &Stmt<'_>) { std::mem::discriminant(&b.kind).hash(&mut self.s); match &b.kind { - StmtKind::Local(local, els) => { + StmtKind::Local(local, ) => { self.hash_pat(local.pat); if let Some(init) = local.init { self.hash_expr(init); } - if let Some(els) = els { + if let Some(els) = local.els { self.hash_block(els); } }, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index ac6490cfd2c..1b32f0aaeb8 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1826,7 +1826,7 @@ pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { .. }, .. - }, _), + }), .. }), _ From 134d0dee41846d1c0867fa3745b8de25b52ae7af Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Tue, 12 Jul 2022 11:00:22 +0900 Subject: [PATCH 62/84] declare span variable before call --- .../src/loops/explicit_counter_loop.rs | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/clippy_lints/src/loops/explicit_counter_loop.rs b/clippy_lints/src/loops/explicit_counter_loop.rs index 32a14887d20..8e3ab26a947 100644 --- a/clippy_lints/src/loops/explicit_counter_loop.rs +++ b/clippy_lints/src/loops/explicit_counter_loop.rs @@ -1,5 +1,5 @@ use super::{make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT_COUNTER_LOOP}; -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::{get_enclosing_block, is_integer_const}; use if_chain::if_chain; @@ -35,28 +35,24 @@ pub(super) fn check<'tcx>( if is_integer_const(cx, initializer, 0); then { 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 Some(ty::Uint(UintTy::Usize)) | None => { - span_lint_and_then( + 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), - |diag| { - diag.span_suggestion( - expr.span.with_hi(arg.span.hi()), - "consider using", - format!( - "for ({}, {}) in {}.enumerate()", - name, - snippet_with_applicability(cx, pat.span, "item", &mut applicability), - make_iterator_snippet(cx, arg, &mut applicability), - ), - applicability, - ); - } + "consider using", + format!( + "for ({}, {}) in {}.enumerate()", + name, + snippet_with_applicability(cx, pat.span, "item", &mut applicability), + make_iterator_snippet(cx, arg, &mut applicability), + ), + applicability, ); return; } @@ -68,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({})", From 9d86ce6533f43ea2322738b2211bd4d4bbf22633 Mon Sep 17 00:00:00 2001 From: ouz-a Date: Mon, 13 Jun 2022 16:37:41 +0300 Subject: [PATCH 63/84] add new rval, pull deref early --- 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 498dcbb8900..f3283588c73 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -124,6 +124,7 @@ fn check_rvalue<'tcx>( Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => { check_place(tcx, *place, span, body) }, + Rvalue::CopyForDeref(place) => check_place(tcx, *place, span, body), Rvalue::Repeat(operand, _) | Rvalue::Use(operand) | Rvalue::Cast( From 8878d674b1d030cbf87605fce2ebbd05cc8f3670 Mon Sep 17 00:00:00 2001 From: Allen Hsu Date: Wed, 13 Apr 2022 00:21:08 +1000 Subject: [PATCH 64/84] Lint for repeated traits within trait bounds or where clauses. --- clippy_lints/src/trait_bounds.rs | 129 +++++++++++++++++++- tests/compile-test.rs | 1 + tests/ui/trait_duplication_in_bounds.rs | 111 +++++++++++++++++ tests/ui/trait_duplication_in_bounds.stderr | 114 +++++++++++++++-- 4 files changed, 340 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 2741179074b..0a42a31fb8c 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -1,5 +1,5 @@ -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; @@ -9,8 +9,8 @@ use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{ - GenericBound, Generics, Item, ItemKind, Node, Path, PathSegment, PredicateOrigin, QPath, TraitBoundModifier, - 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}; @@ -36,7 +36,7 @@ #[clippy::version = "1.38.0"] pub TYPE_REPETITION_IN_BOUNDS, nursery, - "Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`" + "types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`" } declare_clippy_lint! { @@ -63,10 +63,26 @@ /// /// 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, nursery, - "Check if the same trait bounds are specified twice during a function declaration" + "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>) { @@ -241,6 +270,26 @@ 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, tbm) = bound { let trait_path = t.trait_ref.path; @@ -257,3 +306,71 @@ fn get_trait_info_from_bound<'a>(bound: &'a GenericBound<'_>) -> Option<(Res, &' 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/tests/compile-test.rs b/tests/compile-test.rs index 41048298349..52cf751c8d0 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -397,6 +397,7 @@ fn compile_test() { "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/ui/trait_duplication_in_bounds.rs b/tests/ui/trait_duplication_in_bounds.rs index a21d4c5d637..a5751c58aab 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 @@ fn bar() // 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 6f8c8e47dfb..7ef04e52708 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 From 7f4e5d8cc4afb4b911d11a0cdb0ab8bcb0404653 Mon Sep 17 00:00:00 2001 From: Korlo <88337245+Rqnsom@users.noreply.github.com> Date: Tue, 12 Jul 2022 11:17:30 +0200 Subject: [PATCH 65/84] update pull request template Improved suggestion for formatting lint names in the template: changelog: [`lint_name`]: your change --- .github/PULL_REQUEST_TEMPLATE.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 780ee9d63df..9e49f60892d 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. From 1c3f62c7501016149b8133a20117704bd947ccda Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 30 Jun 2022 14:18:51 +0400 Subject: [PATCH 66/84] Fix clippy build --- clippy_lints/src/blocks_in_if_conditions.rs | 4 ++-- clippy_lints/src/bytecount.rs | 4 ++-- clippy_lints/src/eta_reduction.rs | 4 ++-- clippy_lints/src/infinite_iter.rs | 4 ++-- clippy_lints/src/loops/needless_range_loop.rs | 4 ++-- clippy_lints/src/loops/while_let_on_iterator.rs | 8 ++++---- clippy_lints/src/manual_async_fn.rs | 4 ++-- clippy_lints/src/manual_ok_or.rs | 4 ++-- clippy_lints/src/manual_retain.rs | 2 +- clippy_lints/src/map_clone.rs | 2 +- clippy_lints/src/map_err_ignore.rs | 6 +++--- clippy_lints/src/map_unit_fn.rs | 2 +- clippy_lints/src/methods/bind_instead_of_map.rs | 2 +- clippy_lints/src/methods/filter_map.rs | 10 +++++----- clippy_lints/src/methods/option_as_ref_deref.rs | 2 +- clippy_lints/src/methods/option_map_or_none.rs | 2 +- clippy_lints/src/methods/search_is_some.rs | 2 +- clippy_lints/src/methods/unnecessary_filter_map.rs | 2 +- clippy_lints/src/methods/unnecessary_fold.rs | 2 +- clippy_lints/src/methods/unnecessary_lazy_eval.rs | 2 +- clippy_lints/src/needless_for_each.rs | 4 ++-- clippy_lints/src/only_used_in_recursion.rs | 4 ++-- clippy_lints/src/redundant_closure_call.rs | 2 +- clippy_lints/src/suspicious_operation_groupings.rs | 2 +- clippy_lints/src/unit_return_expecting_ord.rs | 4 ++-- clippy_lints/src/unnecessary_sort_by.rs | 4 ++-- clippy_lints/src/utils/author.rs | 6 +++--- clippy_utils/src/ast_utils.rs | 13 +++++++++++-- clippy_utils/src/hir_utils.rs | 6 +++--- clippy_utils/src/lib.rs | 12 ++++++------ clippy_utils/src/sugg.rs | 4 ++-- 31 files changed, 71 insertions(+), 62 deletions(-) diff --git a/clippy_lints/src/blocks_in_if_conditions.rs b/clippy_lints/src/blocks_in_if_conditions.rs index 4b3a04f1255..ad206b5fb30 100644 --- a/clippy_lints/src/blocks_in_if_conditions.rs +++ b/clippy_lints/src/blocks_in_if_conditions.rs @@ -6,7 +6,7 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{BlockCheckMode, Expr, ExprKind}; +use rustc_hir::{BlockCheckMode, Closure, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -51,7 +51,7 @@ struct ExVisitor<'a, 'tcx> { impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - if let ExprKind::Closure { body, .. } = expr.kind { + if let ExprKind::Closure(&Closure { body, .. }) = expr.kind { // do not lint if the closure is called using an iterator (see #1141) if_chain! { if let Some(parent) = get_parent_expr(self.cx, expr); diff --git a/clippy_lints/src/bytecount.rs b/clippy_lints/src/bytecount.rs index 4e530256321..326ce34082a 100644 --- a/clippy_lints/src/bytecount.rs +++ b/clippy_lints/src/bytecount.rs @@ -5,7 +5,7 @@ use clippy_utils::{path_to_local_id, paths, peel_blocks, peel_ref_operators, strip_pat_refs}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind}; +use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, UintTy}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -51,7 +51,7 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { if count.ident.name == sym::count; if let ExprKind::MethodCall(filter, [filter_recv, filter_arg], _) = count_recv.kind; if filter.ident.name == sym!(filter); - if let ExprKind::Closure { body, .. } = filter_arg.kind; + if let ExprKind::Closure(&Closure { body, .. }) = filter_arg.kind; let body = cx.tcx.hir().body(body); if let [param] = body.params; if let PatKind::Binding(_, arg_id, _, _) = strip_pat_refs(param.pat).kind; diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 42fac550ec6..80c84014bfd 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -7,7 +7,7 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; -use rustc_hir::{Expr, ExprKind, Param, PatKind, Unsafety}; +use rustc_hir::{Closure, Expr, ExprKind, Param, PatKind, Unsafety}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc_middle::ty::binding::BindingMode; @@ -78,7 +78,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { return; } let body = match expr.kind { - ExprKind::Closure { body, .. } => cx.tcx.hir().body(body), + ExprKind::Closure(&Closure { body, .. }) => cx.tcx.hir().body(body), _ => return, }; if body.value.span.from_expansion() { diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index 78b5ec8ec1e..01c7eef4e04 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::{higher, match_def_path, path_def_id, paths}; -use rustc_hir::{BorrowKind, Expr, ExprKind}; +use rustc_hir::{BorrowKind, Closure, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::{sym, Symbol}; @@ -159,7 +159,7 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { } } if method.ident.name == sym!(flat_map) && args.len() == 2 { - if let ExprKind::Closure { body, .. } = args[1].kind { + if let ExprKind::Closure(&Closure { body, .. }) = args[1].kind { let body = cx.tcx.hir().body(body); return is_infinite(cx, &body.value); } diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index 0b6d9adb553..a7ef562b21f 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -9,7 +9,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, QPath}; +use rustc_hir::{BinOpKind, BorrowKind, Closure, Expr, ExprKind, HirId, Mutability, Pat, PatKind, QPath}; use rustc_lint::LateContext; use rustc_middle::middle::region; use rustc_middle::ty::{self, Ty}; @@ -369,7 +369,7 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { self.visit_expr(expr); } }, - ExprKind::Closure { body, .. } => { + ExprKind::Closure(&Closure { body, .. }) => { let body = self.cx.tcx.hir().body(body); self.visit_expr(&body.value); }, diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index a5715975066..b94bbd2bd41 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -8,7 +8,7 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp}; +use rustc_hir::{Closure, def::Res, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::adjustment::Adjust; use rustc_span::{symbol::sym, Symbol}; @@ -220,7 +220,7 @@ fn visit_expr(&mut self, e: &'tcx Expr<'_>) { if let Some(e) = e { self.visit_expr(e); } - } else if let ExprKind::Closure { body: id, .. } = e.kind { + } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind { if is_res_used(self.cx, self.iter_expr.path, id) { self.uses_iter = true; } @@ -260,7 +260,7 @@ fn visit_expr(&mut self, e: &'tcx Expr<'_>) { if let Some(e) = e { self.visit_expr(e); } - } else if let ExprKind::Closure { body: id, .. } = e.kind { + } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind { self.used_iter = is_res_used(self.cx, self.iter_expr.path, id); } else { walk_expr(self, e); @@ -307,7 +307,7 @@ fn visit_expr(&mut self, e: &'tcx Expr<'_>) { if let Some(e) = e { self.visit_expr(e); } - } else if let ExprKind::Closure { body: id, .. } = e.kind { + } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind { self.used_after = is_res_used(self.cx, self.iter_expr.path, id); } else { walk_expr(self, e); diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index d7d8a592152..93a34f452f6 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -6,7 +6,7 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{ - AsyncGeneratorKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound, HirId, + 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}; @@ -177,7 +177,7 @@ fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) if let Some(block_expr) = block.expr; if let Some(args) = match_function_call(cx, block_expr, &FUTURE_FROM_GENERATOR); if args.len() == 1; - if let Expr{kind: ExprKind::Closure { body, .. }, ..} = args[0]; + if let Expr{kind: ExprKind::Closure(&Closure { body, .. }), ..} = args[0]; let closure_body = cx.tcx.hir().body(body); if closure_body.generator_kind == Some(GeneratorKind::Async(AsyncGeneratorKind::Block)); then { diff --git a/clippy_lints/src/manual_ok_or.rs b/clippy_lints/src/manual_ok_or.rs index 18cfd003767..9abf2507b92 100644 --- a/clippy_lints/src/manual_ok_or.rs +++ b/clippy_lints/src/manual_ok_or.rs @@ -5,7 +5,7 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::LangItem::{ResultErr, ResultOk}; -use rustc_hir::{Expr, ExprKind, PatKind}; +use rustc_hir::{Closure, Expr, ExprKind, PatKind}; use rustc_lint::LintContext; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; @@ -88,7 +88,7 @@ fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool { } } if_chain! { - if let ExprKind::Closure { body, .. } = map_expr.kind; + if let ExprKind::Closure(&Closure { body, .. }) = map_expr.kind; let body = cx.tcx.hir().body(body); if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind; if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind; diff --git a/clippy_lints/src/manual_retain.rs b/clippy_lints/src/manual_retain.rs index c35e1e021ef..42d2577cc31 100644 --- a/clippy_lints/src/manual_retain.rs +++ b/clippy_lints/src/manual_retain.rs @@ -148,7 +148,7 @@ fn check_to_owned( fn suggest(cx: &LateContext<'_>, parent_expr: &hir::Expr<'_>, left_expr: &hir::Expr<'_>, filter_expr: &hir::Expr<'_>) { if let hir::ExprKind::MethodCall(_, [_, closure], _) = filter_expr.kind - && let hir::ExprKind::Closure{ body, ..} = closure.kind + && let hir::ExprKind::Closure(&hir::Closure { body, ..}) = closure.kind && let filter_body = cx.tcx.hir().body(body) && let [filter_params] = filter_body.params && let Some(sugg) = match filter_params.pat.kind { diff --git a/clippy_lints/src/map_clone.rs b/clippy_lints/src/map_clone.rs index 3533de54a1e..95c312f1fe2 100644 --- a/clippy_lints/src/map_clone.rs +++ b/clippy_lints/src/map_clone.rs @@ -67,7 +67,7 @@ fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) { if method.ident.name == sym::map; let ty = cx.typeck_results().expr_ty(&args[0]); if is_type_diagnostic_item(cx, ty, sym::Option) || is_trait_method(cx, e, sym::Iterator); - if let hir::ExprKind::Closure { body, .. } = args[1].kind; + if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = args[1].kind; then { let closure_body = cx.tcx.hir().body(body); let closure_expr = peel_blocks(&closure_body.value); diff --git a/clippy_lints/src/map_err_ignore.rs b/clippy_lints/src/map_err_ignore.rs index 0c221441048..21d0e19eb0a 100644 --- a/clippy_lints/src/map_err_ignore.rs +++ b/clippy_lints/src/map_err_ignore.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; -use rustc_hir::{CaptureBy, Expr, ExprKind, PatKind}; +use rustc_hir::{CaptureBy, Closure, Expr, ExprKind, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -119,12 +119,12 @@ fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) { if method.ident.as_str() == "map_err" && args.len() == 2 { // make sure the first argument is a closure, and grab the CaptureRef, BodyId, and fn_decl_span // fields - if let ExprKind::Closure { + if let ExprKind::Closure(&Closure { capture_clause, body, fn_decl_span, .. - } = args[1].kind + }) = args[1].kind { // check if this is by Reference (meaning there's no move statement) if capture_clause == CaptureBy::Ref { diff --git a/clippy_lints/src/map_unit_fn.rs b/clippy_lints/src/map_unit_fn.rs index 663246b4c86..af9d948af00 100644 --- a/clippy_lints/src/map_unit_fn.rs +++ b/clippy_lints/src/map_unit_fn.rs @@ -169,7 +169,7 @@ fn unit_closure<'tcx>( expr: &hir::Expr<'_>, ) -> Option<(&'tcx hir::Param<'tcx>, &'tcx hir::Expr<'tcx>)> { if_chain! { - if let hir::ExprKind::Closure { fn_decl, body, .. } = expr.kind; + if let hir::ExprKind::Closure(&hir::Closure { fn_decl, body, .. }) = expr.kind; let body = cx.tcx.hir().body(body); let body_expr = &body.value; if fn_decl.inputs.len() == 1; diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs index d31b736982b..2f117e4dcc3 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -150,7 +150,7 @@ fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: } match arg.kind { - hir::ExprKind::Closure { body, fn_decl_span, .. } => { + hir::ExprKind::Closure(&hir::Closure { body, fn_decl_span, .. }) => { let closure_body = cx.tcx.hir().body(body); let closure_expr = peel_blocks(&closure_body.value); diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 58c3e52e138..7dbfd95c50d 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -6,7 +6,7 @@ use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::def::Res; -use rustc_hir::{Expr, ExprKind, PatKind, PathSegment, QPath, UnOp}; +use rustc_hir::{Closure, Expr, ExprKind, PatKind, PathSegment, QPath, UnOp}; use rustc_lint::LateContext; use rustc_span::source_map::Span; use rustc_span::symbol::{sym, Symbol}; @@ -22,8 +22,8 @@ fn is_method<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, method_name: Sy hir::ExprKind::Path(QPath::Resolved(_, segments)) => { segments.segments.last().unwrap().ident.name == method_name }, - hir::ExprKind::Closure { body, .. } => { - let body = cx.tcx.hir().body(*body); + hir::ExprKind::Closure(&hir::Closure { body, .. }) => { + let body = cx.tcx.hir().body(body); let closure_expr = peel_blocks(&body.value); let arg_id = body.params[0].pat.hir_id; match closure_expr.kind { @@ -106,7 +106,7 @@ pub(super) fn check<'tcx>( if is_trait_method(cx, map_recv, sym::Iterator); // filter(|x| ...is_some())... - if let ExprKind::Closure { body: filter_body_id, .. } = filter_arg.kind; + if let ExprKind::Closure(&Closure { body: filter_body_id, .. }) = filter_arg.kind; let filter_body = cx.tcx.hir().body(filter_body_id); if let [filter_param] = filter_body.params; // optional ref pattern: `filter(|&x| ..)` @@ -129,7 +129,7 @@ pub(super) fn check<'tcx>( if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" }; // ...map(|x| ...unwrap()) - if let ExprKind::Closure { body: map_body_id, .. } = map_arg.kind; + if let ExprKind::Closure(&Closure { body: map_body_id, .. }) = map_arg.kind; let map_body = cx.tcx.hir().body(map_body_id); if let [map_param] = map_body.params; if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind; diff --git a/clippy_lints/src/methods/option_as_ref_deref.rs b/clippy_lints/src/methods/option_as_ref_deref.rs index 912499bf96b..20cad0f181e 100644 --- a/clippy_lints/src/methods/option_as_ref_deref.rs +++ b/clippy_lints/src/methods/option_as_ref_deref.rs @@ -51,7 +51,7 @@ pub(super) fn check<'tcx>( .map_or(false, |fun_def_id| { deref_aliases.iter().any(|path| match_def_path(cx, fun_def_id, path)) }), - hir::ExprKind::Closure { body, .. } => { + hir::ExprKind::Closure(&hir::Closure { body, .. }) => { let closure_body = cx.tcx.hir().body(body); let closure_expr = peel_blocks(&closure_body.value); diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints/src/methods/option_map_or_none.rs index 2d71bd6f240..5a39b82b027 100644 --- a/clippy_lints/src/methods/option_map_or_none.rs +++ b/clippy_lints/src/methods/option_map_or_none.rs @@ -71,7 +71,7 @@ pub(super) fn check<'tcx>( if is_option { let self_snippet = snippet(cx, recv.span, ".."); if_chain! { - if let hir::ExprKind::Closure { body, fn_decl_span, .. } = map_arg.kind; + if let hir::ExprKind::Closure(&hir::Closure { body, fn_decl_span, .. }) = map_arg.kind; let arg_snippet = snippet(cx, fn_decl_span, ".."); let body = cx.tcx.hir().body(body); if let Some((func, [arg_char])) = reduce_unit_expression(&body.value); diff --git a/clippy_lints/src/methods/search_is_some.rs b/clippy_lints/src/methods/search_is_some.rs index b11f4531a91..7572ba3fe9a 100644 --- a/clippy_lints/src/methods/search_is_some.rs +++ b/clippy_lints/src/methods/search_is_some.rs @@ -41,7 +41,7 @@ pub(super) fn check<'tcx>( let mut applicability = Applicability::MachineApplicable; let any_search_snippet = if_chain! { if search_method == "find"; - if let hir::ExprKind::Closure { body, .. } = search_arg.kind; + if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind; let closure_body = cx.tcx.hir().body(body); if let Some(closure_arg) = closure_body.params.get(0); then { diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index a405467f5e8..bafa6fc584d 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -18,7 +18,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr< return; } - if let hir::ExprKind::Closure { body, .. } = arg.kind { + if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind { let body = cx.tcx.hir().body(body); let arg_id = body.params[0].pat.hir_id; let mutates_arg = diff --git a/clippy_lints/src/methods/unnecessary_fold.rs b/clippy_lints/src/methods/unnecessary_fold.rs index 913c4dbedc3..c3531d4d051 100644 --- a/clippy_lints/src/methods/unnecessary_fold.rs +++ b/clippy_lints/src/methods/unnecessary_fold.rs @@ -29,7 +29,7 @@ fn check_fold_with_op( ) { if_chain! { // Extract the body of the closure passed to fold - if let hir::ExprKind::Closure { body, .. } = acc.kind; + if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = acc.kind; let closure_body = cx.tcx.hir().body(body); let closure_expr = peel_blocks(&closure_body.value); diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs index 865f6d0318e..21767d74c87 100644 --- a/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -22,7 +22,7 @@ pub(super) fn check<'tcx>( let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result); if is_option || is_result { - if let hir::ExprKind::Closure { body, .. } = arg.kind { + if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind { let body = cx.tcx.hir().body(body); let body_expr = &body.value; diff --git a/clippy_lints/src/needless_for_each.rs b/clippy_lints/src/needless_for_each.rs index 48ac695f2ac..10e188ecb79 100644 --- a/clippy_lints/src/needless_for_each.rs +++ b/clippy_lints/src/needless_for_each.rs @@ -1,7 +1,7 @@ use rustc_errors::Applicability; use rustc_hir::{ intravisit::{walk_expr, Visitor}, - Expr, ExprKind, Stmt, StmtKind, + Closure, Expr, ExprKind, Stmt, StmtKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -72,7 +72,7 @@ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { if has_iter_method(cx, cx.typeck_results().expr_ty(iter_recv)).is_some(); // Skip the lint if the body is not block because this is simpler than `for` loop. // e.g. `v.iter().for_each(f)` is simpler and clearer than using `for` loop. - if let ExprKind::Closure { body, .. } = for_each_arg.kind; + if let ExprKind::Closure(&Closure { body, .. }) = for_each_arg.kind; let body = cx.tcx.hir().body(body); if let ExprKind::Block(..) = body.value.kind; then { diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index 677ac998b56..d461668077e 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -11,7 +11,7 @@ use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; use rustc_hir::intravisit::{walk_expr, walk_stmt, FnKind, Visitor}; use rustc_hir::{ - Arm, Block, Body, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, PathSegment, + Arm, Closure, Block, Body, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TyKind, UnOp, }; use rustc_lint::{LateContext, LateLintPass}; @@ -298,7 +298,7 @@ fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { }, ExprKind::Match(expr, arms, _) => self.visit_match(expr, arms), // since analysing the closure is not easy, just set all variables in it to side-effect - ExprKind::Closure { body, .. } => { + ExprKind::Closure(&Closure { body, .. }) => { let body = self.tcx.hir().body(body); self.visit_body(body); let vars = std::mem::take(&mut self.ret_vars); diff --git a/clippy_lints/src/redundant_closure_call.rs b/clippy_lints/src/redundant_closure_call.rs index 65ed798867d..f5a93cebab8 100644 --- a/clippy_lints/src/redundant_closure_call.rs +++ b/clippy_lints/src/redundant_closure_call.rs @@ -69,7 +69,7 @@ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { if_chain! { if let ast::ExprKind::Call(ref paren, _) = expr.kind; if let ast::ExprKind::Paren(ref closure) = paren.kind; - if let ast::ExprKind::Closure(_, _, _, ref decl, ref block, _) = closure.kind; + if let ast::ExprKind::Closure(_, _, _, _, ref decl, ref block, _) = closure.kind; then { let mut visitor = ReturnVisitor::new(); visitor.visit_expr(block); diff --git a/clippy_lints/src/suspicious_operation_groupings.rs b/clippy_lints/src/suspicious_operation_groupings.rs index c4c1aa11004..fe885990595 100644 --- a/clippy_lints/src/suspicious_operation_groupings.rs +++ b/clippy_lints/src/suspicious_operation_groupings.rs @@ -582,7 +582,7 @@ fn ident_difference_expr_with_base_location( | (Await(_), Await(_)) | (Async(_, _, _), Async(_, _, _)) | (Block(_, _), Block(_, _)) - | (Closure(_, _, _, _, _, _), Closure(_, _, _, _, _, _)) + | (Closure(_, _, _, _, _, _, _), Closure(_, _, _, _, _, _, _)) | (Match(_, _), Match(_, _)) | (Loop(_, _), Loop(_, _)) | (ForLoop(_, _, _, _), ForLoop(_, _, _, _)) diff --git a/clippy_lints/src/unit_return_expecting_ord.rs b/clippy_lints/src/unit_return_expecting_ord.rs index f58da7ce9b4..b0fce91abeb 100644 --- a/clippy_lints/src/unit_return_expecting_ord.rs +++ b/clippy_lints/src/unit_return_expecting_ord.rs @@ -2,7 +2,7 @@ use clippy_utils::{get_trait_def_id, paths}; use if_chain::if_chain; use rustc_hir::def_id::DefId; -use rustc_hir::{Expr, ExprKind, StmtKind}; +use rustc_hir::{Closure, Expr, ExprKind, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_middle::ty::{GenericPredicates, PredicateKind, ProjectionPredicate, TraitPredicate}; @@ -116,7 +116,7 @@ fn get_args_to_check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Ve fn check_arg<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'tcx>) -> Option<(Span, Option)> { if_chain! { - if let ExprKind::Closure { body, fn_decl_span, .. } = arg.kind; + if let ExprKind::Closure(&Closure { body, fn_decl_span, .. }) = arg.kind; if let ty::Closure(_def_id, substs) = &cx.typeck_results().node_type(arg.hir_id).kind(); let ret_ty = substs.as_closure().sig().output(); let ty = cx.tcx.erase_late_bound_regions(ret_ty); diff --git a/clippy_lints/src/unnecessary_sort_by.rs b/clippy_lints/src/unnecessary_sort_by.rs index 7d4373b2a57..ea5aadbbca1 100644 --- a/clippy_lints/src/unnecessary_sort_by.rs +++ b/clippy_lints/src/unnecessary_sort_by.rs @@ -3,7 +3,7 @@ use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath}; +use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, subst::GenericArgKind}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -155,7 +155,7 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { if let ExprKind::MethodCall(name_ident, args, _) = &expr.kind; if let name = name_ident.ident.name.to_ident_string(); if name == "sort_by" || name == "sort_unstable_by"; - if let [vec, Expr { kind: ExprKind::Closure{ body: closure_body_id, .. }, .. }] = args; + if let [vec, Expr { kind: ExprKind::Closure(Closure { body: closure_body_id, .. }), .. }] = args; if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(vec), sym::Vec); if let closure_body = cx.tcx.hir().body(*closure_body_id); if let &[ diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 2c8820eb7e1..bbb04c9945a 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -6,7 +6,7 @@ use rustc_ast::LitIntType; use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; -use rustc_hir::{ArrayLen, ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind}; +use rustc_hir::{ArrayLen, Closure, ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::{Ident, Symbol}; @@ -466,13 +466,13 @@ macro_rules! kind { self.expr(scrutinee); self.slice(arms, |arm| self.arm(arm)); }, - ExprKind::Closure { + ExprKind::Closure(&Closure { capture_clause, fn_decl, body: body_id, movability, .. - } => { + }) => { let movability = OptionPat::new(movability.map(|m| format!("Movability::{m:?}"))); let ret_ty = match fn_decl.output { diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 177e754ee09..431b09d53c3 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -168,8 +168,8 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool { (AssignOp(lo, lp, lv), AssignOp(ro, rp, rv)) => lo.node == ro.node && eq_expr(lp, rp) && eq_expr(lv, rv), (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(lc, la, lm, lf, lb, _), Closure(rc, ra, rm, rf, rb, _)) => { - lc == rc && la.is_async() == ra.is_async() && lm == rm && eq_fn_decl(lf, rf) && eq_expr(lb, rb) + (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) }, (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), @@ -561,6 +561,15 @@ 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)), + _ => false, + } +} + pub fn eq_fn_ret_ty(l: &FnRetTy, r: &FnRetTy) -> bool { match (l, r) { (FnRetTy::Default(_), FnRetTy::Default(_)) => true, diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 793e3cc58c2..4e5c3850f86 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -6,7 +6,7 @@ use rustc_hir::def::Res; use rustc_hir::HirIdMap; use rustc_hir::{ - ArrayLen, BinOpKind, Block, BodyId, Expr, ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, Guard, HirId, + 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, }; @@ -662,9 +662,9 @@ pub fn hash_expr(&mut self, e: &Expr<'_>) { self.hash_expr(e); self.hash_ty(ty); }, - ExprKind::Closure { + ExprKind::Closure(&Closure { capture_clause, body, .. - } => { + }) => { std::mem::discriminant(&capture_clause).hash(&mut self.s); // closures inherit TypeckResults self.hash_expr(&self.cx.tcx.hir().body(body).value); diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 1b32f0aaeb8..242d4315378 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -79,10 +79,10 @@ use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk}; use rustc_hir::{ - def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Constness, Destination, Expr, ExprKind, FnDecl, - HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Mutability, Node, - Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, - UnOp, + def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Constness, Destination, Expr, + ExprKind, FnDecl, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, + Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, + TraitRef, TyKind, UnOp, }; use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_middle::hir::place::PlaceBase; @@ -1699,7 +1699,7 @@ pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'t _, &[ Expr { - kind: ExprKind::Closure { body, .. }, + kind: ExprKind::Closure(&Closure { body, .. }), .. }, ], @@ -1786,7 +1786,7 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { } match expr.kind { - ExprKind::Closure { body, .. } => is_body_identity_function(cx, cx.tcx.hir().body(body)), + ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir().body(body)), _ => path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, &paths::CONVERT_IDENTITY)), } } diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index aa119539b1b..4326a103d44 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -8,7 +8,7 @@ use rustc_ast_pretty::pprust::token_kind_to_string; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::{ExprKind, HirId, MutTy, TyKind}; +use rustc_hir::{Closure, ExprKind, HirId, MutTy, TyKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{EarlyContext, LateContext, LintContext}; use rustc_middle::hir::place::ProjectionKind; @@ -790,7 +790,7 @@ pub struct DerefClosure { /// /// note: this only works on single line immutable closures with exactly one input parameter. pub fn deref_closure_args<'tcx>(cx: &LateContext<'_>, closure: &'tcx hir::Expr<'_>) -> Option { - if let hir::ExprKind::Closure { fn_decl, body, .. } = closure.kind { + if let hir::ExprKind::Closure(&Closure { fn_decl, body, .. }) = closure.kind { let closure_body = cx.tcx.hir().body(body); // is closure arg a type annotated double reference (i.e.: `|x: &&i32| ...`) // a type annotation is present if param `kind` is different from `TyKind::Infer` From c26188e327c44609c54733a9592ebf872b7b4f7e Mon Sep 17 00:00:00 2001 From: Victor-N-Suadicani Date: Tue, 12 Jul 2022 23:11:19 +0200 Subject: [PATCH 67/84] Moves format_push_string to pedantic. --- clippy_lints/src/format_push_string.rs | 2 +- clippy_lints/src/lib.register_all.rs | 1 - clippy_lints/src/lib.register_pedantic.rs | 1 + clippy_lints/src/lib.register_perf.rs | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index de19c5ec20e..2d01c71d438 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -29,7 +29,7 @@ /// ``` #[clippy::version = "1.62.0"] pub FORMAT_PUSH_STRING, - perf, + pedantic, "`format!(..)` appended to existing `String`" } declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]); diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index da26a3f0130..9afc714b11c 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -71,7 +71,6 @@ 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), diff --git a/clippy_lints/src/lib.register_pedantic.rs b/clippy_lints/src/lib.register_pedantic.rs index 4d4b89687d0..0b7fff56a5d 100644 --- a/clippy_lints/src/lib.register_pedantic.rs +++ b/clippy_lints/src/lib.register_pedantic.rs @@ -31,6 +31,7 @@ LintId::of(eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS), LintId::of(excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS), LintId::of(excessive_bools::STRUCT_EXCESSIVE_BOOLS), + LintId::of(format_push_string::FORMAT_PUSH_STRING), LintId::of(functions::MUST_USE_CANDIDATE), LintId::of(functions::TOO_MANY_LINES), LintId::of(if_not_else::IF_NOT_ELSE), diff --git a/clippy_lints/src/lib.register_perf.rs b/clippy_lints/src/lib.register_perf.rs index 6bf519c24e8..e1b90acb93c 100644 --- a/clippy_lints/src/lib.register_perf.rs +++ b/clippy_lints/src/lib.register_perf.rs @@ -7,7 +7,6 @@ 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), From 852c38c543d4b2cdea4af9fe8d5ddf229ebd1410 Mon Sep 17 00:00:00 2001 From: Korlo <88337245+Rqnsom@users.noreply.github.com> Date: Mon, 11 Jul 2022 16:53:04 +0200 Subject: [PATCH 68/84] fix for manual_flatten help texts order Whenever suggestion for this lint does not fit in one line, lint will generate two help messages. The second help message will always contain the suggestion. The first help message refers to suggestion message, and it should adapt depending on the location of the suggestion: - inline suggestion within the error/warning message - suggestion separated into second help text --- clippy_lints/src/loops/manual_flatten.rs | 16 ++++++++++--- tests/lint_message_convention.rs | 1 + tests/ui/manual_flatten.rs | 16 +++++++++++++ tests/ui/manual_flatten.stderr | 30 +++++++++++++++++++++++- 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs index d276c901059..1d6ddf4b99f 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/tests/lint_message_convention.rs b/tests/lint_message_convention.rs index dd1d4412036..9519c2e9379 100644 --- a/tests/lint_message_convention.rs +++ b/tests/lint_message_convention.rs @@ -42,6 +42,7 @@ fn new(path: PathBuf) -> Self { r".*remove .*the return type...", r"note: Clippy version: .*", r"the compiler unexpectedly panicked. this is a bug.", + r"remove the `if let` statement in the for loop and then...", ]) .unwrap(); diff --git a/tests/ui/manual_flatten.rs b/tests/ui/manual_flatten.rs index 6c5232ec5f5..d922593bc6f 100644 --- a/tests/ui/manual_flatten.rs +++ b/tests/ui/manual_flatten.rs @@ -106,4 +106,20 @@ struct Test { 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 392e1a39393..da053c05668 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 From 565d3fd67d86fdc610e56c0f12133af1838b7cda Mon Sep 17 00:00:00 2001 From: Victor Nordam Suadicani Date: Wed, 13 Jul 2022 12:59:55 +0200 Subject: [PATCH 69/84] Move to restriction --- clippy_lints/src/format_push_string.rs | 2 +- clippy_lints/src/lib.register_pedantic.rs | 1 - clippy_lints/src/lib.register_restriction.rs | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index 2d01c71d438..17aa482d716 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -29,7 +29,7 @@ /// ``` #[clippy::version = "1.62.0"] pub FORMAT_PUSH_STRING, - pedantic, + restriction, "`format!(..)` appended to existing `String`" } declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]); diff --git a/clippy_lints/src/lib.register_pedantic.rs b/clippy_lints/src/lib.register_pedantic.rs index 0b7fff56a5d..4d4b89687d0 100644 --- a/clippy_lints/src/lib.register_pedantic.rs +++ b/clippy_lints/src/lib.register_pedantic.rs @@ -31,7 +31,6 @@ LintId::of(eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS), LintId::of(excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS), LintId::of(excessive_bools::STRUCT_EXCESSIVE_BOOLS), - LintId::of(format_push_string::FORMAT_PUSH_STRING), LintId::of(functions::MUST_USE_CANDIDATE), LintId::of(functions::TOO_MANY_LINES), LintId::of(if_not_else::IF_NOT_ELSE), diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index 970e9db4772..88d43c8989f 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -21,6 +21,7 @@ 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), From dcd248465ee871bfb5798b8491dfb179fbe4a307 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Sat, 4 Jun 2022 14:17:00 +0200 Subject: [PATCH 70/84] Clippy fallout. --- clippy_lints/src/inherent_to_string.rs | 4 ++-- clippy_lints/src/lifetimes.rs | 10 +++++++--- clippy_lints/src/ptr.rs | 3 ++- clippy_lints/src/types/borrowed_box.rs | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/inherent_to_string.rs b/clippy_lints/src/inherent_to_string.rs index 39f68a8a1b4..94db1773fda 100644 --- a/clippy_lints/src/inherent_to_string.rs +++ b/clippy_lints/src/inherent_to_string.rs @@ -2,7 +2,7 @@ use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::{get_trait_def_id, paths, return_ty, trait_ref_of_method}; use if_chain::if_chain; -use rustc_hir::{ImplItem, ImplItemKind}; +use rustc_hir::{GenericParamKind, ImplItem, ImplItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; @@ -102,7 +102,7 @@ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem< let decl = &signature.decl; if decl.implicit_self.has_implicit_self(); if decl.inputs.len() == 1; - if impl_item.generics.params.is_empty(); + if impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. })); // Check if return type is String if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::String); diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 5c0bd57ac50..083c437a293 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -9,8 +9,8 @@ use rustc_hir::FnRetTy::Return; use rustc_hir::{ BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, Impl, ImplItem, - ImplItemKind, Item, ItemKind, LangItem, Lifetime, LifetimeName, ParamName, PolyTraitRef, PredicateOrigin, - TraitBoundModifier, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate, + ImplItemKind, Item, ItemKind, LangItem, Lifetime, LifetimeName, LifetimeParamKind, 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; @@ -338,7 +338,10 @@ fn could_use_elision<'tcx>( fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet { let mut allowed_lts = FxHashSet::default(); for par in named_generics.iter() { - if let GenericParamKind::Lifetime { .. } = par.kind { + if let GenericParamKind::Lifetime { + kind: LifetimeParamKind::Explicit, + } = par.kind + { allowed_lts.insert(RefLt::Named(par.name.ident().name)); } } @@ -379,6 +382,7 @@ fn record(&mut self, lifetime: &Option) { self.lts.push(RefLt::Static); } else if let LifetimeName::Param(_, ParamName::Fresh) = lt.name { // Fresh lifetimes generated should be ignored. + self.lts.push(RefLt::Unnamed); } else if lt.is_elided() { self.lts.push(RefLt::Unnamed); } else { diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 25b73918c0a..8571607054a 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -495,12 +495,13 @@ fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Optio if let FnRetTy::Return(ty) = sig.decl.output && let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty) { + let out_region = cx.tcx.named_region(out.hir_id); let args: Option> = sig .decl .inputs .iter() .filter_map(get_rptr_lm) - .filter(|&(lt, _, _)| lt.name == out.name) + .filter(|&(lt, _, _)| cx.tcx.named_region(lt.hir_id) == out_region) .map(|(_, mutability, span)| (mutability == Mutability::Not).then(|| span)) .collect(); if let Some(args) = args diff --git a/clippy_lints/src/types/borrowed_box.rs b/clippy_lints/src/types/borrowed_box.rs index f35f44eda56..94945b2e1a9 100644 --- a/clippy_lints/src/types/borrowed_box.rs +++ b/clippy_lints/src/types/borrowed_box.rs @@ -31,7 +31,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, lt: &Lifetime, m return false; } - let ltopt = if lt.is_elided() { + let ltopt = if lt.name.is_anonymous() { String::new() } else { format!("{} ", lt.name.ident().as_str()) From 018684dd9a224996ed2e2c8df238897372d43fd0 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Wed, 13 Jul 2022 14:10:39 +0000 Subject: [PATCH 71/84] Use LazyLock for lint_message_convention regexes --- tests/lint_message_convention.rs | 60 +++++++++++++++++--------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/tests/lint_message_convention.rs b/tests/lint_message_convention.rs index 9519c2e9379..c3aae1a9aa2 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,43 +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.", - r"remove the `if let` statement in the for loop and then...", - ]) - .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::>(); From 0930ac91b9109e9ebc31ebe70620a2ec0fe08b57 Mon Sep 17 00:00:00 2001 From: bors Date: Wed, 13 Jul 2022 14:48:32 +0000 Subject: [PATCH 72/84] Fix typos changelog: none --- CHANGELOG.md | 4 ++-- book/src/usage.md | 2 +- clippy_lints/src/borrow_deref_ref.rs | 2 +- clippy_lints/src/large_enum_variant.rs | 2 +- clippy_lints/src/loops/manual_find.rs | 2 +- clippy_lints/src/matches/match_same_arms.rs | 2 +- clippy_lints/src/mismatching_type_param_order.rs | 2 +- clippy_lints/src/utils/internal_lints/metadata_collector.rs | 2 +- clippy_utils/src/visitors.rs | 2 +- tests/ui/if_let_mutex.rs | 2 +- tests/ui/inconsistent_struct_constructor.fixed | 2 +- tests/ui/inconsistent_struct_constructor.rs | 2 +- tests/ui/map_flatten_fixable.fixed | 2 +- tests/ui/map_flatten_fixable.rs | 2 +- tests/ui/map_flatten_fixable.stderr | 4 ++-- tests/ui/same_name_method.rs | 2 +- tests/ui/transmutes_expressible_as_ptr_casts.fixed | 2 +- tests/ui/transmutes_expressible_as_ptr_casts.rs | 2 +- tests/ui/wildcard_imports.fixed | 2 +- tests/ui/wildcard_imports.rs | 2 +- 20 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b792736a6a..201929ec11c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -364,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) @@ -2525,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) diff --git a/book/src/usage.md b/book/src/usage.md index 337680aa313..f03967d88bc 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_lints/src/borrow_deref_ref.rs b/clippy_lints/src/borrow_deref_ref.rs index 1582ec9ee5c..937765b6614 100644 --- a/clippy_lints/src/borrow_deref_ref.rs +++ b/clippy_lints/src/borrow_deref_ref.rs @@ -22,7 +22,7 @@ /// ``` /// 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/large_enum_variant.rs b/clippy_lints/src/large_enum_variant.rs index 9be057bcf90..c58df126d62 100644 --- a/clippy_lints/src/large_enum_variant.rs +++ b/clippy_lints/src/large_enum_variant.rs @@ -30,7 +30,7 @@ /// 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/loops/manual_find.rs b/clippy_lints/src/loops/manual_find.rs index 33736d6d4e6..215c83a7edf 100644 --- a/clippy_lints/src/loops/manual_find.rs +++ b/clippy_lints/src/loops/manual_find.rs @@ -139,7 +139,7 @@ fn extract<'tcx>(block: &Block<'tcx>) -> Option<(&'tcx Stmt<'tcx>, &'tcx Expr<'t 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/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index 61d28b15066..582782f245f 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -365,7 +365,7 @@ fn has_overlapping_values(&self, other: &Self) -> bool { (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/mismatching_type_param_order.rs b/clippy_lints/src/mismatching_type_param_order.rs index d466d54a6ba..254d9a70010 100644 --- a/clippy_lints/src/mismatching_type_param_order.rs +++ b/clippy_lints/src/mismatching_type_param_order.rs @@ -9,7 +9,7 @@ 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/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 3010fc0223c..aaeca47677d 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -1018,7 +1018,7 @@ fn visit_expr(&mut self, expr: &'hir hir::Expr<'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_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index 0b9f75238a6..bae8ad9f565 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -30,7 +30,7 @@ fn descend(&self) -> bool { } } -/// Allows for controlled descent whe using visitor functions. Use `()` instead when always +/// Allows for controlled descent when using visitor functions. Use `()` instead when always /// descending into child nodes. #[derive(Clone, Copy)] pub enum Descend { diff --git a/tests/ui/if_let_mutex.rs b/tests/ui/if_let_mutex.rs index 58feae422a3..6cbfafbb38b 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 eb66d1afddc..74ba2f1c5e7 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 5caadc7c620..ba96e1e330f 100644 --- a/tests/ui/inconsistent_struct_constructor.rs +++ b/tests/ui/inconsistent_struct_constructor.rs @@ -36,7 +36,7 @@ fn test() { // 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/map_flatten_fixable.fixed b/tests/ui/map_flatten_fixable.fixed index 9097a6b9967..312819a0a2c 100644 --- a/tests/ui/map_flatten_fixable.fixed +++ b/tests/ui/map_flatten_fixable.fixed @@ -58,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 3916263404a..3fbf4f9a1b0 100644 --- a/tests/ui/map_flatten_fixable.rs +++ b/tests/ui/map_flatten_fixable.rs @@ -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 afaf6af66b2..c91f0b9ae94 100644 --- a/tests/ui/map_flatten_fixable.stderr +++ b/tests/ui/map_flatten_fixable.stderr @@ -78,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(); @@ -89,7 +89,7 @@ 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 ~ }); diff --git a/tests/ui/same_name_method.rs b/tests/ui/same_name_method.rs index 9562b47f0c4..daef95a425c 100644 --- a/tests/ui/same_name_method.rs +++ b/tests/ui/same_name_method.rs @@ -62,7 +62,7 @@ fn foo() {} impl T1 for S {} } - mod mulitply_conflicit_trait { + mod multiply_conflicit_trait { use crate::{T1, T2}; struct S; diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.fixed b/tests/ui/transmutes_expressible_as_ptr_casts.fixed index b425cdd6cbf..539239fc18f 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 8fd57c59652..b9e446dc89a 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/wildcard_imports.fixed b/tests/ui/wildcard_imports.fixed index b6f47ae906b..ef55f1c31a8 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 eb404b7a3de..b8128514206 100644 --- a/tests/ui/wildcard_imports.rs +++ b/tests/ui/wildcard_imports.rs @@ -200,7 +200,7 @@ fn with_super() { } } - mod should_be_replaced_futher_inside { + mod should_be_replaced_further_inside { fn insidefoo() {} mod inner { use super::*; From 467e1b23aa7f33e126bc92e89217158362129df4 Mon Sep 17 00:00:00 2001 From: Korlo <88337245+Rqnsom@users.noreply.github.com> Date: Wed, 13 Jul 2022 17:02:09 +0200 Subject: [PATCH 73/84] box_collection: raise warn for all std collections So far, only [Vec, String, Hashmap] were considered. Extend collection checklist for this lint with: - HashSet - VecDeque - LinkedList - BTreeMap - BTreeSet - BinaryHeap --- clippy_lints/src/types/box_collection.rs | 27 ++++++++---- tests/ui/box_collection.rs | 16 ++++++- tests/ui/box_collection.stderr | 56 ++++++++++++++++++++++-- 3 files changed, 84 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/types/box_collection.rs b/clippy_lints/src/types/box_collection.rs index 21a9558ec07..ba51404d214 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/tests/ui/box_collection.rs b/tests/ui/box_collection.rs index e00f061f28a..1a74cdb3ff6 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 6de85d05a99..2b28598ded9 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 From 08431cd17e68e69376f62eb05c87d2adb3d9e468 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 17 May 2022 12:12:31 -0400 Subject: [PATCH 74/84] Update highlight.js --- util/gh-pages/index.html | 7 ++++--- util/gh-pages/script.js | 5 ++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/util/gh-pages/index.html b/util/gh-pages/index.html index 6183089911b..c5d602ea303 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 6909fbcae09..366e7c8843f 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 eee0bf459dcdf8ff159e3c8d11600cb73f4519de Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 30 Jun 2022 10:17:49 -0400 Subject: [PATCH 75/84] add array tests, cleanup, tidy, and bless --- tests/ui/derive.rs | 2 +- tests/ui/no_effect.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui/derive.rs b/tests/ui/derive.rs index 4e46bf13991..b276c384c04 100644 --- a/tests/ui/derive.rs +++ b/tests/ui/derive.rs @@ -1,7 +1,7 @@ -#![feature(untagged_unions)] #![allow(dead_code)] #![warn(clippy::expl_impl_clone_on_copy)] + #[derive(Copy)] struct Qux; diff --git a/tests/ui/no_effect.rs b/tests/ui/no_effect.rs index 7ece66a1ccb..f0c59b4080b 100644 --- a/tests/ui/no_effect.rs +++ b/tests/ui/no_effect.rs @@ -4,7 +4,7 @@ #![allow(path_statements)] #![allow(clippy::deref_addrof)] #![allow(clippy::redundant_field_names)] -#![feature(untagged_unions)] + struct Unit; struct Tuple(i32); From 73f32942c6d837301926ec2bfa93f27b9e61afc9 Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Wed, 6 Jul 2022 07:44:47 -0500 Subject: [PATCH 76/84] Rename `debugging_opts` to `unstable_opts` This is no longer used only for debugging options (e.g. `-Zoutput-width`, `-Zallow-features`). Rename it to be more clear. --- src/driver.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/driver.rs b/src/driver.rs index 96d542cfe10..c219c7de830 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -117,7 +117,7 @@ fn config(&mut self, config: &mut interface::Config) { // run on the unoptimized MIR. On the other hand this results in some false negatives. If // MIR passes can be enabled / disabled separately, we should figure out, what passes to // use for Clippy. - config.opts.debugging_opts.mir_opt_level = Some(0); + config.opts.unstable_opts.mir_opt_level = Some(0); } } From 7fbaf8113bae2825b2fef9bf22948eded2798bc3 Mon Sep 17 00:00:00 2001 From: Takayuki Nakata Date: Thu, 14 Jul 2022 09:46:46 +0900 Subject: [PATCH 77/84] Fix the minimal version for `clap` --- lintcheck/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index 254f391e4bd..737c845c045 100644 --- a/lintcheck/Cargo.toml +++ b/lintcheck/Cargo.toml @@ -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"] } From 43ac256cd19ebb7a423ed75a2fd0e81af38ececc Mon Sep 17 00:00:00 2001 From: Victor Nordam Suadicani Date: Thu, 14 Jul 2022 13:26:56 +0200 Subject: [PATCH 78/84] Add Known problems section --- clippy_lints/src/format_push_string.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index 17aa482d716..ebf5ab086dc 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -14,6 +14,12 @@ /// ### 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(); From 2f825db5789678965861e5109aa30b9b9e2a7b4c Mon Sep 17 00:00:00 2001 From: i509VCB Date: Fri, 1 Jul 2022 23:59:03 -0500 Subject: [PATCH 79/84] std_instead_of_core, std_instead_of_alloc, alloc_instead_of_core --- CHANGELOG.md | 3 + clippy_lints/src/lib.register_lints.rs | 3 + clippy_lints/src/lib.register_restriction.rs | 3 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/std_instead_of_core.rs | 134 +++++++++++++++++++ tests/ui/std_instead_of_core.rs | 39 ++++++ tests/ui/std_instead_of_core.stderr | 85 ++++++++++++ 7 files changed, 269 insertions(+) create mode 100644 clippy_lints/src/std_instead_of_core.rs create mode 100644 tests/ui/std_instead_of_core.rs create mode 100644 tests/ui/std_instead_of_core.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 201929ec11c..920d397add7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3432,6 +3432,7 @@ Released 2018-09-13 [`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 @@ -3901,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/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index ceb8470657f..91d27bf526d 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -497,6 +497,9 @@ 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_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index 88d43c8989f..43f1c892eb9 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -66,6 +66,9 @@ 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 1604d1078ee..4f9db4eb5ae 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -365,6 +365,7 @@ macro_rules! declare_clippy_lint { 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; @@ -915,6 +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)); // 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 new file mode 100644 index 00000000000..56f2a7bae15 --- /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/tests/ui/std_instead_of_core.rs b/tests/ui/std_instead_of_core.rs new file mode 100644 index 00000000000..74f05ec1f65 --- /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 00000000000..9f1644835c1 --- /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 + From 2894df34092ce5fb9bfcf74a22a397b17dfaf983 Mon Sep 17 00:00:00 2001 From: alex-semenyuk Date: Fri, 15 Jul 2022 09:15:31 +0300 Subject: [PATCH 80/84] match_like_matches_macro does not trigger when one arm contains contains a block with only a bool literal --- .../src/matches/match_like_matches.rs | 10 ++--- 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 ++++++++++++------- 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index a68eec842ab..0da4833f1df 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -81,14 +81,14 @@ fn find_matches_sugg<'a, 'b, I>( 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 @@ fn find_matches_sugg<'a, 'b, I>( } /// 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/tests/ui/match_expr_like_matches_macro.fixed b/tests/ui/match_expr_like_matches_macro.fixed index 36f233f3346..1ccbfda64b7 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 750f69fa508..a49991f5941 100644 --- a/tests/ui/match_expr_like_matches_macro.rs +++ b/tests/ui/match_expr_like_matches_macro.rs @@ -61,6 +61,18 @@ enum E { _ => 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 d7cedf9f9f1..e94555e2744 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 From 9178ba1f557263a590db31a5c450fc20f36137de Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 15 Jul 2022 09:49:38 +0200 Subject: [PATCH 81/84] Bump nightly version -> 2022-07-15 --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index 6cc6d5036b3..e693e683759 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"] From b707a238148a144217b211d25d643cb596a4753b Mon Sep 17 00:00:00 2001 From: Guilherme-Vasconcelos <49197151+Guilherme-Vasconcelos@users.noreply.github.com> Date: Tue, 12 Jul 2022 21:06:10 -0300 Subject: [PATCH 82/84] Rename rustcSource in contributing docs --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 56cb5a1538c..6e15133d267 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,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 From e7804355de8bfc93b3604beec7a7c32665a305ee Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Sun, 19 Jun 2022 14:15:14 +0000 Subject: [PATCH 83/84] NEEDLESS_OPTION_TAKE doc improvements NEEDLESS_OPTION_TAKE doc improvements NEEDLESS_OPTION_TAKE doc improvements NEEDLESS_OPTION_TAKE doc improvements NEEDLESS_OPTION_TAKE doc improvements NEEDLESS_OPTION_TAKE doc improvements NEEDLESS_OPTION_TAKE doc improvements NEEDLESS_OPTION_TAKE doc improvements --- clippy_lints/src/methods/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 9bb7bb7a7ab..4bdb5785cff 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -2225,8 +2225,12 @@ } 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 From 142898e5443d431269caa258b18047c1ac93e7a2 Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Mon, 18 Jul 2022 00:35:34 +0300 Subject: [PATCH 84/84] Fix example for `clippy::invalid_regex` --- clippy_lints/src/regex.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/regex.rs b/clippy_lints/src/regex.rs index 67129299e2f..f9a9b069193 100644 --- a/clippy_lints/src/regex.rs +++ b/clippy_lints/src/regex.rs @@ -19,7 +19,7 @@ /// /// ### Example /// ```ignore - /// Regex::new("|") + /// Regex::new("(") /// ``` #[clippy::version = "pre 1.29.0"] pub INVALID_REGEX,