From 318b8b6aabb2ef0fae0c0aafbba2c7ad97fb3a2a Mon Sep 17 00:00:00 2001 From: Mark Rousskov Date: Sun, 10 May 2020 22:15:04 -0400 Subject: [PATCH 01/72] Add hint for collect type --- clippy_lints/src/utils/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index f7a91fcdd213a..8d75e1269630d 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -1390,7 +1390,7 @@ pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_, '_>, did: DefId) -> bool .predicates .iter() .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None }) - .collect(); + .collect::>(); !traits::normalize_and_test_predicates( cx.tcx, traits::elaborate_predicates(cx.tcx, predicates) From 221c482142f84d5889edb8d17efab6470c81cf04 Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Fri, 22 May 2020 15:29:47 +0000 Subject: [PATCH 02/72] Stabilize str_strip feature --- src/driver.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/driver.rs b/src/driver.rs index d3a7e24937f95..3e1f423865b1d 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -1,6 +1,5 @@ #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![feature(rustc_private)] -#![feature(str_strip)] // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) From e820a03d1c905d58b27d7ac6d85f450f9754ba79 Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Sat, 23 May 2020 11:49:24 +0200 Subject: [PATCH 03/72] iterate List by value --- clippy_lints/src/needless_pass_by_value.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 60c5360054334..1621c7f947c33 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -111,7 +111,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue { let fn_def_id = cx.tcx.hir().local_def_id(hir_id); - let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds.iter().copied()) + let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds.iter()) .filter(|p| !p.is_global()) .filter_map(|obligation| { if let ty::PredicateKind::Trait(poly_trait_ref, _) = obligation.predicate.kind() { @@ -179,7 +179,6 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue { .substs .iter() .skip(1) - .cloned() .collect::>(); implements_trait(cx, ty_empty_region, t.def_id(), ty_params) }) From 64a05f56c33d4754808ef85e634f72a9053c56fd Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Thu, 28 May 2020 00:36:15 +0200 Subject: [PATCH 04/72] len_zero: skip ranges if feature `range_is_empty` is not enabled --- clippy_lints/src/len_zero.rs | 17 ++++++++++++++++- tests/ui/len_zero.fixed | 8 ++++++++ tests/ui/len_zero.rs | 8 ++++++++ tests/ui/len_zero_ranges.fixed | 14 ++++++++++++++ tests/ui/len_zero_ranges.rs | 14 ++++++++++++++ tests/ui/len_zero_ranges.stderr | 10 ++++++++++ 6 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 tests/ui/len_zero_ranges.fixed create mode 100644 tests/ui/len_zero_ranges.rs create mode 100644 tests/ui/len_zero_ranges.stderr diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 2ec0b5a8d6fb4..f5bfede75a761 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -1,4 +1,4 @@ -use crate::utils::{get_item_name, snippet_with_applicability, span_lint, span_lint_and_sugg, walk_ptrs_ty}; +use crate::utils::{get_item_name, higher, snippet_with_applicability, span_lint, span_lint_and_sugg, walk_ptrs_ty}; use rustc_ast::ast::LitKind; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; @@ -259,6 +259,17 @@ fn check_len( /// Checks if this type has an `is_empty` method. fn has_is_empty(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + /// Special case ranges until `range_is_empty` is stabilized. See issue 3807. + fn should_skip_range(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + higher::range(cx, expr).map_or(false, |_| { + !cx.tcx + .features() + .declared_lib_features + .iter() + .any(|(name, _)| name.as_str() == "range_is_empty") + }) + } + /// Gets an `AssocItem` and return true if it matches `is_empty(self)`. fn is_is_empty(cx: &LateContext<'_, '_>, item: &ty::AssocItem) -> bool { if let ty::AssocKind::Fn = item.kind { @@ -284,6 +295,10 @@ fn has_is_empty(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { }) } + if should_skip_range(cx, expr) { + return false; + } + let ty = &walk_ptrs_ty(cx.tables.expr_ty(expr)); match ty.kind { ty::Dynamic(ref tt, ..) => { diff --git a/tests/ui/len_zero.fixed b/tests/ui/len_zero.fixed index 624e5ef8fcf13..a29b832eb6019 100644 --- a/tests/ui/len_zero.fixed +++ b/tests/ui/len_zero.fixed @@ -141,3 +141,11 @@ fn main() { fn test_slice(b: &[u8]) { if !b.is_empty() {} } + +mod issue_3807 { + // Avoid suggesting changes to ranges if the user did not enable `range_is_empty`. + // See https://github.com/rust-lang/rust/issues/48111#issuecomment-445132965 + fn no_suggestion() { + let _ = (0..42).len() == 0; + } +} diff --git a/tests/ui/len_zero.rs b/tests/ui/len_zero.rs index 7fba971cfd887..8fd0093f4fdbb 100644 --- a/tests/ui/len_zero.rs +++ b/tests/ui/len_zero.rs @@ -141,3 +141,11 @@ fn main() { fn test_slice(b: &[u8]) { if b.len() != 0 {} } + +mod issue_3807 { + // Avoid suggesting changes to ranges if the user did not enable `range_is_empty`. + // See https://github.com/rust-lang/rust/issues/48111#issuecomment-445132965 + fn no_suggestion() { + let _ = (0..42).len() == 0; + } +} diff --git a/tests/ui/len_zero_ranges.fixed b/tests/ui/len_zero_ranges.fixed new file mode 100644 index 0000000000000..7da26f8ff4d47 --- /dev/null +++ b/tests/ui/len_zero_ranges.fixed @@ -0,0 +1,14 @@ +// run-rustfix + +#![feature(range_is_empty)] +#![warn(clippy::len_zero)] +#![allow(unused)] + +mod issue_3807 { + // With the feature enabled, `is_empty` should be suggested + fn suggestion_is_fine() { + let _ = (0..42).is_empty(); + } +} + +fn main() {} diff --git a/tests/ui/len_zero_ranges.rs b/tests/ui/len_zero_ranges.rs new file mode 100644 index 0000000000000..be7b4244bc06c --- /dev/null +++ b/tests/ui/len_zero_ranges.rs @@ -0,0 +1,14 @@ +// run-rustfix + +#![feature(range_is_empty)] +#![warn(clippy::len_zero)] +#![allow(unused)] + +mod issue_3807 { + // With the feature enabled, `is_empty` should be suggested + fn suggestion_is_fine() { + let _ = (0..42).len() == 0; + } +} + +fn main() {} diff --git a/tests/ui/len_zero_ranges.stderr b/tests/ui/len_zero_ranges.stderr new file mode 100644 index 0000000000000..6e5fa41fb08a5 --- /dev/null +++ b/tests/ui/len_zero_ranges.stderr @@ -0,0 +1,10 @@ +error: length comparison to zero + --> $DIR/len_zero_ranges.rs:10:17 + | +LL | let _ = (0..42).len() == 0; + | ^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(0..42).is_empty()` + | + = note: `-D clippy::len-zero` implied by `-D warnings` + +error: aborting due to previous error + From 7b490903809ce5c03c83869357a68e88f8cc0799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Kr=C3=BCger?= Date: Wed, 27 May 2020 14:08:31 +0200 Subject: [PATCH 05/72] clippy_dev: add ra_setup This takes an absolute path to a rustc repo and adds path-dependencies that point towards the respective rustc subcrates into the Cargo.tomls of the clippy and clippy_lints crate. This allows rustc-analyzer to show proper type annotations etc on rustc-internals inside the clippy repo. Usage: cargo dev ra-setup /absolute/path/to/rust/ cc https://github.com/rust-analyzer/rust-analyzer/issues/3517 cc https://github.com/rust-lang/rust-clippy/issues/5514 --- clippy_dev/src/lib.rs | 3 +- clippy_dev/src/main.rs | 16 ++++++- clippy_dev/src/ra_setup.rs | 90 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 clippy_dev/src/ra_setup.rs diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 6fdd282c6849e..5baa31d5cde0c 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -11,6 +11,7 @@ use walkdir::WalkDir; pub mod fmt; pub mod new_lint; +pub mod ra_setup; pub mod stderr_length_check; pub mod update_lints; @@ -400,7 +401,7 @@ fn test_replace_region_no_changes() { changed: false, new_lines: "123\n456\n789".to_string(), }; - let result = replace_region_in_text(text, r#"^\s*123$"#, r#"^\s*456"#, false, || vec![]); + let result = replace_region_in_text(text, r#"^\s*123$"#, r#"^\s*456"#, false, Vec::new); assert_eq!(expected, result); } diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index d99235f7c07a7..281037ae37c97 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -1,7 +1,7 @@ #![cfg_attr(feature = "deny-warnings", deny(warnings))] use clap::{App, Arg, SubCommand}; -use clippy_dev::{fmt, new_lint, stderr_length_check, update_lints}; +use clippy_dev::{fmt, new_lint, ra_setup, stderr_length_check, update_lints}; fn main() { let matches = App::new("Clippy developer tooling") @@ -87,6 +87,19 @@ fn main() { SubCommand::with_name("limit_stderr_length") .about("Ensures that stderr files do not grow longer than a certain amount of lines."), ) + .subcommand( + SubCommand::with_name("ra-setup") + .about("Alter dependencies so rust-analyzer can find rustc internals") + .arg( + Arg::with_name("rustc-repo-path") + .long("repo-path") + .short("r") + .help("The path to a rustc repo that will be used for setting the dependencies") + .takes_value(true) + .value_name("path") + .required(true), + ), + ) .get_matches(); match matches.subcommand() { @@ -115,6 +128,7 @@ fn main() { ("limit_stderr_length", _) => { stderr_length_check::check(); }, + ("ra-setup", Some(matches)) => ra_setup::run(matches.value_of("rustc-repo-path")), _ => {}, } } diff --git a/clippy_dev/src/ra_setup.rs b/clippy_dev/src/ra_setup.rs new file mode 100644 index 0000000000000..8617445c8a600 --- /dev/null +++ b/clippy_dev/src/ra_setup.rs @@ -0,0 +1,90 @@ +#![allow(clippy::filter_map)] + +use std::fs; +use std::fs::File; +use std::io::prelude::*; +use std::path::PathBuf; + +// This module takes an absolute path to a rustc repo and alters the dependencies to point towards +// the respective rustc subcrates instead of using extern crate xyz. +// This allows rust analyzer to analyze rustc internals and show proper information inside clippy +// code. See https://github.com/rust-analyzer/rust-analyzer/issues/3517 and https://github.com/rust-lang/rust-clippy/issues/5514 for details + +pub fn run(rustc_path: Option<&str>) { + // we can unwrap here because the arg is required here + let rustc_path = PathBuf::from(rustc_path.unwrap()); + assert!(rustc_path.is_dir(), "path is not a directory"); + let rustc_source_basedir = rustc_path.join("src"); + assert!( + rustc_source_basedir.is_dir(), + "are you sure the path leads to a rustc repo?" + ); + + let clippy_root_manifest = fs::read_to_string("Cargo.toml").expect("failed to read ./Cargo.toml"); + let clippy_root_lib_rs = fs::read_to_string("src/driver.rs").expect("failed to read ./src/driver.rs"); + inject_deps_into_manifest( + &rustc_source_basedir, + "Cargo.toml", + &clippy_root_manifest, + &clippy_root_lib_rs, + ) + .expect("Failed to inject deps into ./Cargo.toml"); + + let clippy_lints_manifest = + fs::read_to_string("clippy_lints/Cargo.toml").expect("failed to read ./clippy_lints/Cargo.toml"); + let clippy_lints_lib_rs = + fs::read_to_string("clippy_lints/src/lib.rs").expect("failed to read ./clippy_lints/src/lib.rs"); + inject_deps_into_manifest( + &rustc_source_basedir, + "clippy_lints/Cargo.toml", + &clippy_lints_manifest, + &clippy_lints_lib_rs, + ) + .expect("Failed to inject deps into ./clippy_lints/Cargo.toml"); +} + +fn inject_deps_into_manifest( + rustc_source_dir: &PathBuf, + manifest_path: &str, + cargo_toml: &str, + lib_rs: &str, +) -> std::io::Result<()> { + let extern_crates = lib_rs + .lines() + // get the deps + .filter(|line| line.starts_with("extern crate")) + // we have something like "extern crate foo;", we only care about the "foo" + // ↓ ↓ + // extern crate rustc_middle; + .map(|s| &s[13..(s.len() - 1)]); + + let new_deps = extern_crates.map(|dep| { + // format the dependencies that are going to be put inside the Cargo.toml + format!( + "{dep} = {{ path = \"{source_path}/lib{dep}\" }}\n", + dep = dep, + source_path = rustc_source_dir.display() + ) + }); + + // format a new [dependencies]-block with the new deps we need to inject + let mut all_deps = String::from("[dependencies]\n"); + new_deps.for_each(|dep_line| { + all_deps.push_str(&dep_line); + }); + + // replace "[dependencies]" with + // [dependencies] + // dep1 = { path = ... } + // dep2 = { path = ... } + // etc + let new_manifest = cargo_toml.replacen("[dependencies]\n", &all_deps, 1); + + // println!("{}", new_manifest); + let mut file = File::create(manifest_path)?; + file.write_all(new_manifest.as_bytes())?; + + println!("Dependency paths injected: {}", manifest_path); + + Ok(()) +} From a0e9f9bd0dca39ccff47baedffa94aca74911a86 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 28 May 2020 15:45:24 +0200 Subject: [PATCH 06/72] Merge commit '7ea7cd165ad6705603852771bf82cc2fd6560db5' into clippyup2 --- .github/workflows/clippy.yml | 2 +- .github/workflows/clippy_bors.yml | 10 +- CHANGELOG.md | 1 + CONTRIBUTING.md | 100 +++++---- clippy_dev/src/new_lint.rs | 190 +++++++++++------- clippy_lints/Cargo.toml | 2 + clippy_lints/src/attrs.rs | 75 ++++--- clippy_lints/src/cargo_common_metadata.rs | 1 + clippy_lints/src/float_literal.rs | 2 +- clippy_lints/src/lib.rs | 17 +- clippy_lints/src/matches.rs | 74 ++++++- clippy_lints/src/methods/mod.rs | 34 +++- clippy_lints/src/misc_early.rs | 2 +- clippy_lints/src/missing_const_for_fn.rs | 2 +- clippy_lints/src/multiple_crate_versions.rs | 58 ++++-- clippy_lints/src/mut_reference.rs | 2 +- clippy_lints/src/needless_pass_by_value.rs | 2 +- clippy_lints/src/new_without_default.rs | 128 +++--------- clippy_lints/src/non_expressive_names.rs | 15 +- clippy_lints/src/ptr.rs | 10 +- clippy_lints/src/ranges.rs | 30 ++- clippy_lints/src/redundant_field_names.rs | 4 + .../src/trivially_copy_pass_by_ref.rs | 2 +- clippy_lints/src/useless_conversion.rs | 79 ++++++-- clippy_lints/src/utils/conf.rs | 6 +- clippy_lints/src/utils/inspector.rs | 8 +- clippy_lints/src/utils/mod.rs | 2 +- clippy_lints/src/utils/paths.rs | 2 + clippy_lints/src/utils/sugg.rs | 2 +- doc/adding_lints.md | 25 ++- doc/common_tools_writing_lints.md | 152 ++++++++++++++ rustc_tools_util/LICENSE-APACHE | 1 + rustc_tools_util/LICENSE-MIT | 1 + setup-toolchain.sh | 2 +- src/driver.rs | 2 +- src/lintlist/mod.rs | 11 +- tests/compile-test.rs | 150 ++++++++++---- .../cargo_common_metadata/fail/Cargo.toml | 4 + .../cargo_common_metadata/fail/src/main.rs | 4 + .../fail/src/main.stderr | 18 ++ .../cargo_common_metadata/pass/Cargo.toml | 11 + .../cargo_common_metadata/pass/src/main.rs | 4 + .../5041_allow_dev_build/Cargo.toml | 17 ++ .../5041_allow_dev_build/src/main.rs | 4 + .../multiple_crate_versions/fail/Cargo.toml | 8 + .../multiple_crate_versions/fail/src/main.rs | 4 + .../fail/src/main.stderr | 6 + .../multiple_crate_versions/pass/Cargo.toml | 8 + .../multiple_crate_versions/pass/src/main.rs | 4 + tests/ui-cargo/update-all-references.sh | 18 ++ tests/ui-cargo/update-references.sh | 38 ++++ .../wildcard_dependencies/fail/Cargo.toml | 7 + .../wildcard_dependencies/fail/src/main.rs | 4 + .../fail/src/main.stderr | 6 + .../wildcard_dependencies/pass/Cargo.toml | 7 + .../wildcard_dependencies/pass/src/main.rs | 4 + tests/ui/auxiliary/proc_macro_attr.rs | 37 ++++ tests/ui/cognitive_complexity.rs | 2 +- tests/ui/cognitive_complexity_attr_used.rs | 4 +- tests/ui/empty_line_after_outer_attribute.rs | 19 +- .../empty_line_after_outer_attribute.stderr | 12 +- tests/ui/future_not_send.stderr | 26 ++- tests/ui/match_wild_err_arm.stderr | 8 +- .../match_wildcard_for_single_variants.fixed | 59 ++++++ .../ui/match_wildcard_for_single_variants.rs | 59 ++++++ .../match_wildcard_for_single_variants.stderr | 28 +++ tests/ui/new_without_default.rs | 11 + tests/ui/new_without_default.stderr | 35 +++- tests/ui/option_option.rs | 25 +++ tests/ui/option_option.stderr | 8 +- tests/ui/or_fun_call.fixed | 9 + tests/ui/or_fun_call.rs | 9 + tests/ui/ptr_arg.rs | 32 ++- tests/ui/ptr_offset_with_cast.fixed | 12 +- tests/ui/ptr_offset_with_cast.rs | 12 +- tests/ui/ptr_offset_with_cast.stderr | 12 +- tests/ui/reversed_empty_ranges_fixable.fixed | 7 +- tests/ui/reversed_empty_ranges_fixable.rs | 7 +- tests/ui/reversed_empty_ranges_fixable.stderr | 16 +- tests/ui/reversed_empty_ranges_unfixable.rs | 1 - .../ui/reversed_empty_ranges_unfixable.stderr | 10 +- tests/ui/useless_conversion.stderr | 20 +- tests/ui/useless_conversion_try.rs | 42 ++++ tests/ui/useless_conversion_try.stderr | 79 ++++++++ 84 files changed, 1550 insertions(+), 433 deletions(-) create mode 100644 doc/common_tools_writing_lints.md create mode 120000 rustc_tools_util/LICENSE-APACHE create mode 120000 rustc_tools_util/LICENSE-MIT create mode 100644 tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml create mode 100644 tests/ui-cargo/cargo_common_metadata/fail/src/main.rs create mode 100644 tests/ui-cargo/cargo_common_metadata/fail/src/main.stderr create mode 100644 tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml create mode 100644 tests/ui-cargo/cargo_common_metadata/pass/src/main.rs create mode 100644 tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/Cargo.toml create mode 100644 tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs create mode 100644 tests/ui-cargo/multiple_crate_versions/fail/Cargo.toml create mode 100644 tests/ui-cargo/multiple_crate_versions/fail/src/main.rs create mode 100644 tests/ui-cargo/multiple_crate_versions/fail/src/main.stderr create mode 100644 tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml create mode 100644 tests/ui-cargo/multiple_crate_versions/pass/src/main.rs create mode 100755 tests/ui-cargo/update-all-references.sh create mode 100755 tests/ui-cargo/update-references.sh create mode 100644 tests/ui-cargo/wildcard_dependencies/fail/Cargo.toml create mode 100644 tests/ui-cargo/wildcard_dependencies/fail/src/main.rs create mode 100644 tests/ui-cargo/wildcard_dependencies/fail/src/main.stderr create mode 100644 tests/ui-cargo/wildcard_dependencies/pass/Cargo.toml create mode 100644 tests/ui-cargo/wildcard_dependencies/pass/src/main.rs create mode 100644 tests/ui/auxiliary/proc_macro_attr.rs create mode 100644 tests/ui/match_wildcard_for_single_variants.fixed create mode 100644 tests/ui/match_wildcard_for_single_variants.rs create mode 100644 tests/ui/match_wildcard_for_single_variants.stderr create mode 100644 tests/ui/useless_conversion_try.rs create mode 100644 tests/ui/useless_conversion_try.stderr diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 8edf0c23860aa..5fa8009a8b42c 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -49,7 +49,7 @@ jobs: run: cargo update - name: Cache cargo dir - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.cargo key: ${{ runner.os }}-x86_64-unknown-linux-gnu-${{ hashFiles('Cargo.lock') }} diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 6675a1029bbc8..3958ba012467b 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -94,7 +94,7 @@ jobs: run: cargo update - name: Cache cargo dir - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.cargo key: ${{ runner.os }}-${{ matrix.host }}-${{ hashFiles('Cargo.lock') }} @@ -190,7 +190,7 @@ jobs: run: cargo update - name: Cache cargo dir - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.cargo key: ${{ runner.os }}-x86_64-unknown-linux-gnu-${{ hashFiles('Cargo.lock') }} @@ -269,7 +269,7 @@ jobs: run: cargo update - name: Cache cargo dir - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.cargo key: ${{ runner.os }}-x86_64-unknown-linux-gnu-${{ hashFiles('Cargo.lock') }} @@ -312,7 +312,7 @@ jobs: name: bors test finished if: github.event.pusher.name == 'bors' && success() runs-on: ubuntu-latest - needs: [base, integration] + needs: [changelog, base, integration_build, integration] steps: - name: Mark the job as successful @@ -322,7 +322,7 @@ jobs: name: bors test finished if: github.event.pusher.name == 'bors' && (failure() || cancelled()) runs-on: ubuntu-latest - needs: [base, integration] + needs: [changelog, base, integration_build, integration] steps: - name: Mark the job as a failure diff --git a/CHANGELOG.md b/CHANGELOG.md index 583c32ca9e032..2ac9057199ff3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1439,6 +1439,7 @@ Released 2018-09-13 [`match_same_arms`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_same_arms [`match_single_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_single_binding [`match_wild_err_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wild_err_arm +[`match_wildcard_for_single_variants`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wildcard_for_single_variants [`maybe_infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_infinite_iter [`mem_discriminant_non_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_discriminant_non_enum [`mem_forget`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_forget diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 50a5ee8bbf3c8..0f47ac98fd20a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -155,47 +155,77 @@ That's why the `else_if_without_else` example uses the `register_early_pass` fun ## Fixing build failures caused by Rust -Clippy will sometimes fail to build from source because building it depends on unstable internal Rust features. Most of -the times we have to adapt to the changes and only very rarely there's an actual bug in Rust. Fixing build failures -caused by Rust updates, can be a good way to learn about Rust internals. +Clippy currently gets built with `rustc` of the `rust-lang/rust` `master` +branch. Most of the times we have to adapt to the changes and only very rarely +there's an actual bug in Rust. + +If you decide to make Clippy work again with a Rust commit that breaks it, you +have to sync the `rust-lang/rust-clippy` repository with the `subtree` copy of +Clippy in the `rust-lang/rust` repository. + +For general information about `subtree`s in the Rust repository see [Rust's +`CONTRIBUTING.md`][subtree]. + +Here is a TL;DR version of the sync process (all of the following commands have +to be run inside the `rust` directory): + +1. Clone the [`rust-lang/rust`] repository +2. Sync the changes to the rust-copy of Clippy to your Clippy fork: + ```bash + # Make sure to change `your-github-name` to your github name in the following command + git subtree push -P src/tools/clippy git@github.com:your-github-name/rust-clippy sync-from-rust + ``` + _Note:_ This will directly push to the remote repository. You can also push + to your local copy by replacing the remote address with `/path/to/rust-clippy` + directory. + + _Note:_ Most of the time you have to create a merge commit in the + `rust-clippy` repo (this has to be done in the Clippy repo, not in the + rust-copy of Clippy): + ```bash + git fetch origin && git fetch upstream + git checkout sync-from-rust + git merge upstream/master + ``` +3. Open a PR to `rust-lang/rust-clippy` and wait for it to get merged (to + accelerate the process ping the `@rust-lang/clippy` team in your PR and/or + ~~annoy~~ ask them in the [Discord] channel.) +4. Sync the `rust-lang/rust-clippy` master to the rust-copy of Clippy: + ```bash + git checkout -b sync-from-clippy + git subtree pull -P src/tools/clippy https://github.com/rust-lang/rust-clippy master + ``` +5. Open a PR to [`rust-lang/rust`] + +Also, you may want to define remotes, so you don't have to type out the remote +addresses on every sync. You can do this with the following commands (these +commands still have to be run inside the `rust` directory): -In order to find out why Clippy does not work properly with a new Rust commit, you can use the [rust-toolstate commit -history][toolstate_commit_history]. You will then have to look for the last commit that contains -`test-pass -> build-fail` or `test-pass -> test-fail` for the `clippy-driver` component. -[Here][toolstate_commit] is an example. - -The commit message contains a link to the PR. The PRs are usually small enough to discover the breaking API change and -if they are bigger, they likely include some discussion that may help you to fix Clippy. - -To check if Clippy is available for a specific target platform, you can check -the [rustup component history][rustup_component_history]. - -If you decide to make Clippy work again with a Rust commit that breaks it, -you probably want to install the latest Rust from master locally and run Clippy -using that version of Rust. - -You can set up the master toolchain by running `./setup-toolchain.sh`. That script will install -[rustup-toolchain-install-master][rtim] and master toolchain, then run `rustup override set master`. - -After fixing the build failure on this repository, we can submit a pull request -to [`rust-lang/rust`] to fix the toolstate. +```bash +# Set clippy-upstream remote for pulls +$ git remote add clippy-upstream https://github.com/rust-lang/rust-clippy +# Make sure to not push to the upstream repo +$ git remote set-url --push clippy-upstream DISABLED +# Set clippy-origin remote to your fork for pushes +$ git remote add clippy-origin git@github.com:your-github-name/rust-clippy +# Set a local remote +$ git remote add clippy-local /path/to/rust-clippy +``` -To submit a pull request, you should follow these steps: +You can then sync with the remote names from above, e.g.: ```bash -# Assuming you already cloned the rust-lang/rust repo and you're in the correct directory -git submodule update --remote src/tools/clippy -cargo update -p clippy -git add -u -git commit -m "Update Clippy" -./x.py test -i --stage 1 src/tools/clippy # This is optional and should succeed anyway -# Open a PR in rust-lang/rust +$ git subtree push -P src/tools/clippy clippy-local sync-from-rust ``` -[rustup_component_history]: https://rust-lang.github.io/rustup-components-history -[toolstate_commit_history]: https://github.com/rust-lang-nursery/rust-toolstate/commits/master -[toolstate_commit]: https://github.com/rust-lang-nursery/rust-toolstate/commit/aad74d8294e198a7cf8ac81a91aebb7f3bbcf727 -[rtim]: https://github.com/kennytm/rustup-toolchain-install-master +_Note:_ The first time running `git subtree push` a cache has to be built. This +involves going through the complete Clippy history once. For this you have to +increase the stack limit though, which you can do with `ulimit -s 60000`. For +this to work, you will need the fix of `git subtree` available +[here][gitgitgadget-pr]. + +[gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493 +[subtree]: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#external-dependencies-subtree [`rust-lang/rust`]: https://github.com/rust-lang/rust ## Issue and PR triage diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 44b2a5383d211..c0b2dac2f60ff 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -1,91 +1,111 @@ use crate::clippy_project_root; -use std::fs::{File, OpenOptions}; -use std::io; +use std::fs::{self, OpenOptions}; use std::io::prelude::*; -use std::io::ErrorKind; -use std::path::Path; +use std::io::{self, ErrorKind}; +use std::path::{Path, PathBuf}; + +struct LintData<'a> { + pass: &'a str, + name: &'a str, + category: &'a str, + project_root: PathBuf, +} + +trait Context { + fn context>(self, text: C) -> Self; +} + +impl Context for io::Result { + fn context>(self, text: C) -> Self { + match self { + Ok(t) => Ok(t), + Err(e) => { + let message = format!("{}: {}", text.as_ref(), e); + Err(io::Error::new(ErrorKind::Other, message)) + }, + } + } +} -/// Creates files required to implement and test a new lint and runs `update_lints`. +/// Creates the files required to implement and test a new lint and runs `update_lints`. /// /// # Errors /// -/// This function errors, if the files couldn't be created -pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>) -> Result<(), io::Error> { - let pass = pass.expect("`pass` argument is validated by clap"); - let lint_name = lint_name.expect("`name` argument is validated by clap"); - let category = category.expect("`category` argument is validated by clap"); - - match open_files(lint_name) { - Ok((mut test_file, mut lint_file)) => { - let (pass_type, pass_lifetimes, pass_import, context_import) = match pass { - "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"), - "late" => ("LateLintPass", "<'_, '_>", "use rustc_hir::*;", "LateContext"), - _ => { - unreachable!("`pass_type` should only ever be `early` or `late`!"); - }, - }; - - let camel_case_name = to_camel_case(lint_name); - - if let Err(e) = test_file.write_all(get_test_file_contents(lint_name).as_bytes()) { - return Err(io::Error::new( - ErrorKind::Other, - format!("Could not write to test file: {}", e), - )); - }; - - if let Err(e) = lint_file.write_all( - get_lint_file_contents( - pass_type, - pass_lifetimes, - lint_name, - &camel_case_name, - category, - pass_import, - context_import, - ) - .as_bytes(), - ) { - return Err(io::Error::new( - ErrorKind::Other, - format!("Could not write to lint file: {}", e), - )); - } - Ok(()) +/// This function errors out if the files couldn't be created or written to. +pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>) -> io::Result<()> { + let lint = LintData { + pass: pass.expect("`pass` argument is validated by clap"), + name: lint_name.expect("`name` argument is validated by clap"), + category: category.expect("`category` argument is validated by clap"), + project_root: clippy_project_root(), + }; + + create_lint(&lint).context("Unable to create lint implementation")?; + create_test(&lint).context("Unable to create a test for the new lint") +} + +fn create_lint(lint: &LintData) -> io::Result<()> { + let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass { + "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"), + "late" => ("LateLintPass", "<'_, '_>", "use rustc_hir::*;", "LateContext"), + _ => { + unreachable!("`pass_type` should only ever be `early` or `late`!"); }, - Err(e) => Err(io::Error::new( - ErrorKind::Other, - format!("Unable to create lint: {}", e), - )), - } + }; + + let camel_case_name = to_camel_case(lint.name); + let lint_contents = get_lint_file_contents( + pass_type, + pass_lifetimes, + lint.name, + &camel_case_name, + lint.category, + pass_import, + context_import, + ); + + let lint_path = format!("clippy_lints/src/{}.rs", lint.name); + write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes()) } -fn open_files(lint_name: &str) -> Result<(File, File), io::Error> { - let project_root = clippy_project_root(); +fn create_test(lint: &LintData) -> io::Result<()> { + fn create_project_layout>(lint_name: &str, location: P, case: &str, hint: &str) -> io::Result<()> { + let mut path = location.into().join(case); + fs::create_dir(&path)?; + write_file(path.join("Cargo.toml"), get_manifest_contents(lint_name, hint))?; - let test_file_path = project_root.join("tests").join("ui").join(format!("{}.rs", lint_name)); - let lint_file_path = project_root - .join("clippy_lints") - .join("src") - .join(format!("{}.rs", lint_name)); + path.push("src"); + fs::create_dir(&path)?; + let header = format!("// compile-flags: --crate-name={}", lint_name); + write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?; - if Path::new(&test_file_path).exists() { - return Err(io::Error::new( - ErrorKind::AlreadyExists, - format!("test file {:?} already exists", test_file_path), - )); + Ok(()) } - if Path::new(&lint_file_path).exists() { - return Err(io::Error::new( - ErrorKind::AlreadyExists, - format!("lint file {:?} already exists", lint_file_path), - )); + + if lint.category == "cargo" { + let relative_test_dir = format!("tests/ui-cargo/{}", lint.name); + let test_dir = lint.project_root.join(relative_test_dir); + fs::create_dir(&test_dir)?; + + create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?; + create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint") + } else { + let test_path = format!("tests/ui/{}.rs", lint.name); + let test_contents = get_test_file_contents(lint.name, None); + write_file(lint.project_root.join(test_path), test_contents) } +} - let test_file = OpenOptions::new().write(true).create_new(true).open(test_file_path)?; - let lint_file = OpenOptions::new().write(true).create_new(true).open(lint_file_path)?; +fn write_file, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> { + fn inner(path: &Path, contents: &[u8]) -> io::Result<()> { + OpenOptions::new() + .write(true) + .create_new(true) + .open(path)? + .write_all(contents) + } - Ok((test_file, lint_file)) + inner(path.as_ref(), contents.as_ref()).context(format!("writing to file: {}", path.as_ref().display())) } fn to_camel_case(name: &str) -> String { @@ -100,8 +120,8 @@ fn to_camel_case(name: &str) -> String { .collect() } -fn get_test_file_contents(lint_name: &str) -> String { - format!( +fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String { + let mut contents = format!( "#![warn(clippy::{})] fn main() {{ @@ -109,6 +129,26 @@ fn main() {{ }} ", lint_name + ); + + if let Some(header) = header_commands { + contents = format!("{}\n{}", header, contents); + } + + contents +} + +fn get_manifest_contents(lint_name: &str, hint: &str) -> String { + format!( + r#" +# {} + +[package] +name = "{}" +version = "0.1.0" +publish = false +"#, + hint, lint_name ) } diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 1c0be72783462..76baf27fb2dbf 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -32,6 +32,8 @@ semver = "0.9.0" # NOTE: cargo requires serde feat in its url dep # see url = { version = "2.1.0", features = ["serde"] } +quote = "1" +syn = { version = "1", features = ["full"] } [features] deny-warnings = [] diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index 64abc9fdc7174..41f125d48398f 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -248,7 +248,6 @@ declare_lint_pass!(Attributes => [ INLINE_ALWAYS, DEPRECATED_SEMVER, USELESS_ATTRIBUTE, - EMPTY_LINE_AFTER_OUTER_ATTR, UNKNOWN_CLIPPY_LINTS, ]); @@ -480,36 +479,6 @@ fn check_attrs(cx: &LateContext<'_, '_>, span: Span, name: Name, attrs: &[Attrib } for attr in attrs { - let attr_item = if let AttrKind::Normal(ref attr) = attr.kind { - attr - } else { - continue; - }; - - if attr.style == AttrStyle::Outer { - if attr_item.args.inner_tokens().is_empty() || !is_present_in_source(cx, attr.span) { - return; - } - - let begin_of_attr_to_item = Span::new(attr.span.lo(), span.lo(), span.ctxt()); - let end_of_attr_to_item = Span::new(attr.span.hi(), span.lo(), span.ctxt()); - - if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) { - let lines = snippet.split('\n').collect::>(); - let lines = without_block_comments(lines); - - if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 { - span_lint( - cx, - EMPTY_LINE_AFTER_OUTER_ATTR, - begin_of_attr_to_item, - "Found an empty line after an outer attribute. \ - Perhaps you forgot to add a `!` to make it an inner attribute?", - ); - } - } - } - if let Some(values) = attr.meta_item_list() { if values.len() != 1 || !attr.check_name(sym!(inline)) { continue; @@ -551,15 +520,57 @@ fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool { } } -declare_lint_pass!(EarlyAttributes => [DEPRECATED_CFG_ATTR, MISMATCHED_TARGET_OS]); +declare_lint_pass!(EarlyAttributes => [ + DEPRECATED_CFG_ATTR, + MISMATCHED_TARGET_OS, + EMPTY_LINE_AFTER_OUTER_ATTR, +]); impl EarlyLintPass for EarlyAttributes { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::ast::Item) { + check_empty_line_after_outer_attr(cx, item); + } + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { check_deprecated_cfg_attr(cx, attr); check_mismatched_target_os(cx, attr); } } +fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::ast::Item) { + for attr in &item.attrs { + let attr_item = if let AttrKind::Normal(ref attr) = attr.kind { + attr + } else { + return; + }; + + if attr.style == AttrStyle::Outer { + if attr_item.args.inner_tokens().is_empty() || !is_present_in_source(cx, attr.span) { + return; + } + + let begin_of_attr_to_item = Span::new(attr.span.lo(), item.span.lo(), item.span.ctxt()); + let end_of_attr_to_item = Span::new(attr.span.hi(), item.span.lo(), item.span.ctxt()); + + if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) { + let lines = snippet.split('\n').collect::>(); + let lines = without_block_comments(lines); + + if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 { + span_lint( + cx, + EMPTY_LINE_AFTER_OUTER_ATTR, + begin_of_attr_to_item, + "Found an empty line after an outer attribute. \ + Perhaps you forgot to add a `!` to make it an inner attribute?", + ); + } + } + } + } +} + fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute) { if_chain! { // check cfg_attr diff --git a/clippy_lints/src/cargo_common_metadata.rs b/clippy_lints/src/cargo_common_metadata.rs index 782da249808d0..16b46423c8f01 100644 --- a/clippy_lints/src/cargo_common_metadata.rs +++ b/clippy_lints/src/cargo_common_metadata.rs @@ -23,6 +23,7 @@ declare_clippy_lint! { /// [package] /// name = "clippy" /// version = "0.0.212" + /// authors = ["Someone "] /// description = "A bunch of helpful lints to avoid common pitfalls in Rust" /// repository = "https://github.com/rust-lang/rust-clippy" /// readme = "README.md" diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index 3a52b1d3fc20b..4c604cd01075e 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -77,7 +77,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatLiteral { let type_suffix = match lit_float_ty { LitFloatType::Suffixed(FloatTy::F32) => Some("f32"), LitFloatType::Suffixed(FloatTy::F64) => Some("f64"), - _ => None + LitFloatType::Unsuffixed => None }; let (is_whole, mut float_str) = match fty { FloatTy::F32 => { diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index e0787ac0887e4..902f3d56c1e4f 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -346,13 +346,8 @@ mod reexport { /// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass. /// /// Used in `./src/driver.rs`. -pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, conf: &Conf) { +pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) { store.register_pre_expansion_pass(|| box write::Write::default()); - store.register_pre_expansion_pass(|| box redundant_field_names::RedundantFieldNames); - let single_char_binding_names_threshold = conf.single_char_binding_names_threshold; - store.register_pre_expansion_pass(move || box non_expressive_names::NonExpressiveNames { - single_char_binding_names_threshold, - }); store.register_pre_expansion_pass(|| box attrs::EarlyAttributes); store.register_pre_expansion_pass(|| box dbg_macro::DbgMacro); } @@ -638,6 +633,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &matches::MATCH_OVERLAPPING_ARM, &matches::MATCH_REF_PATS, &matches::MATCH_SINGLE_BINDING, + &matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS, &matches::MATCH_WILD_ERR_ARM, &matches::REST_PAT_IN_FULLY_BOUND_STRUCTS, &matches::SINGLE_MATCH, @@ -1065,6 +1061,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems); store.register_early_pass(|| box manual_non_exhaustive::ManualNonExhaustive); store.register_late_pass(|| box manual_async_fn::ManualAsyncFn); + store.register_early_pass(|| box redundant_field_names::RedundantFieldNames); + let single_char_binding_names_threshold = conf.single_char_binding_names_threshold; + store.register_early_pass(move || box non_expressive_names::NonExpressiveNames { + single_char_binding_names_threshold, + }); store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(&arithmetic::FLOAT_ARITHMETIC), @@ -1139,6 +1140,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(¯o_use::MACRO_USE_IMPORTS), LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS), LintId::of(&matches::MATCH_BOOL), + LintId::of(&matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS), + LintId::of(&matches::MATCH_WILD_ERR_ARM), LintId::of(&matches::SINGLE_MATCH_ELSE), LintId::of(&methods::FILTER_MAP), LintId::of(&methods::FILTER_MAP_NEXT), @@ -1283,7 +1286,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&matches::MATCH_OVERLAPPING_ARM), LintId::of(&matches::MATCH_REF_PATS), LintId::of(&matches::MATCH_SINGLE_BINDING), - LintId::of(&matches::MATCH_WILD_ERR_ARM), LintId::of(&matches::SINGLE_MATCH), LintId::of(&matches::WILDCARD_IN_OR_PATTERNS), LintId::of(&mem_discriminant::MEM_DISCRIMINANT_NON_ENUM), @@ -1474,7 +1476,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH), LintId::of(&matches::MATCH_OVERLAPPING_ARM), LintId::of(&matches::MATCH_REF_PATS), - LintId::of(&matches::MATCH_WILD_ERR_ARM), LintId::of(&matches::SINGLE_MATCH), LintId::of(&mem_replace::MEM_REPLACE_OPTION_WITH_NONE), LintId::of(&mem_replace::MEM_REPLACE_WITH_DEFAULT), diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index bbf14374a1f7f..94380acfcfd4c 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -168,7 +168,7 @@ declare_clippy_lint! { /// **What it does:** Checks for arm which matches all errors with `Err(_)` /// and take drastic actions like `panic!`. /// - /// **Why is this bad?** It is generally a bad practice, just like + /// **Why is this bad?** It is generally a bad practice, similar to /// catching all exceptions in java with `catch(Exception)` /// /// **Known problems:** None. @@ -182,7 +182,7 @@ declare_clippy_lint! { /// } /// ``` pub MATCH_WILD_ERR_ARM, - style, + pedantic, "a `match` with `Err(_)` arm and take drastic actions" } @@ -220,7 +220,7 @@ declare_clippy_lint! { /// # enum Foo { A(usize), B(usize) } /// # let x = Foo::B(1); /// match x { - /// A => {}, + /// Foo::A(_) => {}, /// _ => {}, /// } /// ``` @@ -229,6 +229,40 @@ declare_clippy_lint! { "a wildcard enum match arm using `_`" } +declare_clippy_lint! { + /// **What it does:** Checks for wildcard enum matches for a single variant. + /// + /// **Why is this bad?** New enum variants added by library updates can be missed. + /// + /// **Known problems:** Suggested replacements may not use correct path to enum + /// if it's not present in the current scope. + /// + /// **Example:** + /// + /// ```rust + /// # enum Foo { A, B, C } + /// # let x = Foo::B; + /// match x { + /// Foo::A => {}, + /// Foo::B => {}, + /// _ => {}, + /// } + /// ``` + /// Use instead: + /// ```rust + /// # enum Foo { A, B, C } + /// # let x = Foo::B; + /// match x { + /// Foo::A => {}, + /// Foo::B => {}, + /// Foo::C => {}, + /// } + /// ``` + pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + pedantic, + "a wildcard enum match for a single variant" +} + declare_clippy_lint! { /// **What it does:** Checks for wildcard pattern used with others patterns in same match arm. /// @@ -356,6 +390,7 @@ impl_lint_pass!(Matches => [ MATCH_WILD_ERR_ARM, MATCH_AS_REF, WILDCARD_ENUM_MATCH_ARM, + MATCH_WILDCARD_FOR_SINGLE_VARIANTS, WILDCARD_IN_OR_PATTERNS, MATCH_SINGLE_BINDING, INFALLIBLE_DESTRUCTURING_MATCH, @@ -676,7 +711,7 @@ fn check_wild_err_arm(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_>]) arm.pat.span, &format!("`Err({})` matches all errors", &ident_bind_name), None, - "match each error separately or use the error output", + "match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable", ); } } @@ -729,9 +764,21 @@ fn check_wild_enum_match(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_ if let QPath::Resolved(_, p) = path { missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id())); } - } else if let PatKind::TupleStruct(ref path, ..) = arm.pat.kind { + } else if let PatKind::TupleStruct(ref path, ref patterns, ..) = arm.pat.kind { if let QPath::Resolved(_, p) = path { - missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id())); + // Some simple checks for exhaustive patterns. + // There is a room for improvements to detect more cases, + // but it can be more expensive to do so. + let is_pattern_exhaustive = |pat: &&Pat<'_>| { + if let PatKind::Wild | PatKind::Binding(.., None) = pat.kind { + true + } else { + false + } + }; + if patterns.iter().all(is_pattern_exhaustive) { + missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id())); + } } } } @@ -766,6 +813,19 @@ fn check_wild_enum_match(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_ } } + if suggestion.len() == 1 { + // No need to check for non-exhaustive enum as in that case len would be greater than 1 + span_lint_and_sugg( + cx, + MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + wildcard_span, + message, + "try this", + suggestion[0].clone(), + Applicability::MaybeIncorrect, + ) + }; + span_lint_and_sugg( cx, WILDCARD_ENUM_MATCH_ARM, @@ -773,7 +833,7 @@ fn check_wild_enum_match(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[Arm<'_ message, "try this", suggestion.join(" | "), - Applicability::MachineApplicable, + Applicability::MaybeIncorrect, ) } } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 810a226b50d2a..52ca962e7ef97 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -1496,17 +1496,14 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods { if let ty::Opaque(def_id, _) = ret_ty.kind { // one of the associated types must be Self for predicate in cx.tcx.predicates_of(def_id).predicates { - match predicate.0.kind() { - ty::PredicateKind::Projection(poly_projection_predicate) => { - let binder = poly_projection_predicate.ty(); - let associated_type = binder.skip_binder(); - - // walk the associated type and check for Self - if contains_self_ty(associated_type) { - return; - } - }, - _ => {}, + if let ty::PredicateKind::Projection(poly_projection_predicate) = predicate.0.kind() { + let binder = poly_projection_predicate.ty(); + let associated_type = binder.skip_binder(); + + // walk the associated type and check for Self + if contains_self_ty(associated_type) { + return; + } } } } @@ -1617,6 +1614,21 @@ fn lint_or_fun_call<'a, 'tcx>( or_has_args: bool, span: Span, ) { + if let hir::ExprKind::MethodCall(ref path, _, ref args) = &arg.kind { + if path.ident.as_str() == "len" { + let ty = walk_ptrs_ty(cx.tables.expr_ty(&args[0])); + + match ty.kind { + ty::Slice(_) | ty::Array(_, _) => return, + _ => (), + } + + if match_type(cx, ty, &paths::VEC) { + return; + } + } + } + // (path, fn_has_argument, methods, suffix) let know_types: &[(&[_], _, &[_], _)] = &[ (&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"), diff --git a/clippy_lints/src/misc_early.rs b/clippy_lints/src/misc_early.rs index 62ee051624b48..552222eba2ee2 100644 --- a/clippy_lints/src/misc_early.rs +++ b/clippy_lints/src/misc_early.rs @@ -379,7 +379,7 @@ impl EarlyLintPass for MiscEarlyLints { let left_binding = match left { BindingMode::ByRef(Mutability::Mut) => "ref mut ", BindingMode::ByRef(Mutability::Not) => "ref ", - _ => "", + BindingMode::ByValue(..) => "", }; if let PatKind::Wild = right.kind { diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index 4301157e16440..9cfc8d1913497 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -113,7 +113,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MissingConstForFn { return; } }, - _ => return, + FnKind::Closure(..) => return, } let mir = cx.tcx.optimized_mir(def_id); diff --git a/clippy_lints/src/multiple_crate_versions.rs b/clippy_lints/src/multiple_crate_versions.rs index ed85d0315bd25..b6770804e1801 100644 --- a/clippy_lints/src/multiple_crate_versions.rs +++ b/clippy_lints/src/multiple_crate_versions.rs @@ -1,11 +1,14 @@ //! lint on multiple versions of a crate being used use crate::utils::{run_lints, span_lint}; +use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::{Crate, CRATE_HIR_ID}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::DUMMY_SP; +use cargo_metadata::{DependencyKind, MetadataCommand, Node, Package, PackageId}; +use if_chain::if_chain; use itertools::Itertools; declare_clippy_lint! { @@ -39,30 +42,61 @@ impl LateLintPass<'_, '_> for MultipleCrateVersions { return; } - let metadata = if let Ok(metadata) = cargo_metadata::MetadataCommand::new().exec() { + let metadata = if let Ok(metadata) = MetadataCommand::new().exec() { metadata } else { span_lint(cx, MULTIPLE_CRATE_VERSIONS, DUMMY_SP, "could not read cargo metadata"); - return; }; + let local_name = cx.tcx.crate_name(LOCAL_CRATE).as_str(); let mut packages = metadata.packages; packages.sort_by(|a, b| a.name.cmp(&b.name)); - for (name, group) in &packages.into_iter().group_by(|p| p.name.clone()) { - let group: Vec = group.collect(); + if_chain! { + if let Some(resolve) = &metadata.resolve; + if let Some(local_id) = packages + .iter() + .find_map(|p| if p.name == *local_name { Some(&p.id) } else { None }); + then { + for (name, group) in &packages.iter().group_by(|p| p.name.clone()) { + let group: Vec<&Package> = group.collect(); + + if group.len() <= 1 { + continue; + } - if group.len() > 1 { - let versions = group.into_iter().map(|p| p.version).join(", "); + if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) { + let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect(); + versions.sort(); + let versions = versions.iter().join(", "); - span_lint( - cx, - MULTIPLE_CRATE_VERSIONS, - DUMMY_SP, - &format!("multiple versions for dependency `{}`: {}", name, versions), - ); + span_lint( + cx, + MULTIPLE_CRATE_VERSIONS, + DUMMY_SP, + &format!("multiple versions for dependency `{}`: {}", name, versions), + ); + } + } } } } } + +fn is_normal_dep(nodes: &[Node], local_id: &PackageId, dep_id: &PackageId) -> bool { + fn depends_on(node: &Node, dep_id: &PackageId) -> bool { + node.deps.iter().any(|dep| { + dep.pkg == *dep_id + && dep + .dep_kinds + .iter() + .any(|info| matches!(info.kind, DependencyKind::Normal)) + }) + } + + nodes + .iter() + .filter(|node| depends_on(node, dep_id)) + .any(|node| node.id == *local_id || is_normal_dep(nodes, local_id, &node.id)) +} diff --git a/clippy_lints/src/mut_reference.rs b/clippy_lints/src/mut_reference.rs index e5680482e5bfb..67a1ac78a6777 100644 --- a/clippy_lints/src/mut_reference.rs +++ b/clippy_lints/src/mut_reference.rs @@ -6,7 +6,7 @@ use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { - /// **What it does:** Detects giving a mutable reference to a function that only + /// **What it does:** Detects passing a mutable reference to a function that only /// requires an immutable reference. /// /// **Why is this bad?** The immutable reference rules out all other references diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 1621c7f947c33..218b0d27f7486 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -86,7 +86,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue { } }, FnKind::Method(..) => (), - _ => return, + FnKind::Closure(..) => return, } // Exclude non-inherent impls diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index a599667b8d8a8..e556e5d59c188 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -1,27 +1,20 @@ use crate::utils::paths; use crate::utils::sugg::DiagnosticBuilderExt; -use crate::utils::{get_trait_def_id, implements_trait, return_ty, same_tys, span_lint_hir_and_then}; +use crate::utils::{get_trait_def_id, return_ty, same_tys, span_lint_hir_and_then}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::def_id::DefId; use rustc_hir::HirIdSet; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::Ty; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::source_map::Span; declare_clippy_lint! { /// **What it does:** Checks for types with a `fn new() -> Self` method and no /// implementation of /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html). /// - /// It detects both the case when a manual - /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) - /// implementation is required and also when it can be created with - /// `#[derive(Default)]` - /// /// **Why is this bad?** The user might expect to be able to use /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) as the /// type can be constructed without arguments. @@ -40,46 +33,17 @@ declare_clippy_lint! { /// } /// ``` /// - /// Instead, use: + /// To fix the lint, and a `Default` implementation that delegates to `new`: /// /// ```ignore /// struct Foo(Bar); /// /// impl Default for Foo { /// fn default() -> Self { - /// Foo(Bar::new()) + /// Foo::new() /// } /// } /// ``` - /// - /// Or, if - /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) - /// can be derived by `#[derive(Default)]`: - /// - /// ```rust - /// struct Foo; - /// - /// impl Foo { - /// fn new() -> Self { - /// Foo - /// } - /// } - /// ``` - /// - /// Instead, use: - /// - /// ```rust - /// #[derive(Default)] - /// struct Foo; - /// - /// impl Foo { - /// fn new() -> Self { - /// Foo - /// } - /// } - /// ``` - /// - /// You can also have `new()` call `Default::default()`. pub NEW_WITHOUT_DEFAULT, style, "`fn new() -> Self` method without `Default` implementation" @@ -126,8 +90,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NewWithoutDefault { return; } if sig.decl.inputs.is_empty() && name == sym!(new) && cx.access_levels.is_reachable(id) { - let self_did = cx.tcx.hir().local_def_id(cx.tcx.hir().get_parent_item(id)); - let self_ty = cx.tcx.type_of(self_did); + let self_def_id = cx.tcx.hir().local_def_id(cx.tcx.hir().get_parent_item(id)); + let self_ty = cx.tcx.type_of(self_def_id); if_chain! { if same_tys(cx, self_ty, return_ty(cx, id)); if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT); @@ -148,56 +112,35 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NewWithoutDefault { // generics if_chain! { if let Some(ref impling_types) = self.impling_types; - if let Some(self_def) = cx.tcx.type_of(self_did).ty_adt_def(); - if let Some(self_def_id) = self_def.did.as_local(); + if let Some(self_def) = cx.tcx.type_of(self_def_id).ty_adt_def(); + if let Some(self_local_did) = self_def.did.as_local(); then { - let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_def_id); + let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did); if impling_types.contains(&self_id) { return; } } } - if let Some(sp) = can_derive_default(self_ty, cx, default_trait_id) { - span_lint_hir_and_then( - cx, - NEW_WITHOUT_DEFAULT, - id, - impl_item.span, - &format!( - "you should consider deriving a `Default` implementation for `{}`", - self_ty - ), - |diag| { - diag.suggest_item_with_attr( - cx, - sp, - "try this", - "#[derive(Default)]", - Applicability::MaybeIncorrect, - ); - }); - } else { - span_lint_hir_and_then( - cx, - NEW_WITHOUT_DEFAULT, - id, - impl_item.span, - &format!( - "you should consider adding a `Default` implementation for `{}`", - self_ty - ), - |diag| { - diag.suggest_prepend_item( - cx, - item.span, - "try this", - &create_new_without_default_suggest_msg(self_ty), - Applicability::MaybeIncorrect, - ); - }, - ); - } + span_lint_hir_and_then( + cx, + NEW_WITHOUT_DEFAULT, + id, + impl_item.span, + &format!( + "you should consider adding a `Default` implementation for `{}`", + self_ty + ), + |diag| { + diag.suggest_prepend_item( + cx, + item.span, + "try this", + &create_new_without_default_suggest_msg(self_ty), + Applicability::MaybeIncorrect, + ); + }, + ); } } } @@ -217,18 +160,3 @@ fn create_new_without_default_suggest_msg(ty: Ty<'_>) -> String { }} }}", ty) } - -fn can_derive_default<'t, 'c>(ty: Ty<'t>, cx: &LateContext<'c, 't>, default_trait_id: DefId) -> Option { - match ty.kind { - ty::Adt(adt_def, substs) if adt_def.is_struct() => { - for field in adt_def.all_fields() { - let f_ty = field.ty(cx.tcx, substs); - if !implements_trait(cx, f_ty, default_trait_id, &[]) { - return None; - } - } - Some(cx.tcx.def_span(adt_def.did)) - }, - _ => None, - } -} diff --git a/clippy_lints/src/non_expressive_names.rs b/clippy_lints/src/non_expressive_names.rs index 2b51b73207585..5f14fe97afefa 100644 --- a/clippy_lints/src/non_expressive_names.rs +++ b/clippy_lints/src/non_expressive_names.rs @@ -5,6 +5,7 @@ use rustc_ast::ast::{ use rustc_ast::attr; use rustc_ast::visit::{walk_block, walk_expr, walk_pat, Visitor}; use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_middle::lint::in_external_macro; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::Span; use rustc_span::symbol::{Ident, SymbolStr}; @@ -131,7 +132,11 @@ struct SimilarNamesNameVisitor<'a, 'tcx, 'b>(&'b mut SimilarNamesLocalVisitor<'a impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> { fn visit_pat(&mut self, pat: &'tcx Pat) { match pat.kind { - PatKind::Ident(_, ident, _) => self.check_ident(ident), + PatKind::Ident(_, ident, _) => { + if !pat.span.from_expansion() { + self.check_ident(ident); + } + }, PatKind::Struct(_, ref fields, _) => { for field in fields { if !field.is_shorthand { @@ -354,12 +359,20 @@ impl<'a, 'tcx> Visitor<'tcx> for SimilarNamesLocalVisitor<'a, 'tcx> { impl EarlyLintPass for NonExpressiveNames { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if in_external_macro(cx.sess, item.span) { + return; + } + if let ItemKind::Fn(_, ref sig, _, Some(ref blk)) = item.kind { do_check(self, cx, &item.attrs, &sig.decl, blk); } } fn check_impl_item(&mut self, cx: &EarlyContext<'_>, item: &AssocItem) { + if in_external_macro(cx.sess, item.span) { + return; + } + if let AssocItemKind::Fn(_, ref sig, _, Some(ref blk)) = item.kind { do_check(self, cx, &item.attrs, &sig.decl, blk); } diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 2cdf96714195a..4eac571f96620 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -2,7 +2,7 @@ use crate::utils::ptr::get_spans; use crate::utils::{ - is_type_diagnostic_item, match_qpath, match_type, paths, snippet_opt, span_lint, span_lint_and_sugg, + is_allowed, is_type_diagnostic_item, match_qpath, match_type, paths, snippet_opt, span_lint, span_lint_and_sugg, span_lint_and_then, walk_ptrs_hir_ty, }; use if_chain::if_chain; @@ -150,8 +150,16 @@ fn check_fn(cx: &LateContext<'_, '_>, decl: &FnDecl<'_>, fn_id: HirId, opt_body_ let fn_def_id = cx.tcx.hir().local_def_id(fn_id); let sig = cx.tcx.fn_sig(fn_def_id); let fn_ty = sig.skip_binder(); + let body = opt_body_id.map(|id| cx.tcx.hir().body(id)); for (idx, (arg, ty)) in decl.inputs.iter().zip(fn_ty.inputs()).enumerate() { + // Honor the allow attribute on parameters. See issue 5644. + if let Some(body) = &body { + if is_allowed(cx, PTR_ARG, body.params[idx].hir_id) { + continue; + } + } + if let ty::Ref(_, ty, Mutability::Not) = ty.kind { if is_type_diagnostic_item(cx, ty, sym!(vec_type)) { let mut ty_snippet = None; diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 83c6faac04149..1eb26d97ed4d2 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -241,14 +241,14 @@ fn check_inclusive_range_minus_one(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { } fn check_reversed_empty_range(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { - fn inside_indexing_expr(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { - matches!( - get_parent_expr(cx, expr), - Some(Expr { + fn inside_indexing_expr<'a>(cx: &'a LateContext<'_, '_>, expr: &Expr<'_>) -> Option<&'a Expr<'a>> { + match get_parent_expr(cx, expr) { + parent_expr @ Some(Expr { kind: ExprKind::Index(..), .. - }) - ) + }) => parent_expr, + _ => None, + } } fn is_empty_range(limits: RangeLimits, ordering: Ordering) -> bool { @@ -267,18 +267,32 @@ fn check_reversed_empty_range(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { if let Some(ordering) = Constant::partial_cmp(cx.tcx, ty, &start_idx, &end_idx); if is_empty_range(limits, ordering); then { - if inside_indexing_expr(cx, expr) { + if let Some(parent_expr) = inside_indexing_expr(cx, expr) { let (reason, outcome) = if ordering == Ordering::Equal { ("empty", "always yield an empty slice") } else { ("reversed", "panic at run-time") }; - span_lint( + span_lint_and_then( cx, REVERSED_EMPTY_RANGES, expr.span, &format!("this range is {} and using it to index a slice will {}", reason, outcome), + |diag| { + if_chain! { + if ordering == Ordering::Equal; + if let ty::Slice(slice_ty) = cx.tables.expr_ty(parent_expr).kind; + then { + diag.span_suggestion( + parent_expr.span, + "if you want an empty slice, use", + format!("[] as &[{}]", slice_ty), + Applicability::MaybeIncorrect + ); + } + } + } ); } else { span_lint_and_then( diff --git a/clippy_lints/src/redundant_field_names.rs b/clippy_lints/src/redundant_field_names.rs index b12c3c344ef4c..2a81170e49e75 100644 --- a/clippy_lints/src/redundant_field_names.rs +++ b/clippy_lints/src/redundant_field_names.rs @@ -2,6 +2,7 @@ use crate::utils::span_lint_and_sugg; use rustc_ast::ast::{Expr, ExprKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { @@ -36,6 +37,9 @@ declare_lint_pass!(RedundantFieldNames => [REDUNDANT_FIELD_NAMES]); impl EarlyLintPass for RedundantFieldNames { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if in_external_macro(cx.sess, expr.span) { + return; + } if let ExprKind::Struct(_, ref fields, _) = expr.kind { for field in fields { if field.is_shorthand { diff --git a/clippy_lints/src/trivially_copy_pass_by_ref.rs b/clippy_lints/src/trivially_copy_pass_by_ref.rs index 2c101220c5d68..8e0cb94317aff 100644 --- a/clippy_lints/src/trivially_copy_pass_by_ref.rs +++ b/clippy_lints/src/trivially_copy_pass_by_ref.rs @@ -161,7 +161,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TriviallyCopyPassByRef { } }, FnKind::Method(..) => (), - _ => return, + FnKind::Closure(..) => return, } // Exclude non-inherent impls diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 95921518986bc..7fa97b2469912 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -1,14 +1,17 @@ use crate::utils::{ - match_def_path, match_trait_method, paths, same_tys, snippet, snippet_with_macro_callsite, span_lint_and_sugg, + is_type_diagnostic_item, match_def_path, match_trait_method, paths, same_tys, snippet, snippet_with_macro_callsite, + span_lint_and_help, span_lint_and_sugg, }; +use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, HirId, MatchSource}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; use rustc_session::{declare_tool_lint, impl_lint_pass}; declare_clippy_lint! { - /// **What it does:** Checks for `Into`/`From`/`IntoIter` calls that useless converts - /// to the same type as caller. + /// **What it does:** Checks for `Into`, `TryInto`, `From`, `TryFrom`,`IntoIter` calls + /// that useless converts to the same type as caller. /// /// **Why is this bad?** Redundant code. /// @@ -26,7 +29,7 @@ declare_clippy_lint! { /// ``` pub USELESS_CONVERSION, complexity, - "calls to `Into`/`From`/`IntoIter` that performs useless conversions to the same type" + "calls to `Into`, `TryInto`, `From`, `TryFrom`, `IntoIter` that performs useless conversions to the same type" } #[derive(Default)] @@ -36,6 +39,7 @@ pub struct UselessConversion { impl_lint_pass!(UselessConversion => [USELESS_CONVERSION]); +#[allow(clippy::too_many_lines)] impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr<'_>) { if e.span.from_expansion() { @@ -63,12 +67,11 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { let b = cx.tables.expr_ty(&args[0]); if same_tys(cx, a, b) { let sugg = snippet_with_macro_callsite(cx, args[0].span, "").to_string(); - span_lint_and_sugg( cx, USELESS_CONVERSION, e.span, - "useless conversion", + "useless conversion to the same type", "consider removing `.into()`", sugg, Applicability::MachineApplicable, // snippet @@ -84,22 +87,70 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { cx, USELESS_CONVERSION, e.span, - "useless conversion", + "useless conversion to the same type", "consider removing `.into_iter()`", sugg, Applicability::MachineApplicable, // snippet ); } } + if match_trait_method(cx, e, &paths::TRY_INTO_TRAIT) && &*name.ident.as_str() == "try_into" { + if_chain! { + let a = cx.tables.expr_ty(e); + let b = cx.tables.expr_ty(&args[0]); + if is_type_diagnostic_item(cx, a, sym!(result_type)); + if let ty::Adt(_, substs) = a.kind; + if let Some(a_type) = substs.types().next(); + if same_tys(cx, a_type, b); + + then { + span_lint_and_help( + cx, + USELESS_CONVERSION, + e.span, + "useless conversion to the same type", + None, + "consider removing `.try_into()`", + ); + } + } + } }, ExprKind::Call(ref path, ref args) => { - if let ExprKind::Path(ref qpath) = path.kind { - if let Some(def_id) = cx.tables.qpath_res(qpath, path.hir_id).opt_def_id() { - if match_def_path(cx, def_id, &paths::FROM_FROM) { - let a = cx.tables.expr_ty(e); - let b = cx.tables.expr_ty(&args[0]); - if same_tys(cx, a, b) { + if_chain! { + if args.len() == 1; + if let ExprKind::Path(ref qpath) = path.kind; + if let Some(def_id) = cx.tables.qpath_res(qpath, path.hir_id).opt_def_id(); + let a = cx.tables.expr_ty(e); + let b = cx.tables.expr_ty(&args[0]); + + then { + if_chain! { + if match_def_path(cx, def_id, &paths::TRY_FROM); + if is_type_diagnostic_item(cx, a, sym!(result_type)); + if let ty::Adt(_, substs) = a.kind; + if let Some(a_type) = substs.types().next(); + if same_tys(cx, a_type, b); + + then { + let hint = format!("consider removing `{}()`", snippet(cx, path.span, "TryFrom::try_from")); + span_lint_and_help( + cx, + USELESS_CONVERSION, + e.span, + "useless conversion to the same type", + None, + &hint, + ); + } + } + + if_chain! { + if match_def_path(cx, def_id, &paths::FROM_FROM); + if same_tys(cx, a, b); + + then { let sugg = snippet(cx, args[0].span.source_callsite(), "").into_owned(); let sugg_msg = format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); @@ -107,7 +158,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { cx, USELESS_CONVERSION, e.span, - "useless conversion", + "useless conversion to the same type", &sugg_msg, sugg, Applicability::MachineApplicable, // snippet diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 57b9eafd14dbd..9e8e0ff30ec6b 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -120,10 +120,12 @@ define_Conf! { "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", - "JavaScript", + "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "NaN", "NaNs", "OAuth", - "OpenGL", "OpenSSH", "OpenSSL", "OpenStreetMap", + "OCaml", + "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", + "TensorFlow", "TrueType", "iOS", "macOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", diff --git a/clippy_lints/src/utils/inspector.rs b/clippy_lints/src/utils/inspector.rs index 748c11fac64ff..9b672b9ec225b 100644 --- a/clippy_lints/src/utils/inspector.rs +++ b/clippy_lints/src/utils/inspector.rs @@ -289,21 +289,21 @@ fn print_expr(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, indent: usize) { println!("{}operands:", ind); for op in asm.operands { match op { - hir::InlineAsmOperand::In { expr, .. } => print_expr(cx, expr, indent + 1), + hir::InlineAsmOperand::In { expr, .. } + | hir::InlineAsmOperand::InOut { expr, .. } + | hir::InlineAsmOperand::Const { expr } + | hir::InlineAsmOperand::Sym { expr } => print_expr(cx, expr, indent + 1), hir::InlineAsmOperand::Out { expr, .. } => { if let Some(expr) = expr { print_expr(cx, expr, indent + 1); } }, - hir::InlineAsmOperand::InOut { expr, .. } => print_expr(cx, expr, indent + 1), hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { print_expr(cx, in_expr, indent + 1); if let Some(out_expr) = out_expr { print_expr(cx, out_expr, indent + 1); } }, - hir::InlineAsmOperand::Const { expr } => print_expr(cx, expr, indent + 1), - hir::InlineAsmOperand::Sym { expr } => print_expr(cx, expr, indent + 1), } } }, diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index f22473275c466..6c1488664bf00 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -358,7 +358,7 @@ pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'_, 'tcx>, hir_id: HirId) -> O pub fn has_drop<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'tcx>) -> bool { match ty.ty_adt_def() { Some(def) => def.has_dtor(cx.tcx), - _ => false, + None => false, } } diff --git a/clippy_lints/src/utils/paths.rs b/clippy_lints/src/utils/paths.rs index b3ad2ad9d9987..779da7e6bf23c 100644 --- a/clippy_lints/src/utils/paths.rs +++ b/clippy_lints/src/utils/paths.rs @@ -128,8 +128,10 @@ pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned" pub const TO_STRING: [&str; 3] = ["alloc", "string", "ToString"]; pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"]; pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"]; +pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"]; pub const TRY_FROM_ERROR: [&str; 4] = ["std", "ops", "Try", "from_error"]; pub const TRY_INTO_RESULT: [&str; 4] = ["std", "ops", "Try", "into_result"]; +pub const TRY_INTO_TRAIT: [&str; 3] = ["core", "convert", "TryInto"]; pub const VEC: [&str; 3] = ["alloc", "vec", "Vec"]; 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"]; diff --git a/clippy_lints/src/utils/sugg.rs b/clippy_lints/src/utils/sugg.rs index 4ebe2e2852fb4..73758b7eeb7eb 100644 --- a/clippy_lints/src/utils/sugg.rs +++ b/clippy_lints/src/utils/sugg.rs @@ -530,7 +530,7 @@ pub trait DiagnosticBuilderExt<'a, T: LintContext> { /// Suggest to add an item before another. /// - /// The item should not be indented (expect for inner indentation). + /// The item should not be indented (except for inner indentation). /// /// # Example /// diff --git a/doc/adding_lints.md b/doc/adding_lints.md index 9ad1315c17521..8092be277cca0 100644 --- a/doc/adding_lints.md +++ b/doc/adding_lints.md @@ -42,8 +42,10 @@ case), and we don't need type information so it will have an early pass type `cargo dev new_lint --name=foo_functions --pass=early --category=pedantic` (category will default to nursery if not provided). This command will create two files: `tests/ui/foo_functions.rs` and `clippy_lints/src/foo_functions.rs`, -as well as run `cargo dev update_lints` to register the new lint. Next, we'll -open up these files and add our lint! +as well as run `cargo dev update_lints` to register the new lint. For cargo lints, +two project hierarchies (fail/pass) will be created by default under `tests/ui-cargo`. + +Next, we'll open up these files and add our lint! ## Testing @@ -105,6 +107,24 @@ our lint, we need to commit the generated `.stderr` files, too. In general, you should only commit files changed by `tests/ui/update-all-references.sh` for the specific lint you are creating/editing. +### Cargo lints + +For cargo lints, the process of testing differs in that we are interested in +the `Cargo.toml` manifest file. We also need a minimal crate associated +with that manifest. + +If our new lint is named e.g. `foo_categories`, after running `cargo dev new_lint` +we will find by default two new crates, each with its manifest file: + +* `tests/ui-cargo/foo_categories/fail/Cargo.toml`: this file should cause the new lint to raise an error. +* `tests/ui-cargo/foo_categories/pass/Cargo.toml`: this file should not trigger the lint. + +If you need more cases, you can copy one of those crates (under `foo_categories`) and rename it. + +The process of generating the `.stderr` file is the same, and prepending the `TESTNAME` +variable to `cargo uitest` works too, but the script to update the references +is in another path: `tests/ui-cargo/update-all-references.sh`. + ## Rustfix tests If the lint you are working on is making use of structured suggestions, the @@ -445,6 +465,7 @@ Here are some pointers to things you are likely going to need for every lint: * [`from_expansion`][from_expansion] and [`in_external_macro`][in_external_macro] * [`Span`][span] * [`Applicability`][applicability] +* [Common tools for writing lints](common_tools_writing_lints.md) helps with common operations * [The rustc-dev-guide][rustc-dev-guide] explains a lot of internal compiler concepts * [The nightly rustc docs][nightly_docs] which has been linked to throughout this guide diff --git a/doc/common_tools_writing_lints.md b/doc/common_tools_writing_lints.md new file mode 100644 index 0000000000000..ed33b37c6bd1b --- /dev/null +++ b/doc/common_tools_writing_lints.md @@ -0,0 +1,152 @@ +# Common tools for writing lints + +You may need following tooltips to catch up with common operations. + +- [Common tools for writing lints](#common-tools-for-writing-lints) + - [Retrieving the type of an expression](#retrieving-the-type-of-an-expression) + - [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait) + - [Dealing with macros](#dealing-with-macros) + +Useful Rustc dev guide links: +- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation) +- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html) +- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html) + +# Retrieving the type of an expression + +Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for example to answer following questions: + +- which type does this expression correspond to (using its [`TyKind`][TyKind])? +- is it a sized type? +- is it a primitive type? +- does it implement a trait? + +This operation is performed using the [`expr_ty()`][expr_ty] method from the [`TypeckTables`][TypeckTables] struct, +that gives you access to the underlying structure [`TyS`][TyS]. + +Example of use: +```rust +impl LateLintPass<'_, '_> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + // Get type of `expr` + let ty = cx.tables.expr_ty(expr); + // Match its kind to enter its type + match ty.kind { + ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"), + _ => () + } + } +} +``` + +Similarly in [`TypeckTables`][TypeckTables] methods, you have the [`pat_ty()`][pat_ty] method +to retrieve a type from a pattern. + +Two noticeable items here: +- `cx` is the lint context [`LateContext`][LateContext]. + The two most useful data structures in this context are `tcx` and `tables`, + allowing us to jump to type definitions and other compilation stages such as HIR. +- `tables` is [`TypeckTables`][TypeckTables] and is created by type checking step, + it includes useful information such as types of expressions, ways to resolve methods and so on. + +# Checking if a type implements a specific trait + +There are two ways to do this, depending if the target trait is part of lang items. + +```rust +use crate::utils::{implements_trait, match_trait_method, paths}; + +impl LateLintPass<'_, '_> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + // 1. Using expression and Clippy's convenient method + // we use `match_trait_method` function from Clippy's toolbox + if match_trait_method(cx, expr, &paths::INTO) { + // `expr` implements `Into` trait + } + + // 2. Using type context `TyCtxt` + let ty = cx.tables.expr_ty(expr); + if cx.tcx.lang_items() + // we are looking for the `DefId` of `Drop` trait in lang items + .drop_trait() + // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils + .map_or(false, |id| implements_trait(cx, ty, id, &[])) { + // `expr` implements `Drop` trait + } + } +} +``` + +> Prefer using lang items, if the target trait is available there. + +A list of defined paths for Clippy can be found in [paths.rs][paths] + +We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate. + +# Dealing with macros + +There are several helpers in Clippy's utils to deal with macros: + +- `in_macro()`: detect if the given span is expanded by a macro + +You may want to use this for example to not start linting in any macro. + +```rust +macro_rules! foo { + ($param:expr) => { + match $param { + "bar" => println!("whatever"), + _ => () + } + }; +} + +foo!("bar"); + +// if we lint the `match` of `foo` call and test its span +assert_eq!(in_macro(match_span), true); +``` + +- `in_external_macro()`: detect if the given span is from an external macro, defined in a foreign crate + +You may want to use it for example to not start linting in macros from other crates + +```rust +#[macro_use] +extern crate a_crate_with_macros; + +// `foo` is defined in `a_crate_with_macros` +foo!("bar"); + +// if we lint the `match` of `foo` call and test its span +assert_eq!(in_external_macro(cx.sess(), match_span), true); +``` + +- `differing_macro_contexts()`: returns true if the two given spans are not from the same context + +```rust +macro_rules! m { + ($a:expr, $b:expr) => { + if $a.is_some() { + $b; + } + } +} + +let x: Option = Some(42); +m!(x, x.unwrap()); + +// These spans are not from the same context +// x.is_some() is from inside the macro +// x.unwrap() is from outside the macro +assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true); +``` + +[TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.html +[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html +[TypeckTables]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckTables.html +[expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckTables.html#method.expr_ty +[LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html +[TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html +[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckTables.html#method.pat_ty +[paths]: ../clippy_lints/src/utils/paths.rs diff --git a/rustc_tools_util/LICENSE-APACHE b/rustc_tools_util/LICENSE-APACHE new file mode 120000 index 0000000000000..965b606f331b5 --- /dev/null +++ b/rustc_tools_util/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/rustc_tools_util/LICENSE-MIT b/rustc_tools_util/LICENSE-MIT new file mode 120000 index 0000000000000..76219eb72e852 --- /dev/null +++ b/rustc_tools_util/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/setup-toolchain.sh b/setup-toolchain.sh index 6038ed697f91e..191ea4315a6b5 100755 --- a/setup-toolchain.sh +++ b/setup-toolchain.sh @@ -32,5 +32,5 @@ else TOOLCHAIN=() fi -rustup-toolchain-install-master -f -n master "${TOOLCHAIN[@]}" -c rustc-dev -- "$RUST_COMMIT" +rustup-toolchain-install-master -f -n master "${TOOLCHAIN[@]}" -c rustc-dev -c llvm-tools -- "$RUST_COMMIT" rustup override set master diff --git a/src/driver.rs b/src/driver.rs index d3a7e24937f95..70c47b426825e 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -79,7 +79,7 @@ impl rustc_driver::Callbacks for ClippyCallbacks { let conf = clippy_lints::read_conf(&[], &sess); clippy_lints::register_plugins(&mut lint_store, &sess, &conf); - clippy_lints::register_pre_expansion_lints(&mut lint_store, &conf); + clippy_lints::register_pre_expansion_lints(&mut lint_store); clippy_lints::register_renamed(&mut lint_store); })); diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 9457a64f9c6b9..f63301c7db0a6 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1195,11 +1195,18 @@ pub static ref ALL_LINTS: Vec = vec![ }, Lint { name: "match_wild_err_arm", - group: "style", + group: "pedantic", desc: "a `match` with `Err(_)` arm and take drastic actions", deprecation: None, module: "matches", }, + Lint { + name: "match_wildcard_for_single_variants", + group: "pedantic", + desc: "a wildcard enum match for a single variant", + deprecation: None, + module: "matches", + }, Lint { name: "maybe_infinite_iter", group: "pedantic", @@ -2414,7 +2421,7 @@ pub static ref ALL_LINTS: Vec = vec![ Lint { name: "useless_conversion", group: "complexity", - desc: "calls to `Into`/`From`/`IntoIter` that performs useless conversions to the same type", + desc: "calls to `Into`, `TryInto`, `From`, `TryFrom`, `IntoIter` that performs useless conversions to the same type", deprecation: None, module: "useless_conversion", }, diff --git a/tests/compile-test.rs b/tests/compile-test.rs index de2cf6d7873f8..2758b9a7e7604 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -38,13 +38,13 @@ fn clippy_driver_path() -> PathBuf { // as what we manually pass to `cargo` invocation fn third_party_crates() -> String { use std::collections::HashMap; - static CRATES: &[&str] = &["serde", "serde_derive", "regex", "clippy_lints"]; + static CRATES: &[&str] = &["serde", "serde_derive", "regex", "clippy_lints", "syn", "quote"]; let dep_dir = cargo::TARGET_LIB.join("deps"); let mut crates: HashMap<&str, PathBuf> = HashMap::with_capacity(CRATES.len()); for entry in fs::read_dir(dep_dir).unwrap() { let path = match entry { Ok(entry) => entry.path(), - _ => continue, + Err(_) => continue, }; if let Some(name) = path.file_name().and_then(OsStr::to_str) { for dep in CRATES { @@ -101,54 +101,133 @@ fn run_mode(cfg: &mut compiletest::Config) { compiletest::run_tests(&cfg); } -#[allow(clippy::identity_conversion)] -fn run_ui_toml_tests(config: &compiletest::Config, mut tests: Vec) -> Result { - let mut result = true; - let opts = compiletest::test_opts(config); - for dir in fs::read_dir(&config.src_base)? { - let dir = dir?; - if !dir.file_type()?.is_dir() { - continue; - } - let dir_path = dir.path(); - set_var("CARGO_MANIFEST_DIR", &dir_path); - for file in fs::read_dir(&dir_path)? { - let file = file?; - let file_path = file.path(); - if file.file_type()?.is_dir() { +fn run_ui_toml(config: &mut compiletest::Config) { + fn run_tests(config: &compiletest::Config, mut tests: Vec) -> Result { + let mut result = true; + let opts = compiletest::test_opts(config); + for dir in fs::read_dir(&config.src_base)? { + let dir = dir?; + if !dir.file_type()?.is_dir() { continue; } - if file_path.extension() != Some(OsStr::new("rs")) { - continue; + let dir_path = dir.path(); + set_var("CARGO_MANIFEST_DIR", &dir_path); + for file in fs::read_dir(&dir_path)? { + let file = file?; + let file_path = file.path(); + if file.file_type()?.is_dir() { + continue; + } + if file_path.extension() != Some(OsStr::new("rs")) { + continue; + } + let paths = compiletest::common::TestPaths { + file: file_path, + base: config.src_base.clone(), + relative_dir: dir_path.file_name().unwrap().into(), + }; + let test_name = compiletest::make_test_name(&config, &paths); + let index = tests + .iter() + .position(|test| test.desc.name == test_name) + .expect("The test should be in there"); + result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?; } - let paths = compiletest::common::TestPaths { - file: file_path, - base: config.src_base.clone(), - relative_dir: dir_path.file_name().unwrap().into(), - }; - let test_name = compiletest::make_test_name(&config, &paths); - let index = tests - .iter() - .position(|test| test.desc.name == test_name) - .expect("The test should be in there"); - result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?; } + Ok(result) } - Ok(result) -} -fn run_ui_toml(config: &mut compiletest::Config) { config.mode = TestMode::Ui; config.src_base = Path::new("tests").join("ui-toml").canonicalize().unwrap(); let tests = compiletest::make_tests(&config); - let res = run_ui_toml_tests(&config, tests); + let res = run_tests(&config, tests); + match res { + Ok(true) => {}, + Ok(false) => panic!("Some tests failed"), + Err(e) => { + panic!("I/O failure during tests: {:?}", e); + }, + } +} + +fn run_ui_cargo(config: &mut compiletest::Config) { + fn run_tests( + config: &compiletest::Config, + filter: &Option, + mut tests: Vec, + ) -> Result { + let mut result = true; + let opts = compiletest::test_opts(config); + + for dir in fs::read_dir(&config.src_base)? { + let dir = dir?; + if !dir.file_type()?.is_dir() { + continue; + } + + // Use the filter if provided + let dir_path = dir.path(); + match &filter { + Some(name) if !dir_path.ends_with(name) => continue, + _ => {}, + } + + for case in fs::read_dir(&dir_path)? { + let case = case?; + if !case.file_type()?.is_dir() { + continue; + } + + let src_path = case.path().join("src"); + env::set_current_dir(&src_path)?; + + for file in fs::read_dir(&src_path)? { + let file = file?; + if file.file_type()?.is_dir() { + continue; + } + + // Search for the main file to avoid running a test for each file in the project + let file_path = file.path(); + match file_path.file_name().and_then(OsStr::to_str) { + Some("main.rs") => {}, + _ => continue, + } + + let paths = compiletest::common::TestPaths { + file: file_path, + base: config.src_base.clone(), + relative_dir: src_path.strip_prefix(&config.src_base).unwrap().into(), + }; + let test_name = compiletest::make_test_name(&config, &paths); + let index = tests + .iter() + .position(|test| test.desc.name == test_name) + .expect("The test should be in there"); + result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?; + } + } + } + Ok(result) + } + + config.mode = TestMode::Ui; + config.src_base = Path::new("tests").join("ui-cargo").canonicalize().unwrap(); + + let tests = compiletest::make_tests(&config); + + let current_dir = env::current_dir().unwrap(); + let filter = env::var("TESTNAME").ok(); + let res = run_tests(&config, &filter, tests); + env::set_current_dir(current_dir).unwrap(); + match res { Ok(true) => {}, Ok(false) => panic!("Some tests failed"), Err(e) => { - println!("I/O failure during tests: {:?}", e); + panic!("I/O failure during tests: {:?}", e); }, } } @@ -165,4 +244,5 @@ fn compile_test() { let mut config = default_config(); run_mode(&mut config); run_ui_toml(&mut config); + run_ui_cargo(&mut config); } diff --git a/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml b/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml new file mode 100644 index 0000000000000..c64adcf7c0131 --- /dev/null +++ b/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "cargo_common_metadata" +version = "0.1.0" +publish = false diff --git a/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs b/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs new file mode 100644 index 0000000000000..27841e18aa9ef --- /dev/null +++ b/tests/ui-cargo/cargo_common_metadata/fail/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=cargo_common_metadata +#![warn(clippy::cargo_common_metadata)] + +fn main() {} diff --git a/tests/ui-cargo/cargo_common_metadata/fail/src/main.stderr b/tests/ui-cargo/cargo_common_metadata/fail/src/main.stderr new file mode 100644 index 0000000000000..c8ae6c820df9d --- /dev/null +++ b/tests/ui-cargo/cargo_common_metadata/fail/src/main.stderr @@ -0,0 +1,18 @@ +error: package `cargo_common_metadata` is missing `package.authors` metadata + | + = note: `-D clippy::cargo-common-metadata` implied by `-D warnings` + +error: package `cargo_common_metadata` is missing `package.description` metadata + +error: package `cargo_common_metadata` is missing `either package.license or package.license_file` metadata + +error: package `cargo_common_metadata` is missing `package.repository` metadata + +error: package `cargo_common_metadata` is missing `package.readme` metadata + +error: package `cargo_common_metadata` is missing `package.keywords` metadata + +error: package `cargo_common_metadata` is missing `package.categories` metadata + +error: aborting due to 7 previous errors + diff --git a/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml b/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml new file mode 100644 index 0000000000000..c8233f328bb05 --- /dev/null +++ b/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "cargo_common_metadata" +version = "0.1.0" +publish = false +authors = ["Random person from the Internet "] +description = "A test package for the cargo_common_metadata lint" +repository = "https://github.com/someone/cargo_common_metadata" +readme = "README.md" +license = "MIT OR Apache-2.0" +keywords = ["metadata", "lint", "clippy"] +categories = ["development-tools::testing"] diff --git a/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs b/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs new file mode 100644 index 0000000000000..27841e18aa9ef --- /dev/null +++ b/tests/ui-cargo/cargo_common_metadata/pass/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=cargo_common_metadata +#![warn(clippy::cargo_common_metadata)] + +fn main() {} diff --git a/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/Cargo.toml b/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/Cargo.toml new file mode 100644 index 0000000000000..72731fbc75d00 --- /dev/null +++ b/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/Cargo.toml @@ -0,0 +1,17 @@ +# Should not lint for dev or build dependencies. See issue 5041. + +[package] +name = "multiple_crate_versions" +version = "0.1.0" +publish = false + +# One of the versions of winapi is only a dev dependency: allowed +[dependencies] +ctrlc = "=3.1.0" +[dev-dependencies] +ansi_term = "=0.11.0" + +# Both versions of winapi are a build dependency: allowed +[build-dependencies] +ctrlc = "=3.1.0" +ansi_term = "=0.11.0" diff --git a/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs b/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs new file mode 100644 index 0000000000000..1b2d3ec9459f9 --- /dev/null +++ b/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=multiple_crate_versions +#![warn(clippy::multiple_crate_versions)] + +fn main() {} diff --git a/tests/ui-cargo/multiple_crate_versions/fail/Cargo.toml b/tests/ui-cargo/multiple_crate_versions/fail/Cargo.toml new file mode 100644 index 0000000000000..3a94b723f3fdb --- /dev/null +++ b/tests/ui-cargo/multiple_crate_versions/fail/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "multiple_crate_versions" +version = "0.1.0" +publish = false + +[dependencies] +ctrlc = "=3.1.0" +ansi_term = "=0.11.0" diff --git a/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs b/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs new file mode 100644 index 0000000000000..1b2d3ec9459f9 --- /dev/null +++ b/tests/ui-cargo/multiple_crate_versions/fail/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=multiple_crate_versions +#![warn(clippy::multiple_crate_versions)] + +fn main() {} diff --git a/tests/ui-cargo/multiple_crate_versions/fail/src/main.stderr b/tests/ui-cargo/multiple_crate_versions/fail/src/main.stderr new file mode 100644 index 0000000000000..4f668599be950 --- /dev/null +++ b/tests/ui-cargo/multiple_crate_versions/fail/src/main.stderr @@ -0,0 +1,6 @@ +error: multiple versions for dependency `winapi`: 0.2.8, 0.3.8 + | + = note: `-D clippy::multiple-crate-versions` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml b/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml new file mode 100644 index 0000000000000..a9b06420b333d --- /dev/null +++ b/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "cargo_common_metadata" +version = "0.1.0" +publish = false + +[dependencies] +regex = "1.3.7" +serde = "1.0.110" diff --git a/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs b/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs new file mode 100644 index 0000000000000..1b2d3ec9459f9 --- /dev/null +++ b/tests/ui-cargo/multiple_crate_versions/pass/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=multiple_crate_versions +#![warn(clippy::multiple_crate_versions)] + +fn main() {} diff --git a/tests/ui-cargo/update-all-references.sh b/tests/ui-cargo/update-all-references.sh new file mode 100755 index 0000000000000..7028b251ea030 --- /dev/null +++ b/tests/ui-cargo/update-all-references.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# +# A script to update the references for all tests. The idea is that +# you do a run, which will generate files in the build directory +# containing the (normalized) actual output of the compiler. You then +# run this script, which will copy those files over. If you find +# yourself manually editing a foo.stderr file, you're doing it wrong. +# +# See all `update-references.sh`, if you just want to update a single test. + +if [[ "$1" == "--help" || "$1" == "-h" ]]; then + echo "usage: $0" +fi + +BUILD_DIR=$PWD/target/debug/test_build_base +MY_DIR=$(dirname "$0") +cd "$MY_DIR" || exit +find . -name '*.rs' -exec ./update-references.sh "$BUILD_DIR" {} + diff --git a/tests/ui-cargo/update-references.sh b/tests/ui-cargo/update-references.sh new file mode 100755 index 0000000000000..50d42678734e9 --- /dev/null +++ b/tests/ui-cargo/update-references.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# A script to update the references for particular tests. The idea is +# that you do a run, which will generate files in the build directory +# containing the (normalized) actual output of the compiler. This +# script will then copy that output and replace the "expected output" +# files. You can then commit the changes. +# +# If you find yourself manually editing a foo.stderr file, you're +# doing it wrong. + +if [[ "$1" == "--help" || "$1" == "-h" || "$1" == "" || "$2" == "" ]]; then + echo "usage: $0 " + echo "" + echo "For example:" + echo " $0 ../../../build/x86_64-apple-darwin/test/ui *.rs */*.rs" +fi + +MYDIR=$(dirname "$0") + +BUILD_DIR="$1" +shift + +while [[ "$1" != "" ]]; do + STDERR_NAME="${1/%.rs/.stderr}" + STDOUT_NAME="${1/%.rs/.stdout}" + shift + if [[ -f "$BUILD_DIR"/"$STDOUT_NAME" ]] && \ + ! (cmp -s -- "$BUILD_DIR"/"$STDOUT_NAME" "$MYDIR"/"$STDOUT_NAME"); then + echo updating "$MYDIR"/"$STDOUT_NAME" + cp "$BUILD_DIR"/"$STDOUT_NAME" "$MYDIR"/"$STDOUT_NAME" + fi + if [[ -f "$BUILD_DIR"/"$STDERR_NAME" ]] && \ + ! (cmp -s -- "$BUILD_DIR"/"$STDERR_NAME" "$MYDIR"/"$STDERR_NAME"); then + echo updating "$MYDIR"/"$STDERR_NAME" + cp "$BUILD_DIR"/"$STDERR_NAME" "$MYDIR"/"$STDERR_NAME" + fi +done diff --git a/tests/ui-cargo/wildcard_dependencies/fail/Cargo.toml b/tests/ui-cargo/wildcard_dependencies/fail/Cargo.toml new file mode 100644 index 0000000000000..fd2a341485683 --- /dev/null +++ b/tests/ui-cargo/wildcard_dependencies/fail/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "wildcard_dependencies" +version = "0.1.0" +publish = false + +[dependencies] +regex = "*" diff --git a/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs b/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs new file mode 100644 index 0000000000000..581babfeacbff --- /dev/null +++ b/tests/ui-cargo/wildcard_dependencies/fail/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=wildcard_dependencies +#![warn(clippy::wildcard_dependencies)] + +fn main() {} diff --git a/tests/ui-cargo/wildcard_dependencies/fail/src/main.stderr b/tests/ui-cargo/wildcard_dependencies/fail/src/main.stderr new file mode 100644 index 0000000000000..9e65d2f99420a --- /dev/null +++ b/tests/ui-cargo/wildcard_dependencies/fail/src/main.stderr @@ -0,0 +1,6 @@ +error: wildcard dependency for `regex` + | + = note: `-D clippy::wildcard-dependencies` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/tests/ui-cargo/wildcard_dependencies/pass/Cargo.toml b/tests/ui-cargo/wildcard_dependencies/pass/Cargo.toml new file mode 100644 index 0000000000000..38cb139146e05 --- /dev/null +++ b/tests/ui-cargo/wildcard_dependencies/pass/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "wildcard_dependencies" +version = "0.1.0" +publish = false + +[dependencies] +regex = "1" diff --git a/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs b/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs new file mode 100644 index 0000000000000..581babfeacbff --- /dev/null +++ b/tests/ui-cargo/wildcard_dependencies/pass/src/main.rs @@ -0,0 +1,4 @@ +// compile-flags: --crate-name=wildcard_dependencies +#![warn(clippy::wildcard_dependencies)] + +fn main() {} diff --git a/tests/ui/auxiliary/proc_macro_attr.rs b/tests/ui/auxiliary/proc_macro_attr.rs new file mode 100644 index 0000000000000..e6626d57a7722 --- /dev/null +++ b/tests/ui/auxiliary/proc_macro_attr.rs @@ -0,0 +1,37 @@ +// no-prefer-dynamic + +#![crate_type = "proc-macro"] +#![feature(repr128, proc_macro_hygiene, proc_macro_quote)] +#![allow(clippy::useless_conversion)] + +extern crate proc_macro; +extern crate quote; +extern crate syn; + +use proc_macro::TokenStream; +use quote::{quote, quote_spanned}; +use syn::parse_macro_input; +use syn::{parse_quote, ItemTrait, TraitItem}; + +#[proc_macro_attribute] +pub fn fake_async_trait(_args: TokenStream, input: TokenStream) -> TokenStream { + let mut item = parse_macro_input!(input as ItemTrait); + for inner in &mut item.items { + if let TraitItem::Method(method) = inner { + let sig = &method.sig; + let block = &mut method.default; + if let Some(block) = block { + let brace = block.brace_token; + + let my_block = quote_spanned!( brace.span => { + // Should not trigger `empty_line_after_outer_attr` + #[crate_type = "lib"] + #sig #block + Vec::new() + }); + *block = parse_quote!(#my_block); + } + } + } + TokenStream::from(quote!(#item)) +} diff --git a/tests/ui/cognitive_complexity.rs b/tests/ui/cognitive_complexity.rs index 1d3fe405521cd..912e6788afddc 100644 --- a/tests/ui/cognitive_complexity.rs +++ b/tests/ui/cognitive_complexity.rs @@ -1,6 +1,6 @@ #![allow(clippy::all)] #![warn(clippy::cognitive_complexity)] -#![allow(unused)] +#![allow(unused, unused_crate_dependencies)] #[rustfmt::skip] fn main() { diff --git a/tests/ui/cognitive_complexity_attr_used.rs b/tests/ui/cognitive_complexity_attr_used.rs index 403eff566ed6d..771a26fc9a86d 100644 --- a/tests/ui/cognitive_complexity_attr_used.rs +++ b/tests/ui/cognitive_complexity_attr_used.rs @@ -1,5 +1,5 @@ -#![warn(clippy::cognitive_complexity)] -#![warn(unused)] +#![warn(unused, clippy::cognitive_complexity)] +#![allow(unused_crate_dependencies)] fn main() { kaboom(); diff --git a/tests/ui/empty_line_after_outer_attribute.rs b/tests/ui/empty_line_after_outer_attribute.rs index 5343dff9da1db..3e92bca986ab5 100644 --- a/tests/ui/empty_line_after_outer_attribute.rs +++ b/tests/ui/empty_line_after_outer_attribute.rs @@ -1,8 +1,12 @@ +// aux-build:proc_macro_attr.rs #![warn(clippy::empty_line_after_outer_attr)] #![allow(clippy::assertions_on_constants)] #![feature(custom_inner_attributes)] #![rustfmt::skip] +#[macro_use] +extern crate proc_macro_attr; + // This should produce a warning #[crate_type = "lib"] @@ -93,4 +97,17 @@ pub struct S; /* test */ pub struct T; -fn main() { } +// This should not produce a warning +// See https://github.com/rust-lang/rust-clippy/issues/5567 +#[fake_async_trait] +pub trait Bazz { + fn foo() -> Vec { + let _i = ""; + + + + vec![] + } +} + +fn main() {} diff --git a/tests/ui/empty_line_after_outer_attribute.stderr b/tests/ui/empty_line_after_outer_attribute.stderr index d8c9786541f0b..bf753a732f000 100644 --- a/tests/ui/empty_line_after_outer_attribute.stderr +++ b/tests/ui/empty_line_after_outer_attribute.stderr @@ -1,5 +1,5 @@ error: Found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? - --> $DIR/empty_line_after_outer_attribute.rs:7:1 + --> $DIR/empty_line_after_outer_attribute.rs:11:1 | LL | / #[crate_type = "lib"] LL | | @@ -10,7 +10,7 @@ LL | | fn with_one_newline_and_comment() { assert!(true) } = note: `-D clippy::empty-line-after-outer-attr` implied by `-D warnings` error: Found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? - --> $DIR/empty_line_after_outer_attribute.rs:19:1 + --> $DIR/empty_line_after_outer_attribute.rs:23:1 | LL | / #[crate_type = "lib"] LL | | @@ -18,7 +18,7 @@ LL | | fn with_one_newline() { assert!(true) } | |_ error: Found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? - --> $DIR/empty_line_after_outer_attribute.rs:24:1 + --> $DIR/empty_line_after_outer_attribute.rs:28:1 | LL | / #[crate_type = "lib"] LL | | @@ -27,7 +27,7 @@ LL | | fn with_two_newlines() { assert!(true) } | |_ error: Found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? - --> $DIR/empty_line_after_outer_attribute.rs:31:1 + --> $DIR/empty_line_after_outer_attribute.rs:35:1 | LL | / #[crate_type = "lib"] LL | | @@ -35,7 +35,7 @@ LL | | enum Baz { | |_ error: Found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? - --> $DIR/empty_line_after_outer_attribute.rs:39:1 + --> $DIR/empty_line_after_outer_attribute.rs:43:1 | LL | / #[crate_type = "lib"] LL | | @@ -43,7 +43,7 @@ LL | | struct Foo { | |_ error: Found an empty line after an outer attribute. Perhaps you forgot to add a `!` to make it an inner attribute? - --> $DIR/empty_line_after_outer_attribute.rs:47:1 + --> $DIR/empty_line_after_outer_attribute.rs:51:1 | LL | / #[crate_type = "lib"] LL | | diff --git a/tests/ui/future_not_send.stderr b/tests/ui/future_not_send.stderr index d1863701bfe7c..b59dbb3e76c64 100644 --- a/tests/ui/future_not_send.stderr +++ b/tests/ui/future_not_send.stderr @@ -47,17 +47,32 @@ error: future cannot be sent between threads safely --> $DIR/future_not_send.rs:20:63 | LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { - | ^^^^ + | ^^^^ future returned by `private_future2` is not `Send` | +note: captured value is not `Send` + --> $DIR/future_not_send.rs:20:26 + | +LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { + | ^^ has type `std::rc::Rc<[u8]>` which is not `Send` = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` +note: captured value is not `Send` + --> $DIR/future_not_send.rs:20:40 + | +LL | async fn private_future2(rc: Rc<[u8]>, cell: &Cell) -> bool { + | ^^^^ has type `&std::cell::Cell` which is not `Send` = note: `std::cell::Cell` doesn't implement `std::marker::Sync` error: future cannot be sent between threads safely --> $DIR/future_not_send.rs:24:43 | LL | pub async fn public_future2(rc: Rc<[u8]>) {} - | ^ + | ^ future returned by `public_future2` is not `Send` | +note: captured value is not `Send` + --> $DIR/future_not_send.rs:24:29 + | +LL | pub async fn public_future2(rc: Rc<[u8]>) {} + | ^^ has type `std::rc::Rc<[u8]>` which is not `Send` = note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Send` error: future cannot be sent between threads safely @@ -117,8 +132,13 @@ error: future cannot be sent between threads safely --> $DIR/future_not_send.rs:66:34 | LL | async fn unclear_future(t: T) {} - | ^ + | ^ future returned by `unclear_future` is not `Send` | +note: captured value is not `Send` + --> $DIR/future_not_send.rs:66:28 + | +LL | async fn unclear_future(t: T) {} + | ^ has type `T` which is not `Send` = note: `T` doesn't implement `std::marker::Send` error: aborting due to 8 previous errors diff --git a/tests/ui/match_wild_err_arm.stderr b/tests/ui/match_wild_err_arm.stderr index 20d4c933418b7..6a2a02987dea7 100644 --- a/tests/ui/match_wild_err_arm.stderr +++ b/tests/ui/match_wild_err_arm.stderr @@ -5,7 +5,7 @@ LL | Err(_) => panic!("err"), | ^^^^^^ | = note: `-D clippy::match-wild-err-arm` implied by `-D warnings` - = note: match each error separately or use the error output + = note: match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable error: `Err(_)` matches all errors --> $DIR/match_wild_err_arm.rs:17:9 @@ -13,7 +13,7 @@ error: `Err(_)` matches all errors LL | Err(_) => panic!(), | ^^^^^^ | - = note: match each error separately or use the error output + = note: match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable error: `Err(_)` matches all errors --> $DIR/match_wild_err_arm.rs:23:9 @@ -21,7 +21,7 @@ error: `Err(_)` matches all errors LL | Err(_) => { | ^^^^^^ | - = note: match each error separately or use the error output + = note: match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable error: `Err(_e)` matches all errors --> $DIR/match_wild_err_arm.rs:31:9 @@ -29,7 +29,7 @@ error: `Err(_e)` matches all errors LL | Err(_e) => panic!(), | ^^^^^^^ | - = note: match each error separately or use the error output + = note: match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable error: aborting due to 4 previous errors diff --git a/tests/ui/match_wildcard_for_single_variants.fixed b/tests/ui/match_wildcard_for_single_variants.fixed new file mode 100644 index 0000000000000..519200977a798 --- /dev/null +++ b/tests/ui/match_wildcard_for_single_variants.fixed @@ -0,0 +1,59 @@ +// run-rustfix + +#![warn(clippy::match_wildcard_for_single_variants)] +#![allow(dead_code)] + +enum Foo { + A, + B, + C, +} + +enum Color { + Red, + Green, + Blue, + Rgb(u8, u8, u8), +} + +fn main() { + let f = Foo::A; + match f { + Foo::A => {}, + Foo::B => {}, + Foo::C => {}, + } + + let color = Color::Red; + + // check exhaustive bindings + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(_r, _g, _b) => {}, + Color::Blue => {}, + } + + // check exhaustive wild + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(..) => {}, + Color::Blue => {}, + } + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(_, _, _) => {}, + Color::Blue => {}, + } + + // shouldn't lint as there is one missing variant + // and one that isn't exhaustively covered + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(255, _, _) => {}, + _ => {}, + } +} diff --git a/tests/ui/match_wildcard_for_single_variants.rs b/tests/ui/match_wildcard_for_single_variants.rs new file mode 100644 index 0000000000000..1df917e085c71 --- /dev/null +++ b/tests/ui/match_wildcard_for_single_variants.rs @@ -0,0 +1,59 @@ +// run-rustfix + +#![warn(clippy::match_wildcard_for_single_variants)] +#![allow(dead_code)] + +enum Foo { + A, + B, + C, +} + +enum Color { + Red, + Green, + Blue, + Rgb(u8, u8, u8), +} + +fn main() { + let f = Foo::A; + match f { + Foo::A => {}, + Foo::B => {}, + _ => {}, + } + + let color = Color::Red; + + // check exhaustive bindings + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(_r, _g, _b) => {}, + _ => {}, + } + + // check exhaustive wild + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(..) => {}, + _ => {}, + } + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(_, _, _) => {}, + _ => {}, + } + + // shouldn't lint as there is one missing variant + // and one that isn't exhaustively covered + match color { + Color::Red => {}, + Color::Green => {}, + Color::Rgb(255, _, _) => {}, + _ => {}, + } +} diff --git a/tests/ui/match_wildcard_for_single_variants.stderr b/tests/ui/match_wildcard_for_single_variants.stderr new file mode 100644 index 0000000000000..82790aa9e80bb --- /dev/null +++ b/tests/ui/match_wildcard_for_single_variants.stderr @@ -0,0 +1,28 @@ +error: wildcard match will miss any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:24:9 + | +LL | _ => {}, + | ^ help: try this: `Foo::C` + | + = note: `-D clippy::match-wildcard-for-single-variants` implied by `-D warnings` + +error: wildcard match will miss any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:34:9 + | +LL | _ => {}, + | ^ help: try this: `Color::Blue` + +error: wildcard match will miss any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:42:9 + | +LL | _ => {}, + | ^ help: try this: `Color::Blue` + +error: wildcard match will miss any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:48:9 + | +LL | _ => {}, + | ^ help: try this: `Color::Blue` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/new_without_default.rs b/tests/ui/new_without_default.rs index 781ea7bb15283..3b6041823d878 100644 --- a/tests/ui/new_without_default.rs +++ b/tests/ui/new_without_default.rs @@ -148,4 +148,15 @@ impl AllowDerive { } } +pub struct NewNotEqualToDerive { + foo: i32, +} + +impl NewNotEqualToDerive { + // This `new` implementation is not equal to a derived `Default`, so do not suggest deriving. + pub fn new() -> Self { + NewNotEqualToDerive { foo: 1 } + } +} + fn main() {} diff --git a/tests/ui/new_without_default.stderr b/tests/ui/new_without_default.stderr index 5e485d40663f3..e529e441eb735 100644 --- a/tests/ui/new_without_default.stderr +++ b/tests/ui/new_without_default.stderr @@ -1,4 +1,4 @@ -error: you should consider deriving a `Default` implementation for `Foo` +error: you should consider adding a `Default` implementation for `Foo` --> $DIR/new_without_default.rs:8:5 | LL | / pub fn new() -> Foo { @@ -9,10 +9,14 @@ LL | | } = note: `-D clippy::new-without-default` implied by `-D warnings` help: try this | -LL | #[derive(Default)] +LL | impl Default for Foo { +LL | fn default() -> Self { +LL | Self::new() +LL | } +LL | } | -error: you should consider deriving a `Default` implementation for `Bar` +error: you should consider adding a `Default` implementation for `Bar` --> $DIR/new_without_default.rs:16:5 | LL | / pub fn new() -> Self { @@ -22,7 +26,11 @@ LL | | } | help: try this | -LL | #[derive(Default)] +LL | impl Default for Bar { +LL | fn default() -> Self { +LL | Self::new() +LL | } +LL | } | error: you should consider adding a `Default` implementation for `LtKo<'c>` @@ -42,5 +50,22 @@ LL | } LL | } | -error: aborting due to 3 previous errors +error: you should consider adding a `Default` implementation for `NewNotEqualToDerive` + --> $DIR/new_without_default.rs:157:5 + | +LL | / pub fn new() -> Self { +LL | | NewNotEqualToDerive { foo: 1 } +LL | | } + | |_____^ + | +help: try this + | +LL | impl Default for NewNotEqualToDerive { +LL | fn default() -> Self { +LL | Self::new() +LL | } +LL | } + | + +error: aborting due to 4 previous errors diff --git a/tests/ui/option_option.rs b/tests/ui/option_option.rs index 904c50e14039a..a2617a13ecace 100644 --- a/tests/ui/option_option.rs +++ b/tests/ui/option_option.rs @@ -60,3 +60,28 @@ fn main() { // The lint allows this let expr = Some(Some(true)); } + +extern crate serde; +mod issue_4298 { + use serde::{Deserialize, Deserializer, Serialize}; + use std::borrow::Cow; + + #[derive(Serialize, Deserialize)] + struct Foo<'a> { + #[serde(deserialize_with = "func")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + #[serde(borrow)] + // FIXME: should not lint here + #[allow(clippy::option_option)] + foo: Option>>, + } + + #[allow(clippy::option_option)] + fn func<'a, D>(_: D) -> Result>>, D::Error> + where + D: Deserializer<'a>, + { + Ok(Some(Some(Cow::Borrowed("hi")))) + } +} diff --git a/tests/ui/option_option.stderr b/tests/ui/option_option.stderr index 79db186d7ea77..0cd4c96eb4f9a 100644 --- a/tests/ui/option_option.stderr +++ b/tests/ui/option_option.stderr @@ -58,5 +58,11 @@ error: consider using `Option` instead of `Option>` or a custom enu LL | Struct { x: Option> }, | ^^^^^^^^^^^^^^^^^^ -error: aborting due to 9 previous errors +error: consider using `Option` instead of `Option>` or a custom enum if you need to distinguish all 3 cases + --> $DIR/option_option.rs:77:14 + | +LL | foo: Option>>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 10 previous errors diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index 8ea03fe42616c..7bb08797ef396 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -95,6 +95,15 @@ fn test_or_with_ctors() { let b = "b".to_string(); let _ = Some(Bar("a".to_string(), Duration::from_secs(1))) .or(Some(Bar(b, Duration::from_secs(2)))); + + let vec = vec!["foo"]; + let _ = opt.ok_or(vec.len()); + + let array = ["foo"]; + let _ = opt.ok_or(array.len()); + + let slice = &["foo"][..]; + let _ = opt.ok_or(slice.len()); } // Issue 4514 - early return diff --git a/tests/ui/or_fun_call.rs b/tests/ui/or_fun_call.rs index 7599b945a9137..522f31b72d01f 100644 --- a/tests/ui/or_fun_call.rs +++ b/tests/ui/or_fun_call.rs @@ -95,6 +95,15 @@ fn test_or_with_ctors() { let b = "b".to_string(); let _ = Some(Bar("a".to_string(), Duration::from_secs(1))) .or(Some(Bar(b, Duration::from_secs(2)))); + + let vec = vec!["foo"]; + let _ = opt.ok_or(vec.len()); + + let array = ["foo"]; + let _ = opt.ok_or(array.len()); + + let slice = &["foo"][..]; + let _ = opt.ok_or(slice.len()); } // Issue 4514 - early return diff --git a/tests/ui/ptr_arg.rs b/tests/ui/ptr_arg.rs index 30f39e9b06398..541225e635102 100644 --- a/tests/ui/ptr_arg.rs +++ b/tests/ui/ptr_arg.rs @@ -71,7 +71,6 @@ fn false_positive_capacity_too(x: &String) -> String { #[allow(dead_code)] fn test_cow_with_ref(c: &Cow<[i32]>) {} -#[allow(dead_code)] fn test_cow(c: Cow<[i32]>) { let _c = c; } @@ -84,3 +83,34 @@ trait Foo2 { impl Foo2 for String { fn do_string(&self) {} } + +// Check that the allow attribute on parameters is honored +mod issue_5644 { + use std::borrow::Cow; + + fn allowed( + #[allow(clippy::ptr_arg)] _v: &Vec, + #[allow(clippy::ptr_arg)] _s: &String, + #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, + ) { + } + + struct S {} + impl S { + fn allowed( + #[allow(clippy::ptr_arg)] _v: &Vec, + #[allow(clippy::ptr_arg)] _s: &String, + #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, + ) { + } + } + + trait T { + fn allowed( + #[allow(clippy::ptr_arg)] _v: &Vec, + #[allow(clippy::ptr_arg)] _s: &String, + #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, + ) { + } + } +} diff --git a/tests/ui/ptr_offset_with_cast.fixed b/tests/ui/ptr_offset_with_cast.fixed index ebdd6c4003d19..718e391e8bf69 100644 --- a/tests/ui/ptr_offset_with_cast.fixed +++ b/tests/ui/ptr_offset_with_cast.fixed @@ -9,12 +9,12 @@ fn main() { let offset_isize = 1_isize; unsafe { - ptr.add(offset_usize); - ptr.offset(offset_isize as isize); - ptr.offset(offset_u8 as isize); + let _ = ptr.add(offset_usize); + let _ = ptr.offset(offset_isize as isize); + let _ = ptr.offset(offset_u8 as isize); - ptr.wrapping_add(offset_usize); - ptr.wrapping_offset(offset_isize as isize); - ptr.wrapping_offset(offset_u8 as isize); + let _ = ptr.wrapping_add(offset_usize); + let _ = ptr.wrapping_offset(offset_isize as isize); + let _ = ptr.wrapping_offset(offset_u8 as isize); } } diff --git a/tests/ui/ptr_offset_with_cast.rs b/tests/ui/ptr_offset_with_cast.rs index 3416c4b727a53..f613742c741ef 100644 --- a/tests/ui/ptr_offset_with_cast.rs +++ b/tests/ui/ptr_offset_with_cast.rs @@ -9,12 +9,12 @@ fn main() { let offset_isize = 1_isize; unsafe { - ptr.offset(offset_usize as isize); - ptr.offset(offset_isize as isize); - ptr.offset(offset_u8 as isize); + let _ = ptr.offset(offset_usize as isize); + let _ = ptr.offset(offset_isize as isize); + let _ = ptr.offset(offset_u8 as isize); - ptr.wrapping_offset(offset_usize as isize); - ptr.wrapping_offset(offset_isize as isize); - ptr.wrapping_offset(offset_u8 as isize); + let _ = ptr.wrapping_offset(offset_usize as isize); + let _ = ptr.wrapping_offset(offset_isize as isize); + let _ = ptr.wrapping_offset(offset_u8 as isize); } } diff --git a/tests/ui/ptr_offset_with_cast.stderr b/tests/ui/ptr_offset_with_cast.stderr index b5c7a03e2775e..fd45224ca067f 100644 --- a/tests/ui/ptr_offset_with_cast.stderr +++ b/tests/ui/ptr_offset_with_cast.stderr @@ -1,16 +1,16 @@ error: use of `offset` with a `usize` casted to an `isize` - --> $DIR/ptr_offset_with_cast.rs:12:9 + --> $DIR/ptr_offset_with_cast.rs:12:17 | -LL | ptr.offset(offset_usize as isize); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.add(offset_usize)` +LL | let _ = ptr.offset(offset_usize as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.add(offset_usize)` | = note: `-D clippy::ptr-offset-with-cast` implied by `-D warnings` error: use of `wrapping_offset` with a `usize` casted to an `isize` - --> $DIR/ptr_offset_with_cast.rs:16:9 + --> $DIR/ptr_offset_with_cast.rs:16:17 | -LL | ptr.wrapping_offset(offset_usize as isize); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.wrapping_add(offset_usize)` +LL | let _ = ptr.wrapping_offset(offset_usize as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ptr.wrapping_add(offset_usize)` error: aborting due to 2 previous errors diff --git a/tests/ui/reversed_empty_ranges_fixable.fixed b/tests/ui/reversed_empty_ranges_fixable.fixed index ee2cbc3cf540e..332c0427ef65a 100644 --- a/tests/ui/reversed_empty_ranges_fixable.fixed +++ b/tests/ui/reversed_empty_ranges_fixable.fixed @@ -4,18 +4,23 @@ const ANSWER: i32 = 42; fn main() { + let arr = [1, 2, 3, 4, 5]; + + // These should be linted: + (21..=42).rev().for_each(|x| println!("{}", x)); let _ = (21..ANSWER).rev().filter(|x| x % 2 == 0).take(10).collect::>(); for _ in (-42..=-21).rev() {} for _ in (21u32..42u32).rev() {} + let _ = &[] as &[i32]; + // These should be ignored as they are not empty ranges: (21..=42).for_each(|x| println!("{}", x)); (21..42).for_each(|x| println!("{}", x)); - let arr = [1, 2, 3, 4, 5]; let _ = &arr[1..=3]; let _ = &arr[1..3]; diff --git a/tests/ui/reversed_empty_ranges_fixable.rs b/tests/ui/reversed_empty_ranges_fixable.rs index 6ed5ca6daa0e8..901ec8bcc09f4 100644 --- a/tests/ui/reversed_empty_ranges_fixable.rs +++ b/tests/ui/reversed_empty_ranges_fixable.rs @@ -4,18 +4,23 @@ const ANSWER: i32 = 42; fn main() { + let arr = [1, 2, 3, 4, 5]; + + // These should be linted: + (42..=21).for_each(|x| println!("{}", x)); let _ = (ANSWER..21).filter(|x| x % 2 == 0).take(10).collect::>(); for _ in -21..=-42 {} for _ in 42u32..21u32 {} + let _ = &arr[3..3]; + // These should be ignored as they are not empty ranges: (21..=42).for_each(|x| println!("{}", x)); (21..42).for_each(|x| println!("{}", x)); - let arr = [1, 2, 3, 4, 5]; let _ = &arr[1..=3]; let _ = &arr[1..3]; diff --git a/tests/ui/reversed_empty_ranges_fixable.stderr b/tests/ui/reversed_empty_ranges_fixable.stderr index 97933b8ff8515..9a646fd99398d 100644 --- a/tests/ui/reversed_empty_ranges_fixable.stderr +++ b/tests/ui/reversed_empty_ranges_fixable.stderr @@ -1,5 +1,5 @@ error: this range is empty so it will yield no values - --> $DIR/reversed_empty_ranges_fixable.rs:7:5 + --> $DIR/reversed_empty_ranges_fixable.rs:11:5 | LL | (42..=21).for_each(|x| println!("{}", x)); | ^^^^^^^^^ @@ -11,7 +11,7 @@ LL | (21..=42).rev().for_each(|x| println!("{}", x)); | ^^^^^^^^^^^^^^^ error: this range is empty so it will yield no values - --> $DIR/reversed_empty_ranges_fixable.rs:8:13 + --> $DIR/reversed_empty_ranges_fixable.rs:12:13 | LL | let _ = (ANSWER..21).filter(|x| x % 2 == 0).take(10).collect::>(); | ^^^^^^^^^^^^ @@ -22,7 +22,7 @@ LL | let _ = (21..ANSWER).rev().filter(|x| x % 2 == 0).take(10).collect:: $DIR/reversed_empty_ranges_fixable.rs:10:14 + --> $DIR/reversed_empty_ranges_fixable.rs:14:14 | LL | for _ in -21..=-42 {} | ^^^^^^^^^ @@ -33,7 +33,7 @@ LL | for _ in (-42..=-21).rev() {} | ^^^^^^^^^^^^^^^^^ error: this range is empty so it will yield no values - --> $DIR/reversed_empty_ranges_fixable.rs:11:14 + --> $DIR/reversed_empty_ranges_fixable.rs:15:14 | LL | for _ in 42u32..21u32 {} | ^^^^^^^^^^^^ @@ -43,5 +43,11 @@ help: consider using the following if you are attempting to iterate over this ra LL | for _ in (21u32..42u32).rev() {} | ^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 4 previous errors +error: this range is empty and using it to index a slice will always yield an empty slice + --> $DIR/reversed_empty_ranges_fixable.rs:17:18 + | +LL | let _ = &arr[3..3]; + | ----^^^^- help: if you want an empty slice, use: `[] as &[i32]` + +error: aborting due to 5 previous errors diff --git a/tests/ui/reversed_empty_ranges_unfixable.rs b/tests/ui/reversed_empty_ranges_unfixable.rs index c9ca4c4766831..561a35625f02e 100644 --- a/tests/ui/reversed_empty_ranges_unfixable.rs +++ b/tests/ui/reversed_empty_ranges_unfixable.rs @@ -9,7 +9,6 @@ fn main() { let arr = [1, 2, 3, 4, 5]; let _ = &arr[3usize..=1usize]; let _ = &arr[SOME_NUM..1]; - let _ = &arr[3..3]; for _ in ANSWER..ANSWER {} } diff --git a/tests/ui/reversed_empty_ranges_unfixable.stderr b/tests/ui/reversed_empty_ranges_unfixable.stderr index 12e5483ecdfff..240188cbb46cb 100644 --- a/tests/ui/reversed_empty_ranges_unfixable.stderr +++ b/tests/ui/reversed_empty_ranges_unfixable.stderr @@ -18,17 +18,11 @@ error: this range is reversed and using it to index a slice will panic at run-ti LL | let _ = &arr[SOME_NUM..1]; | ^^^^^^^^^^^ -error: this range is empty and using it to index a slice will always yield an empty slice - --> $DIR/reversed_empty_ranges_unfixable.rs:12:18 - | -LL | let _ = &arr[3..3]; - | ^^^^ - error: this range is empty so it will yield no values - --> $DIR/reversed_empty_ranges_unfixable.rs:14:14 + --> $DIR/reversed_empty_ranges_unfixable.rs:13:14 | LL | for _ in ANSWER..ANSWER {} | ^^^^^^^^^^^^^^ -error: aborting due to 5 previous errors +error: aborting due to 4 previous errors diff --git a/tests/ui/useless_conversion.stderr b/tests/ui/useless_conversion.stderr index 7df3507edfd9e..84ec53702788c 100644 --- a/tests/ui/useless_conversion.stderr +++ b/tests/ui/useless_conversion.stderr @@ -1,4 +1,4 @@ -error: useless conversion +error: useless conversion to the same type --> $DIR/useless_conversion.rs:6:13 | LL | let _ = T::from(val); @@ -10,55 +10,55 @@ note: the lint level is defined here LL | #![deny(clippy::useless_conversion)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: useless conversion +error: useless conversion to the same type --> $DIR/useless_conversion.rs:7:5 | LL | val.into() | ^^^^^^^^^^ help: consider removing `.into()`: `val` -error: useless conversion +error: useless conversion to the same type --> $DIR/useless_conversion.rs:19:22 | LL | let _: i32 = 0i32.into(); | ^^^^^^^^^^^ help: consider removing `.into()`: `0i32` -error: useless conversion +error: useless conversion to the same type --> $DIR/useless_conversion.rs:51:21 | LL | let _: String = "foo".to_string().into(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `"foo".to_string()` -error: useless conversion +error: useless conversion to the same type --> $DIR/useless_conversion.rs:52:21 | LL | let _: String = From::from("foo".to_string()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `From::from()`: `"foo".to_string()` -error: useless conversion +error: useless conversion to the same type --> $DIR/useless_conversion.rs:53:13 | LL | let _ = String::from("foo".to_string()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `"foo".to_string()` -error: useless conversion +error: useless conversion to the same type --> $DIR/useless_conversion.rs:54:13 | LL | let _ = String::from(format!("A: {:04}", 123)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `format!("A: {:04}", 123)` -error: useless conversion +error: useless conversion to the same type --> $DIR/useless_conversion.rs:55:13 | LL | let _ = "".lines().into_iter(); | ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `"".lines()` -error: useless conversion +error: useless conversion to the same type --> $DIR/useless_conversion.rs:56:13 | LL | let _ = vec![1, 2, 3].into_iter().into_iter(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `vec![1, 2, 3].into_iter()` -error: useless conversion +error: useless conversion to the same type --> $DIR/useless_conversion.rs:57:21 | LL | let _: String = format!("Hello {}", "world").into(); diff --git a/tests/ui/useless_conversion_try.rs b/tests/ui/useless_conversion_try.rs new file mode 100644 index 0000000000000..3787ea991445c --- /dev/null +++ b/tests/ui/useless_conversion_try.rs @@ -0,0 +1,42 @@ +#![deny(clippy::useless_conversion)] + +use std::convert::{TryFrom, TryInto}; + +fn test_generic(val: T) -> T { + let _ = T::try_from(val).unwrap(); + val.try_into().unwrap() +} + +fn test_generic2 + Into, U: From>(val: T) { + // ok + let _: i32 = val.try_into().unwrap(); + let _: U = val.try_into().unwrap(); + let _ = U::try_from(val).unwrap(); +} + +fn main() { + test_generic(10i32); + test_generic2::(10i32); + + let _: String = "foo".try_into().unwrap(); + let _: String = TryFrom::try_from("foo").unwrap(); + let _ = String::try_from("foo").unwrap(); + #[allow(clippy::useless_conversion)] + { + let _ = String::try_from("foo").unwrap(); + let _: String = "foo".try_into().unwrap(); + } + let _: String = "foo".to_string().try_into().unwrap(); + let _: String = TryFrom::try_from("foo".to_string()).unwrap(); + let _ = String::try_from("foo".to_string()).unwrap(); + let _ = String::try_from(format!("A: {:04}", 123)).unwrap(); + let _: String = format!("Hello {}", "world").try_into().unwrap(); + let _: String = "".to_owned().try_into().unwrap(); + let _: String = match String::from("_").try_into() { + Ok(a) => a, + Err(_) => "".into(), + }; + // FIXME this is a false negative + #[allow(clippy::cmp_owned)] + if String::from("a") == TryInto::::try_into(String::from("a")).unwrap() {} +} diff --git a/tests/ui/useless_conversion_try.stderr b/tests/ui/useless_conversion_try.stderr new file mode 100644 index 0000000000000..b765727c168f5 --- /dev/null +++ b/tests/ui/useless_conversion_try.stderr @@ -0,0 +1,79 @@ +error: useless conversion to the same type + --> $DIR/useless_conversion_try.rs:6:13 + | +LL | let _ = T::try_from(val).unwrap(); + | ^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/useless_conversion_try.rs:1:9 + | +LL | #![deny(clippy::useless_conversion)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: consider removing `T::try_from()` + +error: useless conversion to the same type + --> $DIR/useless_conversion_try.rs:7:5 + | +LL | val.try_into().unwrap() + | ^^^^^^^^^^^^^^ + | + = help: consider removing `.try_into()` + +error: useless conversion to the same type + --> $DIR/useless_conversion_try.rs:29:21 + | +LL | let _: String = "foo".to_string().try_into().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `.try_into()` + +error: useless conversion to the same type + --> $DIR/useless_conversion_try.rs:30:21 + | +LL | let _: String = TryFrom::try_from("foo".to_string()).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `TryFrom::try_from()` + +error: useless conversion to the same type + --> $DIR/useless_conversion_try.rs:31:13 + | +LL | let _ = String::try_from("foo".to_string()).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `String::try_from()` + +error: useless conversion to the same type + --> $DIR/useless_conversion_try.rs:32:13 + | +LL | let _ = String::try_from(format!("A: {:04}", 123)).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `String::try_from()` + +error: useless conversion to the same type + --> $DIR/useless_conversion_try.rs:33:21 + | +LL | let _: String = format!("Hello {}", "world").try_into().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `.try_into()` + +error: useless conversion to the same type + --> $DIR/useless_conversion_try.rs:34:21 + | +LL | let _: String = "".to_owned().try_into().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `.try_into()` + +error: useless conversion to the same type + --> $DIR/useless_conversion_try.rs:35:27 + | +LL | let _: String = match String::from("_").try_into() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing `.try_into()` + +error: aborting due to 9 previous errors + From b6c58f0d723d6178506773fa9a4dba9de08ce90a Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 28 May 2020 17:19:30 +0200 Subject: [PATCH 07/72] Temp fix: don't run cargo lint tests in rustc test suite --- tests/compile-test.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 2758b9a7e7604..1c4914a470c9b 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -153,6 +153,9 @@ fn run_ui_toml(config: &mut compiletest::Config) { } fn run_ui_cargo(config: &mut compiletest::Config) { + if cargo::is_rustc_test_suite() { + return; + } fn run_tests( config: &compiletest::Config, filter: &Option, From b92cc8a08d74fb412bc444a4361df51b0c95401c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Kr=C3=BCger?= Date: Fri, 29 May 2020 22:46:05 +0200 Subject: [PATCH 08/72] add testcase that no longer ICEs Fixes #3969 --- tests/ui/crashes/ice-3969.rs | 51 ++++++++++++++++++++++++++++++++ tests/ui/crashes/ice-3969.stderr | 22 ++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 tests/ui/crashes/ice-3969.rs create mode 100644 tests/ui/crashes/ice-3969.stderr diff --git a/tests/ui/crashes/ice-3969.rs b/tests/ui/crashes/ice-3969.rs new file mode 100644 index 0000000000000..4feab7910b744 --- /dev/null +++ b/tests/ui/crashes/ice-3969.rs @@ -0,0 +1,51 @@ +// https://github.com/rust-lang/rust-clippy/issues/3969 +// used to crash: error: internal compiler error: +// src/librustc_traits/normalize_erasing_regions.rs:43: could not fully normalize `::Item test from rustc ./ui/trivial-bounds/trivial-bounds-inconsistent.rs + +// Check that tautalogically false bounds are accepted, and are used +// in type inference. +#![feature(trivial_bounds)] +#![allow(unused)] + +trait A {} + +impl A for i32 {} + +struct Dst { + x: X, +} + +struct TwoStrs(str, str) +where + str: Sized; + +fn unsized_local() +where + for<'a> Dst: Sized, +{ + let x: Dst = *(Box::new(Dst { x: 1 }) as Box>); +} + +fn return_str() -> str +where + str: Sized, +{ + *"Sized".to_string().into_boxed_str() +} + +fn use_op(s: String) -> String +where + String: ::std::ops::Neg, +{ + -s +} + +fn use_for() +where + i32: Iterator, +{ + for _ in 2i32 {} +} + +fn main() {} diff --git a/tests/ui/crashes/ice-3969.stderr b/tests/ui/crashes/ice-3969.stderr new file mode 100644 index 0000000000000..923db0664a714 --- /dev/null +++ b/tests/ui/crashes/ice-3969.stderr @@ -0,0 +1,22 @@ +error: trait objects without an explicit `dyn` are deprecated + --> $DIR/ice-3969.rs:25:17 + | +LL | for<'a> Dst: Sized, + | ^^^^^^ help: use `dyn`: `dyn A + 'a` + | + = note: `-D bare-trait-objects` implied by `-D warnings` + +error: trait objects without an explicit `dyn` are deprecated + --> $DIR/ice-3969.rs:27:16 + | +LL | let x: Dst = *(Box::new(Dst { x: 1 }) as Box>); + | ^ help: use `dyn`: `dyn A` + +error: trait objects without an explicit `dyn` are deprecated + --> $DIR/ice-3969.rs:27:57 + | +LL | let x: Dst = *(Box::new(Dst { x: 1 }) as Box>); + | ^ help: use `dyn`: `dyn A` + +error: aborting due to 3 previous errors + From 9fd8e1088fe858b6a5da2b7e8baaeeacd26c6a31 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sat, 30 May 2020 18:48:54 +0900 Subject: [PATCH 09/72] Return early to avoid ICE --- clippy_lints/src/utils/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index 6c1488664bf00..6dd8fef7e82da 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -323,6 +323,11 @@ pub fn implements_trait<'a, 'tcx>( trait_id: DefId, ty_params: &[GenericArg<'tcx>], ) -> bool { + // Do not check on infer_types to avoid panic in evaluate_obligation. + if ty.has_infer_types() { + return false; + } + let ty = cx.tcx.erase_regions(&ty); let ty_params = cx.tcx.mk_substs(ty_params.iter()); cx.tcx.type_implements_trait((trait_id, ty, ty_params, cx.param_env)) } From 5faab874f9f8655c8f284944b5acdede5c088af4 Mon Sep 17 00:00:00 2001 From: Tim Nielens Date: Sat, 23 May 2020 00:07:09 +0200 Subject: [PATCH 10/72] new lint: vec_resize_to_zero --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 5 +++ clippy_lints/src/utils/paths.rs | 1 + clippy_lints/src/vec_resize_to_zero.rs | 59 ++++++++++++++++++++++++++ src/lintlist/mod.rs | 7 +++ tests/ui/vec_resize_to_zero.rs | 15 +++++++ tests/ui/vec_resize_to_zero.stderr | 13 ++++++ 7 files changed, 101 insertions(+) create mode 100644 clippy_lints/src/vec_resize_to_zero.rs create mode 100644 tests/ui/vec_resize_to_zero.rs create mode 100644 tests/ui/vec_resize_to_zero.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ac9057199ff3..f7dae3dcfff0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1630,6 +1630,7 @@ Released 2018-09-13 [`useless_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_transmute [`useless_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_vec [`vec_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_box +[`vec_resize_to_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_resize_to_zero [`verbose_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_bit_mask [`verbose_file_reads`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_file_reads [`vtable_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#vtable_address_comparisons diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 902f3d56c1e4f..4f0ecab393d03 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -325,6 +325,7 @@ mod unwrap; mod use_self; mod useless_conversion; mod vec; +mod vec_resize_to_zero; mod verbose_file_reads; mod wildcard_dependencies; mod wildcard_imports; @@ -847,6 +848,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &utils::internal_lints::OUTER_EXPN_EXPN_DATA, &utils::internal_lints::PRODUCE_ICE, &vec::USELESS_VEC, + &vec_resize_to_zero::VEC_RESIZE_TO_ZERO, &verbose_file_reads::VERBOSE_FILE_READS, &wildcard_dependencies::WILDCARD_DEPENDENCIES, &wildcard_imports::ENUM_GLOB_USE, @@ -1062,6 +1064,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| box manual_non_exhaustive::ManualNonExhaustive); store.register_late_pass(|| box manual_async_fn::ManualAsyncFn); store.register_early_pass(|| box redundant_field_names::RedundantFieldNames); + store.register_late_pass(|| box vec_resize_to_zero::VecResizeToZero); let single_char_binding_names_threshold = conf.single_char_binding_names_threshold; store.register_early_pass(move || box non_expressive_names::NonExpressiveNames { single_char_binding_names_threshold, @@ -1430,6 +1433,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&unwrap::UNNECESSARY_UNWRAP), LintId::of(&useless_conversion::USELESS_CONVERSION), LintId::of(&vec::USELESS_VEC), + LintId::of(&vec_resize_to_zero::VEC_RESIZE_TO_ZERO), LintId::of(&write::PRINTLN_EMPTY_STRING), LintId::of(&write::PRINT_LITERAL), LintId::of(&write::PRINT_WITH_NEWLINE), @@ -1677,6 +1681,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS), LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), LintId::of(&unwrap::PANICKING_UNWRAP), + LintId::of(&vec_resize_to_zero::VEC_RESIZE_TO_ZERO), ]); store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ diff --git a/clippy_lints/src/utils/paths.rs b/clippy_lints/src/utils/paths.rs index 779da7e6bf23c..3b7e9739211b0 100644 --- a/clippy_lints/src/utils/paths.rs +++ b/clippy_lints/src/utils/paths.rs @@ -138,5 +138,6 @@ pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"]; pub const VEC_DEQUE: [&str; 4] = ["alloc", "collections", "vec_deque", "VecDeque"]; 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"]; pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"]; pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"]; diff --git a/clippy_lints/src/vec_resize_to_zero.rs b/clippy_lints/src/vec_resize_to_zero.rs new file mode 100644 index 0000000000000..86cbfa8203d54 --- /dev/null +++ b/clippy_lints/src/vec_resize_to_zero.rs @@ -0,0 +1,59 @@ +use crate::utils::span_lint_and_then; +use if_chain::if_chain; +use rustc_errors::Applicability; +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 crate::utils::{match_def_path, paths}; +use rustc_ast::ast::LitKind; +use rustc_hir as hir; + +declare_clippy_lint! { + /// **What it does:** Finds occurences of `Vec::resize(0, an_int)` + /// + /// **Why is this bad?** This is probably an argument inversion mistake. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// vec!(1, 2, 3, 4, 5).resize(0, 5) + /// ``` + pub VEC_RESIZE_TO_ZERO, + correctness, + "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake" +} + +declare_lint_pass!(VecResizeToZero => [VEC_RESIZE_TO_ZERO]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for VecResizeToZero { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let hir::ExprKind::MethodCall(path_segment, _, ref args) = expr.kind; + if let Some(method_def_id) = cx.tables.type_dependent_def_id(expr.hir_id); + if match_def_path(cx, method_def_id, &paths::VEC_RESIZE) && args.len() == 3; + if let ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = args[1].kind; + if let ExprKind::Lit(Spanned { node: LitKind::Int(..), .. }) = args[2].kind; + then { + let method_call_span = expr.span.with_lo(path_segment.ident.span.lo()); + span_lint_and_then( + cx, + VEC_RESIZE_TO_ZERO, + expr.span, + "emptying a vector with `resize`", + |db| { + db.help("the arguments may be inverted..."); + db.span_suggestion( + method_call_span, + "...or you can empty the vector with", + "clear()".to_string(), + Applicability::MaybeIncorrect, + ); + }, + ); + } + } + } +} diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index f63301c7db0a6..1e94ca00c1457 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -2460,6 +2460,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "types", }, + Lint { + name: "vec_resize_to_zero", + group: "correctness", + desc: "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake", + deprecation: None, + module: "vec_resize_to_zero", + }, Lint { name: "verbose_bit_mask", group: "style", diff --git a/tests/ui/vec_resize_to_zero.rs b/tests/ui/vec_resize_to_zero.rs new file mode 100644 index 0000000000000..0263e2f5f20c1 --- /dev/null +++ b/tests/ui/vec_resize_to_zero.rs @@ -0,0 +1,15 @@ +#![warn(clippy::vec_resize_to_zero)] + +fn main() { + // applicable here + vec![1, 2, 3, 4, 5].resize(0, 5); + + // not applicable + vec![1, 2, 3, 4, 5].resize(2, 5); + + // applicable here, but only implemented for integer litterals for now + vec!["foo", "bar", "baz"].resize(0, "bar"); + + // not applicable + vec!["foo", "bar", "baz"].resize(2, "bar") +} diff --git a/tests/ui/vec_resize_to_zero.stderr b/tests/ui/vec_resize_to_zero.stderr new file mode 100644 index 0000000000000..feb846298c656 --- /dev/null +++ b/tests/ui/vec_resize_to_zero.stderr @@ -0,0 +1,13 @@ +error: emptying a vector with `resize` + --> $DIR/vec_resize_to_zero.rs:5:5 + | +LL | vec![1, 2, 3, 4, 5].resize(0, 5); + | ^^^^^^^^^^^^^^^^^^^^------------ + | | + | help: ...or you can empty the vector with: `clear()` + | + = note: `-D clippy::vec-resize-to-zero` implied by `-D warnings` + = help: the arguments may be inverted... + +error: aborting due to previous error + From 37381d33a4761a064311dd95fbc54b5da6ad3766 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Sun, 31 May 2020 14:05:57 +0200 Subject: [PATCH 11/72] Fix sync fallout --- clippy_lints/src/needless_pass_by_value.rs | 8 +------- clippy_lints/src/write.rs | 13 ++++++------- tests/compile-test.rs | 7 ++++--- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 218b0d27f7486..9c508fc0e4a1a 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -173,13 +173,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue { !preds.is_empty() && { let ty_empty_region = cx.tcx.mk_imm_ref(cx.tcx.lifetimes.re_root_empty, ty); preds.iter().all(|t| { - let ty_params = &t - .skip_binder() - .trait_ref - .substs - .iter() - .skip(1) - .collect::>(); + let ty_params = &t.skip_binder().trait_ref.substs.iter().skip(1).collect::>(); implements_trait(cx, ty_empty_region, t.def_id(), ty_params) }) }, diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index dfa6223f1b9dd..5f794598052f6 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -279,13 +279,12 @@ impl EarlyLintPass for Write { if let (Some(fmt_str), expr) = self.check_tts(cx, &mac.args.inner_tokens(), true) { if fmt_str.symbol == Symbol::intern("") { let mut applicability = Applicability::MachineApplicable; - let suggestion = expr.map_or_else( - move || { - applicability = Applicability::HasPlaceholders; - Cow::Borrowed("v") - }, - move |expr| snippet_with_applicability(cx, expr.span, "v", &mut applicability), - ); + let suggestion = if let Some(e) = expr { + snippet_with_applicability(cx, e.span, "v", &mut applicability) + } else { + applicability = Applicability::HasPlaceholders; + Cow::Borrowed("v") + }; span_lint_and_sugg( cx, diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 1c4914a470c9b..7bd5f09f333dd 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -153,9 +153,6 @@ fn run_ui_toml(config: &mut compiletest::Config) { } fn run_ui_cargo(config: &mut compiletest::Config) { - if cargo::is_rustc_test_suite() { - return; - } fn run_tests( config: &compiletest::Config, filter: &Option, @@ -216,6 +213,10 @@ fn run_ui_cargo(config: &mut compiletest::Config) { Ok(result) } + if cargo::is_rustc_test_suite() { + return; + } + config.mode = TestMode::Ui; config.src_base = Path::new("tests").join("ui-cargo").canonicalize().unwrap(); From 0bcfae92f80d31cad4e5fb687da8033a38d06a32 Mon Sep 17 00:00:00 2001 From: djugei Date: Sun, 31 May 2020 15:41:33 +0200 Subject: [PATCH 12/72] moved cast_ptr_alignment to pedantic and expanded documentation --- clippy_lints/src/types.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/types.rs b/clippy_lints/src/types.rs index 6ed9ff22e4664..3ac99e2468411 100644 --- a/clippy_lints/src/types.rs +++ b/clippy_lints/src/types.rs @@ -974,7 +974,8 @@ declare_clippy_lint! { /// behavior. /// /// **Known problems:** Using `std::ptr::read_unaligned` and `std::ptr::write_unaligned` or similar - /// on the resulting pointer is fine. + /// on the resulting pointer is fine. Is over-zealous: Casts with manual alignment checks or casts like + /// u64-> u8 -> u16 can be fine. Miri is able to do a more in-depth analysis. /// /// **Example:** /// ```rust @@ -982,7 +983,7 @@ declare_clippy_lint! { /// let _ = (&mut 1u8 as *mut u8) as *mut u16; /// ``` pub CAST_PTR_ALIGNMENT, - correctness, + pedantic, "cast from a pointer to a more-strictly-aligned pointer" } From 18b5ceed7991e3d8616b74b42de26330ca4c40db Mon Sep 17 00:00:00 2001 From: djugei Date: Sun, 31 May 2020 16:00:29 +0200 Subject: [PATCH 13/72] ran update_lints --- clippy_lints/src/lib.rs | 3 +-- src/lintlist/mod.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 902f3d56c1e4f..6475fa67d2518 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1164,6 +1164,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::CAST_POSSIBLE_TRUNCATION), LintId::of(&types::CAST_POSSIBLE_WRAP), LintId::of(&types::CAST_PRECISION_LOSS), + LintId::of(&types::CAST_PTR_ALIGNMENT), LintId::of(&types::CAST_SIGN_LOSS), LintId::of(&types::IMPLICIT_HASHER), LintId::of(&types::INVALID_UPCAST_COMPARISONS), @@ -1410,7 +1411,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::ABSURD_EXTREME_COMPARISONS), LintId::of(&types::BORROWED_BOX), LintId::of(&types::BOX_VEC), - LintId::of(&types::CAST_PTR_ALIGNMENT), LintId::of(&types::CAST_REF_TO_MUT), LintId::of(&types::CHAR_LIT_AS_U8), LintId::of(&types::FN_TO_NUMERIC_CAST), @@ -1669,7 +1669,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&transmute::WRONG_TRANSMUTE), LintId::of(&transmuting_null::TRANSMUTING_NULL), LintId::of(&types::ABSURD_EXTREME_COMPARISONS), - LintId::of(&types::CAST_PTR_ALIGNMENT), LintId::of(&types::CAST_REF_TO_MUT), LintId::of(&types::UNIT_CMP), LintId::of(&unicode::ZERO_WIDTH_SPACE), diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index f63301c7db0a6..b9c84654593f9 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -166,7 +166,7 @@ pub static ref ALL_LINTS: Vec = vec![ }, Lint { name: "cast_ptr_alignment", - group: "correctness", + group: "pedantic", desc: "cast from a pointer to a more-strictly-aligned pointer", deprecation: None, module: "types", From 6122612232976c1ac766a5d415265eb3eb30e72c Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Sun, 31 May 2020 17:38:59 +0200 Subject: [PATCH 14/72] Increase cargo_metadata version to 0.9.1 `clippy_lints` makes use of `dep_kinds` on `NodeDep` but this was only added in versoin 0.9.1. Compiling with 0.9.0 will fail because of this. --- Cargo.toml | 2 +- clippy_lints/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6999b6bd74040..836897927b015 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ tempfile = { version = "3.1.0", optional = true } lazy_static = "1.0" [dev-dependencies] -cargo_metadata = "0.9.0" +cargo_metadata = "0.9.1" compiletest_rs = { version = "0.5.0", features = ["tmp"] } tester = "0.7" lazy_static = "1.0" diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 76baf27fb2dbf..98391732d1893 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -17,7 +17,7 @@ keywords = ["clippy", "lint", "plugin"] edition = "2018" [dependencies] -cargo_metadata = "0.9.0" +cargo_metadata = "0.9.1" if_chain = "1.0.0" itertools = "0.9" lazy_static = "1.0.2" From 0ab823c509897ce2f516feb760fe1bf02cf77443 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Mon, 26 Aug 2019 18:34:30 +0200 Subject: [PATCH 15/72] Rework suggestion generation of `unit_arg` lint --- clippy_lints/src/types.rs | 42 ++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/types.rs b/clippy_lints/src/types.rs index 3ac99e2468411..8fcca4b7bb9ca 100644 --- a/clippy_lints/src/types.rs +++ b/clippy_lints/src/types.rs @@ -779,6 +779,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitArg { match expr.kind { ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args) => { + let mut args_to_recover = vec![]; for arg in args { if is_unit(cx.tables.expr_ty(arg)) && !is_unit_literal(arg) { if let ExprKind::Match(.., match_source) = &arg.kind { @@ -787,17 +788,40 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitArg { } } - span_lint_and_sugg( - cx, - UNIT_ARG, - arg.span, - "passing a unit value to a function", - "if you intended to pass a unit value, use a unit literal instead", - "()".to_string(), - Applicability::MaybeIncorrect, - ); + args_to_recover.push(arg); } } + if !args_to_recover.is_empty() { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_then(cx, UNIT_ARG, expr.span, "passing a unit value to a function", |db| { + db.span_suggestion( + expr.span.with_hi(expr.span.lo()), + "move the expressions in front of the call...", + format!( + "{} ", + args_to_recover + .iter() + .map(|arg| { + format!( + "{};", + snippet_with_applicability(cx, arg.span, "..", &mut applicability) + ) + }) + .collect::>() + .join(" ") + ), + applicability, + ); + db.multipart_suggestion( + "...and use unit literals instead", + args_to_recover + .iter() + .map(|arg| (arg.span, "()".to_string())) + .collect::>(), + applicability, + ); + }); + } }, _ => (), } From 380d941a045dc213ae28807d74fc32d1b1841e22 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Mon, 26 Aug 2019 19:35:25 +0200 Subject: [PATCH 16/72] Adapt stderr and fixed files --- tests/ui/unit_arg.fixed | 29 +++++++--- tests/ui/unit_arg.rs | 10 +++- tests/ui/unit_arg.stderr | 112 ++++++++++++++++++++++++++++++--------- 3 files changed, 118 insertions(+), 33 deletions(-) diff --git a/tests/ui/unit_arg.fixed b/tests/ui/unit_arg.fixed index a739cf7ad814e..67c6bdf8873b6 100644 --- a/tests/ui/unit_arg.fixed +++ b/tests/ui/unit_arg.fixed @@ -1,6 +1,6 @@ // run-rustfix #![warn(clippy::unit_arg)] -#![allow(unused_braces, clippy::no_effect, unused_must_use)] +#![allow(clippy::no_effect, unused_must_use, unused_variables)] use std::fmt::Debug; @@ -21,13 +21,21 @@ impl Bar { } fn bad() { - foo(()); - foo(()); - foo(()); - foo(()); - foo3((), 2, 2); + {}; foo(()); + { + 1; + }; foo(()); + foo(1); foo(()); + { + foo(1); + foo(2); + }; foo(()); + {}; foo3((), 2, 2); let b = Bar; - b.bar(()); + { + 1; + }; b.bar(()); + foo(0); foo(1); taking_multiple_units((), ()); } fn ok() { @@ -58,6 +66,13 @@ mod issue_2945 { } } +#[allow(dead_code)] +fn returning_expr() -> Option<()> { + foo(1); Some(()) +} + +fn taking_multiple_units(a: (), b: ()) {} + fn main() { bad(); ok(); diff --git a/tests/ui/unit_arg.rs b/tests/ui/unit_arg.rs index d90c49f79de62..c6e465b2e4c4b 100644 --- a/tests/ui/unit_arg.rs +++ b/tests/ui/unit_arg.rs @@ -1,6 +1,6 @@ // run-rustfix #![warn(clippy::unit_arg)] -#![allow(unused_braces, clippy::no_effect, unused_must_use)] +#![allow(clippy::no_effect, unused_must_use, unused_variables)] use std::fmt::Debug; @@ -35,6 +35,7 @@ fn bad() { b.bar({ 1; }); + taking_multiple_units(foo(0), foo(1)); } fn ok() { @@ -65,6 +66,13 @@ mod issue_2945 { } } +#[allow(dead_code)] +fn returning_expr() -> Option<()> { + Some(foo(1)) +} + +fn taking_multiple_units(a: (), b: ()) {} + fn main() { bad(); ok(); diff --git a/tests/ui/unit_arg.stderr b/tests/ui/unit_arg.stderr index 21ccc684ea9de..ce9ab2f1271a7 100644 --- a/tests/ui/unit_arg.stderr +++ b/tests/ui/unit_arg.stderr @@ -1,79 +1,141 @@ error: passing a unit value to a function - --> $DIR/unit_arg.rs:24:9 + --> $DIR/unit_arg.rs:24:5 | LL | foo({}); - | ^^ + | ^^^^^^^ | = note: `-D clippy::unit-arg` implied by `-D warnings` -help: if you intended to pass a unit value, use a unit literal instead +help: move the expressions in front of the call... + | +LL | {}; foo({}); + | ^^^ +help: ...and use unit literals instead | LL | foo(()); | ^^ error: passing a unit value to a function - --> $DIR/unit_arg.rs:25:9 + --> $DIR/unit_arg.rs:25:5 | -LL | foo({ - | _________^ +LL | / foo({ LL | | 1; LL | | }); - | |_____^ + | |______^ + | +help: move the expressions in front of the call... + | +LL | { +LL | 1; +LL | }; foo({ | -help: if you intended to pass a unit value, use a unit literal instead +help: ...and use unit literals instead | LL | foo(()); | ^^ error: passing a unit value to a function - --> $DIR/unit_arg.rs:28:9 + --> $DIR/unit_arg.rs:28:5 | LL | foo(foo(1)); - | ^^^^^^ + | ^^^^^^^^^^^ | -help: if you intended to pass a unit value, use a unit literal instead +help: move the expressions in front of the call... + | +LL | foo(1); foo(foo(1)); + | ^^^^^^^ +help: ...and use unit literals instead | LL | foo(()); | ^^ error: passing a unit value to a function - --> $DIR/unit_arg.rs:29:9 + --> $DIR/unit_arg.rs:29:5 | -LL | foo({ - | _________^ +LL | / foo({ LL | | foo(1); LL | | foo(2); LL | | }); - | |_____^ + | |______^ + | +help: move the expressions in front of the call... + | +LL | { +LL | foo(1); +LL | foo(2); +LL | }; foo({ | -help: if you intended to pass a unit value, use a unit literal instead +help: ...and use unit literals instead | LL | foo(()); | ^^ error: passing a unit value to a function - --> $DIR/unit_arg.rs:33:10 + --> $DIR/unit_arg.rs:33:5 | LL | foo3({}, 2, 2); - | ^^ + | ^^^^^^^^^^^^^^ + | +help: move the expressions in front of the call... | -help: if you intended to pass a unit value, use a unit literal instead +LL | {}; foo3({}, 2, 2); + | ^^^ +help: ...and use unit literals instead | LL | foo3((), 2, 2); | ^^ error: passing a unit value to a function - --> $DIR/unit_arg.rs:35:11 + --> $DIR/unit_arg.rs:35:5 | -LL | b.bar({ - | ___________^ +LL | / b.bar({ LL | | 1; LL | | }); - | |_____^ + | |______^ + | +help: move the expressions in front of the call... | -help: if you intended to pass a unit value, use a unit literal instead +LL | { +LL | 1; +LL | }; b.bar({ + | +help: ...and use unit literals instead | LL | b.bar(()); | ^^ -error: aborting due to 6 previous errors +error: passing a unit value to a function + --> $DIR/unit_arg.rs:38:5 + | +LL | taking_multiple_units(foo(0), foo(1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expressions in front of the call... + | +LL | foo(0); foo(1); taking_multiple_units(foo(0), foo(1)); + | ^^^^^^^^^^^^^^^ +help: ...and use unit literals instead + | +LL | taking_multiple_units((), foo(1)); + | ^^ +help: ...and use unit literals instead + | +LL | taking_multiple_units(foo(0), ()); + | ^^ + +error: passing a unit value to a function + --> $DIR/unit_arg.rs:71:5 + | +LL | Some(foo(1)) + | ^^^^^^^^^^^^ + | +help: move the expressions in front of the call... + | +LL | foo(1); Some(foo(1)) + | ^^^^^^^ +help: ...and use unit literals instead + | +LL | Some(()) + | ^^ + +error: aborting due to 8 previous errors From a1a1a4b82a35b810570dbf7d2ee7f00896bee232 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Mon, 26 Aug 2019 21:43:29 +0200 Subject: [PATCH 17/72] Use multiple span_suggestions instead of multipart_suggestion multipart suggestions aren't autofixable by rustfix yet --- clippy_lints/src/types.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/types.rs b/clippy_lints/src/types.rs index 8fcca4b7bb9ca..3fbea77757dae 100644 --- a/clippy_lints/src/types.rs +++ b/clippy_lints/src/types.rs @@ -812,14 +812,14 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitArg { ), applicability, ); - db.multipart_suggestion( - "...and use unit literals instead", - args_to_recover - .iter() - .map(|arg| (arg.span, "()".to_string())) - .collect::>(), - applicability, - ); + for arg in args_to_recover { + db.span_suggestion( + arg.span, + "...and use unit literals instead", + "()".to_string(), + applicability, + ); + } }); } }, From 0f69cafc2dd77d573e24870887a4a13cfe50515a Mon Sep 17 00:00:00 2001 From: flip1995 Date: Mon, 17 Feb 2020 18:10:59 +0100 Subject: [PATCH 18/72] Rework suggestion generation and use multipart_suggestion again --- clippy_lints/src/types.rs | 55 +++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/clippy_lints/src/types.rs b/clippy_lints/src/types.rs index 3fbea77757dae..c95bd5d72bcd6 100644 --- a/clippy_lints/src/types.rs +++ b/clippy_lints/src/types.rs @@ -794,32 +794,43 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitArg { if !args_to_recover.is_empty() { let mut applicability = Applicability::MachineApplicable; span_lint_and_then(cx, UNIT_ARG, expr.span, "passing a unit value to a function", |db| { + let sugg = args_to_recover + .iter() + .enumerate() + .map(|(i, arg)| { + let indent = if i == 0 { + 0 + } else { + indent_of(cx, expr.span).unwrap_or(0) + }; + format!( + "{}{};", + " ".repeat(indent), + snippet_block_with_applicability( + cx, + arg.span, + "..", + Some(expr.span), + &mut applicability + ) + ) + }) + .collect::>() + .join("\n"); db.span_suggestion( expr.span.with_hi(expr.span.lo()), - "move the expressions in front of the call...", - format!( - "{} ", - args_to_recover - .iter() - .map(|arg| { - format!( - "{};", - snippet_with_applicability(cx, arg.span, "..", &mut applicability) - ) - }) - .collect::>() - .join(" ") - ), + &format!("{}move the expressions in front of the call...", or), + format!("{}\n", sugg), + applicability, + ); + db.multipart_suggestion( + "...and use unit literals instead", + args_to_recover + .iter() + .map(|arg| (arg.span, "()".to_string())) + .collect::>(), applicability, ); - for arg in args_to_recover { - db.span_suggestion( - arg.span, - "...and use unit literals instead", - "()".to_string(), - applicability, - ); - } }); } }, From f9c325f5b657e0c37ba2016a51cddbeab7f7693f Mon Sep 17 00:00:00 2001 From: flip1995 Date: Mon, 17 Feb 2020 18:11:50 +0100 Subject: [PATCH 19/72] Suggest to remove the semicolon of the last stmt in a block --- clippy_lints/src/types.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/clippy_lints/src/types.rs b/clippy_lints/src/types.rs index c95bd5d72bcd6..51d7d9b3ab713 100644 --- a/clippy_lints/src/types.rs +++ b/clippy_lints/src/types.rs @@ -794,6 +794,36 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitArg { if !args_to_recover.is_empty() { let mut applicability = Applicability::MachineApplicable; span_lint_and_then(cx, UNIT_ARG, expr.span, "passing a unit value to a function", |db| { + let mut or = ""; + args_to_recover + .iter() + .filter_map(|arg| { + if_chain! { + if let ExprKind::Block(block, _) = arg.kind; + if block.expr.is_none(); + if let Some(last_stmt) = block.stmts.iter().last(); + if let StmtKind::Semi(last_expr) = last_stmt.kind; + if let Some(snip) = snippet_opt(cx, last_expr.span); + then { + Some(( + last_stmt.span, + snip, + )) + } + else { + None + } + } + }) + .for_each(|(span, sugg)| { + db.span_suggestion( + span, + "remove the semicolon from the last statement in the block", + sugg, + Applicability::MaybeIncorrect, + ); + or = "or "; + }); let sugg = args_to_recover .iter() .enumerate() From 4c9cefa12232aa0224b1680f51654fe10f5cf3b7 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Tue, 18 Feb 2020 09:51:52 +0100 Subject: [PATCH 20/72] Move linting out in its own function --- clippy_lints/src/types.rs | 171 ++++++++++++++++++++------------------ 1 file changed, 91 insertions(+), 80 deletions(-) diff --git a/clippy_lints/src/types.rs b/clippy_lints/src/types.rs index 51d7d9b3ab713..6866635b904be 100644 --- a/clippy_lints/src/types.rs +++ b/clippy_lints/src/types.rs @@ -779,89 +779,22 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitArg { match expr.kind { ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args) => { - let mut args_to_recover = vec![]; - for arg in args { - if is_unit(cx.tables.expr_ty(arg)) && !is_unit_literal(arg) { - if let ExprKind::Match(.., match_source) = &arg.kind { - if *match_source == MatchSource::TryDesugar { - continue; + let args_to_recover = args + .iter() + .filter(|arg| { + if is_unit(cx.tables.expr_ty(arg)) && !is_unit_literal(arg) { + if let ExprKind::Match(.., MatchSource::TryDesugar) = &arg.kind { + false + } else { + true } + } else { + false } - - args_to_recover.push(arg); - } - } + }) + .collect::>(); if !args_to_recover.is_empty() { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_then(cx, UNIT_ARG, expr.span, "passing a unit value to a function", |db| { - let mut or = ""; - args_to_recover - .iter() - .filter_map(|arg| { - if_chain! { - if let ExprKind::Block(block, _) = arg.kind; - if block.expr.is_none(); - if let Some(last_stmt) = block.stmts.iter().last(); - if let StmtKind::Semi(last_expr) = last_stmt.kind; - if let Some(snip) = snippet_opt(cx, last_expr.span); - then { - Some(( - last_stmt.span, - snip, - )) - } - else { - None - } - } - }) - .for_each(|(span, sugg)| { - db.span_suggestion( - span, - "remove the semicolon from the last statement in the block", - sugg, - Applicability::MaybeIncorrect, - ); - or = "or "; - }); - let sugg = args_to_recover - .iter() - .enumerate() - .map(|(i, arg)| { - let indent = if i == 0 { - 0 - } else { - indent_of(cx, expr.span).unwrap_or(0) - }; - format!( - "{}{};", - " ".repeat(indent), - snippet_block_with_applicability( - cx, - arg.span, - "..", - Some(expr.span), - &mut applicability - ) - ) - }) - .collect::>() - .join("\n"); - db.span_suggestion( - expr.span.with_hi(expr.span.lo()), - &format!("{}move the expressions in front of the call...", or), - format!("{}\n", sugg), - applicability, - ); - db.multipart_suggestion( - "...and use unit literals instead", - args_to_recover - .iter() - .map(|arg| (arg.span, "()".to_string())) - .collect::>(), - applicability, - ); - }); + lint_unit_args(cx, expr, &args_to_recover); } }, _ => (), @@ -869,6 +802,84 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitArg { } } +fn lint_unit_args(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args_to_recover: &[&Expr<'_>]) { + let mut applicability = Applicability::MachineApplicable; + let (singular, plural) = if args_to_recover.len() > 1 { + ("", "s") + } else { + ("a ", "") + }; + span_lint_and_then( + cx, + UNIT_ARG, + expr.span, + &format!("passing {}unit value{} to a function", singular, plural), + |db| { + let mut or = ""; + args_to_recover + .iter() + .filter_map(|arg| { + if_chain! { + if let ExprKind::Block(block, _) = arg.kind; + if block.expr.is_none(); + if let Some(last_stmt) = block.stmts.iter().last(); + if let StmtKind::Semi(last_expr) = last_stmt.kind; + if let Some(snip) = snippet_opt(cx, last_expr.span); + then { + Some(( + last_stmt.span, + snip, + )) + } + else { + None + } + } + }) + .for_each(|(span, sugg)| { + db.span_suggestion( + span, + "remove the semicolon from the last statement in the block", + sugg, + Applicability::MaybeIncorrect, + ); + or = "or "; + }); + let sugg = args_to_recover + .iter() + .enumerate() + .map(|(i, arg)| { + let indent = if i == 0 { + 0 + } else { + indent_of(cx, expr.span).unwrap_or(0) + }; + format!( + "{}{};", + " ".repeat(indent), + snippet_block_with_applicability(cx, arg.span, "..", Some(expr.span), &mut applicability) + ) + }) + .collect::>() + .join("\n"); + db.span_suggestion( + expr.span.with_hi(expr.span.lo()), + &format!("{}move the expression{} in front of the call...", or, plural), + format!("{}\n", sugg), + applicability, + ); + db.multipart_suggestion( + &format!("...and use {}unit literal{} instead", singular, plural), + args_to_recover + .iter() + .map(|arg| (arg.span, "()".to_string())) + .collect::>(), + applicability, + ); + }, + ); +} + fn is_questionmark_desugar_marked_call(expr: &Expr<'_>) -> bool { use rustc_span::hygiene::DesugaringKind; if let ExprKind::Call(ref callee, _) = expr.kind { From 6d15a149640e5647ce232690d54b540346fa1641 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Mon, 17 Feb 2020 18:12:01 +0100 Subject: [PATCH 21/72] Update test files --- tests/ui/unit_arg.fixed | 79 -------------------- tests/ui/unit_arg.rs | 15 +++- tests/ui/unit_arg.stderr | 152 ++++++++++++++++++++++++++++----------- 3 files changed, 125 insertions(+), 121 deletions(-) delete mode 100644 tests/ui/unit_arg.fixed diff --git a/tests/ui/unit_arg.fixed b/tests/ui/unit_arg.fixed deleted file mode 100644 index 67c6bdf8873b6..0000000000000 --- a/tests/ui/unit_arg.fixed +++ /dev/null @@ -1,79 +0,0 @@ -// run-rustfix -#![warn(clippy::unit_arg)] -#![allow(clippy::no_effect, unused_must_use, unused_variables)] - -use std::fmt::Debug; - -fn foo(t: T) { - println!("{:?}", t); -} - -fn foo3(t1: T1, t2: T2, t3: T3) { - println!("{:?}, {:?}, {:?}", t1, t2, t3); -} - -struct Bar; - -impl Bar { - fn bar(&self, t: T) { - println!("{:?}", t); - } -} - -fn bad() { - {}; foo(()); - { - 1; - }; foo(()); - foo(1); foo(()); - { - foo(1); - foo(2); - }; foo(()); - {}; foo3((), 2, 2); - let b = Bar; - { - 1; - }; b.bar(()); - foo(0); foo(1); taking_multiple_units((), ()); -} - -fn ok() { - foo(()); - foo(1); - foo({ 1 }); - foo3("a", 3, vec![3]); - let b = Bar; - b.bar({ 1 }); - b.bar(()); - question_mark(); -} - -fn question_mark() -> Result<(), ()> { - Ok(Ok(())?)?; - Ok(Ok(()))??; - Ok(()) -} - -#[allow(dead_code)] -mod issue_2945 { - fn unit_fn() -> Result<(), i32> { - Ok(()) - } - - fn fallible() -> Result<(), i32> { - Ok(unit_fn()?) - } -} - -#[allow(dead_code)] -fn returning_expr() -> Option<()> { - foo(1); Some(()) -} - -fn taking_multiple_units(a: (), b: ()) {} - -fn main() { - bad(); - ok(); -} diff --git a/tests/ui/unit_arg.rs b/tests/ui/unit_arg.rs index c6e465b2e4c4b..7d1b99fedc965 100644 --- a/tests/ui/unit_arg.rs +++ b/tests/ui/unit_arg.rs @@ -1,4 +1,3 @@ -// run-rustfix #![warn(clippy::unit_arg)] #![allow(clippy::no_effect, unused_must_use, unused_variables)] @@ -36,6 +35,20 @@ fn bad() { 1; }); taking_multiple_units(foo(0), foo(1)); + taking_multiple_units(foo(0), { + foo(1); + foo(2); + }); + taking_multiple_units( + { + foo(0); + foo(1); + }, + { + foo(2); + foo(3); + }, + ); } fn ok() { diff --git a/tests/ui/unit_arg.stderr b/tests/ui/unit_arg.stderr index ce9ab2f1271a7..145b3c62b0627 100644 --- a/tests/ui/unit_arg.stderr +++ b/tests/ui/unit_arg.stderr @@ -1,55 +1,59 @@ error: passing a unit value to a function - --> $DIR/unit_arg.rs:24:5 + --> $DIR/unit_arg.rs:23:5 | LL | foo({}); | ^^^^^^^ | = note: `-D clippy::unit-arg` implied by `-D warnings` -help: move the expressions in front of the call... +help: move the expression in front of the call... | -LL | {}; foo({}); - | ^^^ -help: ...and use unit literals instead +LL | {}; + | +help: ...and use a unit literal instead | LL | foo(()); | ^^ error: passing a unit value to a function - --> $DIR/unit_arg.rs:25:5 + --> $DIR/unit_arg.rs:24:5 | LL | / foo({ LL | | 1; LL | | }); | |______^ | -help: move the expressions in front of the call... +help: remove the semicolon from the last statement in the block + | +LL | 1 + | +help: or move the expression in front of the call... | LL | { LL | 1; -LL | }; foo({ +LL | }; | -help: ...and use unit literals instead +help: ...and use a unit literal instead | LL | foo(()); | ^^ error: passing a unit value to a function - --> $DIR/unit_arg.rs:28:5 + --> $DIR/unit_arg.rs:27:5 | LL | foo(foo(1)); | ^^^^^^^^^^^ | -help: move the expressions in front of the call... +help: move the expression in front of the call... | -LL | foo(1); foo(foo(1)); - | ^^^^^^^ -help: ...and use unit literals instead +LL | foo(1); + | +help: ...and use a unit literal instead | LL | foo(()); | ^^ error: passing a unit value to a function - --> $DIR/unit_arg.rs:29:5 + --> $DIR/unit_arg.rs:28:5 | LL | / foo({ LL | | foo(1); @@ -57,85 +61,151 @@ LL | | foo(2); LL | | }); | |______^ | -help: move the expressions in front of the call... +help: remove the semicolon from the last statement in the block + | +LL | foo(2) + | +help: or move the expression in front of the call... | LL | { LL | foo(1); LL | foo(2); -LL | }; foo({ +LL | }; | -help: ...and use unit literals instead +help: ...and use a unit literal instead | LL | foo(()); | ^^ error: passing a unit value to a function - --> $DIR/unit_arg.rs:33:5 + --> $DIR/unit_arg.rs:32:5 | LL | foo3({}, 2, 2); | ^^^^^^^^^^^^^^ | -help: move the expressions in front of the call... +help: move the expression in front of the call... | -LL | {}; foo3({}, 2, 2); - | ^^^ -help: ...and use unit literals instead +LL | {}; + | +help: ...and use a unit literal instead | LL | foo3((), 2, 2); | ^^ error: passing a unit value to a function - --> $DIR/unit_arg.rs:35:5 + --> $DIR/unit_arg.rs:34:5 | LL | / b.bar({ LL | | 1; LL | | }); | |______^ | -help: move the expressions in front of the call... +help: remove the semicolon from the last statement in the block + | +LL | 1 + | +help: or move the expression in front of the call... | LL | { LL | 1; -LL | }; b.bar({ +LL | }; | -help: ...and use unit literals instead +help: ...and use a unit literal instead | LL | b.bar(()); | ^^ -error: passing a unit value to a function - --> $DIR/unit_arg.rs:38:5 +error: passing unit values to a function + --> $DIR/unit_arg.rs:37:5 | LL | taking_multiple_units(foo(0), foo(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: move the expressions in front of the call... | -LL | foo(0); foo(1); taking_multiple_units(foo(0), foo(1)); - | ^^^^^^^^^^^^^^^ +LL | foo(0); +LL | foo(1); + | help: ...and use unit literals instead | -LL | taking_multiple_units((), foo(1)); - | ^^ +LL | taking_multiple_units((), ()); + | ^^ ^^ + +error: passing unit values to a function + --> $DIR/unit_arg.rs:38:5 + | +LL | / taking_multiple_units(foo(0), { +LL | | foo(1); +LL | | foo(2); +LL | | }); + | |______^ + | +help: remove the semicolon from the last statement in the block + | +LL | foo(2) + | +help: or move the expressions in front of the call... + | +LL | foo(0); +LL | { +LL | foo(1); +LL | foo(2); +LL | }; + | help: ...and use unit literals instead | -LL | taking_multiple_units(foo(0), ()); - | ^^ +LL | taking_multiple_units((), ()); + | ^^ ^^ + +error: passing unit values to a function + --> $DIR/unit_arg.rs:42:5 + | +LL | / taking_multiple_units( +LL | | { +LL | | foo(0); +LL | | foo(1); +... | +LL | | }, +LL | | ); + | |_____^ + | +help: remove the semicolon from the last statement in the block + | +LL | foo(1) + | +help: remove the semicolon from the last statement in the block + | +LL | foo(3) + | +help: or move the expressions in front of the call... + | +LL | { +LL | foo(0); +LL | foo(1); +LL | }; +LL | { +LL | foo(2); + ... +help: ...and use unit literals instead + | +LL | (), +LL | (), + | error: passing a unit value to a function - --> $DIR/unit_arg.rs:71:5 + --> $DIR/unit_arg.rs:84:5 | LL | Some(foo(1)) | ^^^^^^^^^^^^ | -help: move the expressions in front of the call... +help: move the expression in front of the call... | -LL | foo(1); Some(foo(1)) - | ^^^^^^^ -help: ...and use unit literals instead +LL | foo(1); + | +help: ...and use a unit literal instead | LL | Some(()) | ^^ -error: aborting due to 8 previous errors +error: aborting due to 10 previous errors From a9cde3a804808e82402888a20878053404a8eded Mon Sep 17 00:00:00 2001 From: flip1995 Date: Sun, 31 May 2020 18:45:16 +0200 Subject: [PATCH 22/72] Don't suggest to move empty blocks --- clippy_lints/src/types.rs | 43 +++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/types.rs b/clippy_lints/src/types.rs index 6866635b904be..5ca30d598eb15 100644 --- a/clippy_lints/src/types.rs +++ b/clippy_lints/src/types.rs @@ -10,7 +10,7 @@ use rustc_errors::{Applicability, DiagnosticBuilder}; use rustc_hir as hir; use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, FnKind, NestedVisitorMap, Visitor}; use rustc_hir::{ - BinOpKind, Body, Expr, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericParamKind, HirId, ImplItem, + BinOpKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericParamKind, HirId, ImplItem, ImplItemKind, Item, ItemKind, Lifetime, Local, MatchSource, MutTy, Mutability, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind, TyKind, UnOp, }; @@ -29,10 +29,10 @@ use rustc_typeck::hir_ty_to_ty; use crate::consts::{constant, Constant}; use crate::utils::paths; use crate::utils::{ - clip, comparisons, differing_macro_contexts, higher, in_constant, int_bits, is_type_diagnostic_item, + clip, comparisons, differing_macro_contexts, higher, in_constant, indent_of, int_bits, is_type_diagnostic_item, last_path_segment, match_def_path, match_path, method_chain_args, multispan_sugg, numeric_literal::NumericLiteral, - qpath_res, same_tys, sext, snippet, snippet_opt, snippet_with_applicability, snippet_with_macro_callsite, - span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, unsext, + qpath_res, same_tys, sext, snippet, snippet_block_with_applicability, snippet_opt, snippet_with_applicability, + snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, unsext, }; declare_clippy_lint! { @@ -847,6 +847,7 @@ fn lint_unit_args(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args_to_recover: &[ }); let sugg = args_to_recover .iter() + .filter(|arg| !is_empty_block(arg)) .enumerate() .map(|(i, arg)| { let indent = if i == 0 { @@ -860,16 +861,20 @@ fn lint_unit_args(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args_to_recover: &[ snippet_block_with_applicability(cx, arg.span, "..", Some(expr.span), &mut applicability) ) }) - .collect::>() - .join("\n"); - db.span_suggestion( - expr.span.with_hi(expr.span.lo()), - &format!("{}move the expression{} in front of the call...", or, plural), - format!("{}\n", sugg), - applicability, - ); + .collect::>(); + let mut and = ""; + if !sugg.is_empty() { + let plural = if sugg.len() > 1 { "s" } else { "" }; + db.span_suggestion( + expr.span.with_hi(expr.span.lo()), + &format!("{}move the expression{} in front of the call...", or, plural), + format!("{}\n", sugg.join("\n")), + applicability, + ); + and = "...and " + } db.multipart_suggestion( - &format!("...and use {}unit literal{} instead", singular, plural), + &format!("{}use {}unit literal{} instead", and, singular, plural), args_to_recover .iter() .map(|arg| (arg.span, "()".to_string())) @@ -880,6 +885,18 @@ fn lint_unit_args(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args_to_recover: &[ ); } +fn is_empty_block(expr: &Expr<'_>) -> bool { + matches!( + expr.kind, + ExprKind::Block( + Block { + stmts: &[], expr: None, .. + }, + _, + ) + ) +} + fn is_questionmark_desugar_marked_call(expr: &Expr<'_>) -> bool { use rustc_span::hygiene::DesugaringKind; if let ExprKind::Call(ref callee, _) = expr.kind { From 77dd0ea62aa6a2af70da4c5e05de064eee182a6c Mon Sep 17 00:00:00 2001 From: flip1995 Date: Sun, 31 May 2020 19:29:36 +0200 Subject: [PATCH 23/72] Add tests for empty blocks --- tests/ui/unit_arg.rs | 2 -- tests/ui/unit_arg.stderr | 46 +++++------------------- tests/ui/unit_arg_empty_blocks.rs | 26 ++++++++++++++ tests/ui/unit_arg_empty_blocks.stderr | 51 +++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 40 deletions(-) create mode 100644 tests/ui/unit_arg_empty_blocks.rs create mode 100644 tests/ui/unit_arg_empty_blocks.stderr diff --git a/tests/ui/unit_arg.rs b/tests/ui/unit_arg.rs index 7d1b99fedc965..2992abae775b8 100644 --- a/tests/ui/unit_arg.rs +++ b/tests/ui/unit_arg.rs @@ -20,7 +20,6 @@ impl Bar { } fn bad() { - foo({}); foo({ 1; }); @@ -29,7 +28,6 @@ fn bad() { foo(1); foo(2); }); - foo3({}, 2, 2); let b = Bar; b.bar({ 1; diff --git a/tests/ui/unit_arg.stderr b/tests/ui/unit_arg.stderr index 145b3c62b0627..56f6a855dfa55 100644 --- a/tests/ui/unit_arg.stderr +++ b/tests/ui/unit_arg.stderr @@ -1,27 +1,12 @@ error: passing a unit value to a function --> $DIR/unit_arg.rs:23:5 | -LL | foo({}); - | ^^^^^^^ - | - = note: `-D clippy::unit-arg` implied by `-D warnings` -help: move the expression in front of the call... - | -LL | {}; - | -help: ...and use a unit literal instead - | -LL | foo(()); - | ^^ - -error: passing a unit value to a function - --> $DIR/unit_arg.rs:24:5 - | LL | / foo({ LL | | 1; LL | | }); | |______^ | + = note: `-D clippy::unit-arg` implied by `-D warnings` help: remove the semicolon from the last statement in the block | LL | 1 @@ -38,7 +23,7 @@ LL | foo(()); | ^^ error: passing a unit value to a function - --> $DIR/unit_arg.rs:27:5 + --> $DIR/unit_arg.rs:26:5 | LL | foo(foo(1)); | ^^^^^^^^^^^ @@ -53,7 +38,7 @@ LL | foo(()); | ^^ error: passing a unit value to a function - --> $DIR/unit_arg.rs:28:5 + --> $DIR/unit_arg.rs:27:5 | LL | / foo({ LL | | foo(1); @@ -80,21 +65,6 @@ LL | foo(()); error: passing a unit value to a function --> $DIR/unit_arg.rs:32:5 | -LL | foo3({}, 2, 2); - | ^^^^^^^^^^^^^^ - | -help: move the expression in front of the call... - | -LL | {}; - | -help: ...and use a unit literal instead - | -LL | foo3((), 2, 2); - | ^^ - -error: passing a unit value to a function - --> $DIR/unit_arg.rs:34:5 - | LL | / b.bar({ LL | | 1; LL | | }); @@ -116,7 +86,7 @@ LL | b.bar(()); | ^^ error: passing unit values to a function - --> $DIR/unit_arg.rs:37:5 + --> $DIR/unit_arg.rs:35:5 | LL | taking_multiple_units(foo(0), foo(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -132,7 +102,7 @@ LL | taking_multiple_units((), ()); | ^^ ^^ error: passing unit values to a function - --> $DIR/unit_arg.rs:38:5 + --> $DIR/unit_arg.rs:36:5 | LL | / taking_multiple_units(foo(0), { LL | | foo(1); @@ -158,7 +128,7 @@ LL | taking_multiple_units((), ()); | ^^ ^^ error: passing unit values to a function - --> $DIR/unit_arg.rs:42:5 + --> $DIR/unit_arg.rs:40:5 | LL | / taking_multiple_units( LL | | { @@ -193,7 +163,7 @@ LL | (), | error: passing a unit value to a function - --> $DIR/unit_arg.rs:84:5 + --> $DIR/unit_arg.rs:82:5 | LL | Some(foo(1)) | ^^^^^^^^^^^^ @@ -207,5 +177,5 @@ help: ...and use a unit literal instead LL | Some(()) | ^^ -error: aborting due to 10 previous errors +error: aborting due to 8 previous errors diff --git a/tests/ui/unit_arg_empty_blocks.rs b/tests/ui/unit_arg_empty_blocks.rs new file mode 100644 index 0000000000000..18a31eb3deee2 --- /dev/null +++ b/tests/ui/unit_arg_empty_blocks.rs @@ -0,0 +1,26 @@ +#![warn(clippy::unit_arg)] +#![allow(clippy::no_effect, unused_must_use, unused_variables)] + +use std::fmt::Debug; + +fn foo(t: T) { + println!("{:?}", t); +} + +fn foo3(t1: T1, t2: T2, t3: T3) { + println!("{:?}, {:?}, {:?}", t1, t2, t3); +} + +fn bad() { + foo({}); + foo3({}, 2, 2); + taking_two_units({}, foo(0)); + taking_three_units({}, foo(0), foo(1)); +} + +fn taking_two_units(a: (), b: ()) {} +fn taking_three_units(a: (), b: (), c: ()) {} + +fn main() { + bad(); +} diff --git a/tests/ui/unit_arg_empty_blocks.stderr b/tests/ui/unit_arg_empty_blocks.stderr new file mode 100644 index 0000000000000..bb58483584b3e --- /dev/null +++ b/tests/ui/unit_arg_empty_blocks.stderr @@ -0,0 +1,51 @@ +error: passing a unit value to a function + --> $DIR/unit_arg_empty_blocks.rs:15:5 + | +LL | foo({}); + | ^^^^--^ + | | + | help: use a unit literal instead: `()` + | + = note: `-D clippy::unit-arg` implied by `-D warnings` + +error: passing a unit value to a function + --> $DIR/unit_arg_empty_blocks.rs:16:5 + | +LL | foo3({}, 2, 2); + | ^^^^^--^^^^^^^ + | | + | help: use a unit literal instead: `()` + +error: passing unit values to a function + --> $DIR/unit_arg_empty_blocks.rs:17:5 + | +LL | taking_two_units({}, foo(0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expression in front of the call... + | +LL | foo(0); + | +help: ...and use unit literals instead + | +LL | taking_two_units((), ()); + | ^^ ^^ + +error: passing unit values to a function + --> $DIR/unit_arg_empty_blocks.rs:18:5 + | +LL | taking_three_units({}, foo(0), foo(1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expressions in front of the call... + | +LL | foo(0); +LL | foo(1); + | +help: ...and use unit literals instead + | +LL | taking_three_units((), (), ()); + | ^^ ^^ ^^ + +error: aborting due to 4 previous errors + From 14e9100543166e48acd0ea00233249d2cddf09c2 Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Mon, 25 May 2020 00:41:13 +0200 Subject: [PATCH 24/72] cargo-ui tests: check that /src exists before processing test --- tests/compile-test.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 7bd5f09f333dd..194354b291fda 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -181,8 +181,15 @@ fn run_ui_cargo(config: &mut compiletest::Config) { } let src_path = case.path().join("src"); - env::set_current_dir(&src_path)?; + // When switching between branches, if the previous branch had a test + // that the current branch does not have, the directory is not removed + // because an ignored Cargo.lock file exists. + if !src_path.exists() { + continue; + } + + env::set_current_dir(&src_path)?; for file in fs::read_dir(&src_path)? { let file = file?; if file.file_type()?.is_dir() { From 7e843515d9525b6389c3fc1bcfa6ae046c1351dc Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Thu, 14 May 2020 15:06:05 -0700 Subject: [PATCH 25/72] Created lint --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 4 ++++ clippy_lints/src/sort_by_key_reverse.rs | 28 +++++++++++++++++++++++++ src/lintlist/mod.rs | 7 +++++++ tests/ui/sort_by_key_reverse.rs | 5 +++++ 5 files changed, 45 insertions(+) create mode 100644 clippy_lints/src/sort_by_key_reverse.rs create mode 100644 tests/ui/sort_by_key_reverse.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f7dae3dcfff0f..c00f84bdb8570 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1555,6 +1555,7 @@ Released 2018-09-13 [`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else [`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 +[`sort_by_key_reverse`]: https://rust-lang.github.io/rust-clippy/master/index.html#sort_by_key_reverse [`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.rs b/clippy_lints/src/lib.rs index 38cfa212d9f48..f51855badff81 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -304,6 +304,7 @@ mod serde_api; mod shadow; mod single_component_path_imports; mod slow_vector_initialization; +mod sort_by_key_reverse; mod strings; mod suspicious_trait_impl; mod swap; @@ -779,6 +780,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &shadow::SHADOW_UNRELATED, &single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS, &slow_vector_initialization::SLOW_VECTOR_INITIALIZATION, + &sort_by_key_reverse::SORT_BY_KEY_REVERSE, &strings::STRING_ADD, &strings::STRING_ADD_ASSIGN, &strings::STRING_LIT_AS_BYTES, @@ -1391,6 +1393,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&serde_api::SERDE_API_MISUSE), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), + LintId::of(&sort_by_key_reverse::SORT_BY_KEY_REVERSE), LintId::of(&strings::STRING_LIT_AS_BYTES), LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), @@ -1592,6 +1595,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&ranges::RANGE_ZIP_WITH_LEN), LintId::of(&reference::DEREF_ADDROF), LintId::of(&reference::REF_IN_DEREF), + LintId::of(&sort_by_key_reverse::SORT_BY_KEY_REVERSE), LintId::of(&swap::MANUAL_SWAP), LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT), LintId::of(&transmute::CROSSPOINTER_TRANSMUTE), diff --git a/clippy_lints/src/sort_by_key_reverse.rs b/clippy_lints/src/sort_by_key_reverse.rs new file mode 100644 index 0000000000000..65830afd0f8d0 --- /dev/null +++ b/clippy_lints/src/sort_by_key_reverse.rs @@ -0,0 +1,28 @@ +use rustc_lint::{LateLintPass, LateContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_hir::*; + +declare_clippy_lint! { + /// **What it does:** + /// + /// **Why is this bad?** + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // example code where clippy issues a warning + /// ``` + /// Use instead: + /// ```rust + /// // example code which does not raise clippy warning + /// ``` + pub SORT_BY_KEY_REVERSE, + complexity, + "default lint description" +} + +declare_lint_pass!(SortByKeyReverse => [SORT_BY_KEY_REVERSE]); + +impl LateLintPass<'_, '_> for SortByKeyReverse {} diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 69578732898fc..1b82f34c8634d 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1984,6 +1984,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "slow_vector_initialization", }, + Lint { + name: "sort_by_key_reverse", + group: "complexity", + desc: "default lint description", + deprecation: None, + module: "sort_by_key_reverse", + }, Lint { name: "string_add", group: "restriction", diff --git a/tests/ui/sort_by_key_reverse.rs b/tests/ui/sort_by_key_reverse.rs new file mode 100644 index 0000000000000..2338dc6e5940b --- /dev/null +++ b/tests/ui/sort_by_key_reverse.rs @@ -0,0 +1,5 @@ +#![warn(clippy::sort_by_key_reverse)] + +fn main() { + // test code goes here +} From 24847ea53e332853597aca2c7dfe48a9f3be1de8 Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Sat, 16 May 2020 13:50:33 -0700 Subject: [PATCH 26/72] Attempted start at sort_by_key_reverse lint --- clippy_lints/src/sort_by_key_reverse.rs | 71 +++++++++++++++++++++++-- src/lintlist/mod.rs | 2 +- tests/ui/sort_by_key_reverse.fixed | 0 tests/ui/sort_by_key_reverse.rs | 3 +- tests/ui/sort_by_key_reverse.stderr | 0 5 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 tests/ui/sort_by_key_reverse.fixed create mode 100644 tests/ui/sort_by_key_reverse.stderr diff --git a/clippy_lints/src/sort_by_key_reverse.rs b/clippy_lints/src/sort_by_key_reverse.rs index 65830afd0f8d0..7d7097a81253e 100644 --- a/clippy_lints/src/sort_by_key_reverse.rs +++ b/clippy_lints/src/sort_by_key_reverse.rs @@ -1,28 +1,91 @@ +use crate::utils::{match_type, span_lint_and_sugg}; +use crate::utils::paths; +use crate::utils::sugg::Sugg; +use if_chain::if_chain; +use rustc_errors::Applicability; use rustc_lint::{LateLintPass, LateContext}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_hir::*; declare_clippy_lint! { /// **What it does:** + /// Detects when people use `Vec::sort_by` and pass in a function + /// which compares the second argument to the first. /// /// **Why is this bad?** + /// It is more clear to use `Vec::sort_by_key` and `std::cmp::Reverse` /// /// **Known problems:** None. /// /// **Example:** /// /// ```rust - /// // example code where clippy issues a warning + /// vec.sort_by(|a, b| b.foo().cmp(&a.foo())); /// ``` /// Use instead: /// ```rust - /// // example code which does not raise clippy warning + /// vec.sort_by_key(|e| Reverse(e.foo())); /// ``` pub SORT_BY_KEY_REVERSE, complexity, - "default lint description" + "Use of `Vec::sort_by` when `Vec::sort_by_key` would be clearer" } declare_lint_pass!(SortByKeyReverse => [SORT_BY_KEY_REVERSE]); -impl LateLintPass<'_, '_> for SortByKeyReverse {} +struct LintTrigger { + vec_name: String, + closure_arg: String, + closure_reverse_body: String, + unstable: bool, +} + +fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option { + if_chain! { + 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(_, closure_decl, closure_body_id, _, _), .. }] = args; + if closure_decl.inputs.len() == 2; + if match_type(cx, &cx.tables.expr_ty(vec), &paths::VEC); + then { + let vec_name = Sugg::hir(cx, &args[0], "..").to_string(); + let unstable = name == "sort_unstable_by"; + Some(LintTrigger { vec_name, unstable, closure_arg: "e".to_string(), closure_reverse_body: "e".to_string() }) + } else { + None + } + } +} + +impl LateLintPass<'_, '_> for SortByKeyReverse { + fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { + println!("{:?}", expr); + span_lint_and_sugg( + cx, + SORT_BY_KEY_REVERSE, + expr.span, + "use Vec::sort_by_key here instead", + "try", + String::from("being a better person"), + Applicability::MachineApplicable, + ); + if let Some(trigger) = detect_lint(cx, expr) { + span_lint_and_sugg( + cx, + SORT_BY_KEY_REVERSE, + expr.span, + "use Vec::sort_by_key here instead", + "try", + format!( + "{}.sort{}_by_key(|{}| Reverse({}))", + trigger.vec_name, + if trigger.unstable { "_unstable" } else { "" }, + trigger.closure_arg, + trigger.closure_reverse_body, + ), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 1b82f34c8634d..b5d9ef0110e25 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1987,7 +1987,7 @@ pub static ref ALL_LINTS: Vec = vec![ Lint { name: "sort_by_key_reverse", group: "complexity", - desc: "default lint description", + desc: "Use of `Vec::sort_by` when `Vec::sort_by_key` would be clearer", deprecation: None, module: "sort_by_key_reverse", }, diff --git a/tests/ui/sort_by_key_reverse.fixed b/tests/ui/sort_by_key_reverse.fixed new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/ui/sort_by_key_reverse.rs b/tests/ui/sort_by_key_reverse.rs index 2338dc6e5940b..c0350f243c7be 100644 --- a/tests/ui/sort_by_key_reverse.rs +++ b/tests/ui/sort_by_key_reverse.rs @@ -1,5 +1,6 @@ #![warn(clippy::sort_by_key_reverse)] fn main() { - // test code goes here + let mut vec = vec![3, 6, 1, 2, 5]; + vec.sort_by(|a, b| b.cmp(a)); } diff --git a/tests/ui/sort_by_key_reverse.stderr b/tests/ui/sort_by_key_reverse.stderr new file mode 100644 index 0000000000000..e69de29bb2d1d From 8590ab4d46de4eb43e7ebd42cb2f13b0064573e6 Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Mon, 18 May 2020 21:48:35 -0700 Subject: [PATCH 27/72] More progress towards sort_by_key_reverse lint --- clippy_lints/src/lib.rs | 1 + clippy_lints/src/sort_by_key_reverse.rs | 131 ++++++++++++++++++++---- tests/ui/sort_by_key_reverse.fixed | 9 ++ tests/ui/sort_by_key_reverse.rs | 5 +- tests/ui/sort_by_key_reverse.stderr | 22 ++++ 5 files changed, 149 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index f51855badff81..e7a4c1ecaa954 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -998,6 +998,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box ptr_offset_with_cast::PtrOffsetWithCast); store.register_late_pass(|| box redundant_clone::RedundantClone); store.register_late_pass(|| box slow_vector_initialization::SlowVectorInit); + store.register_late_pass(|| box sort_by_key_reverse::SortByKeyReverse); store.register_late_pass(|| box types::RefToMut); store.register_late_pass(|| box assertions_on_constants::AssertionsOnConstants); store.register_late_pass(|| box missing_const_for_fn::MissingConstForFn); diff --git a/clippy_lints/src/sort_by_key_reverse.rs b/clippy_lints/src/sort_by_key_reverse.rs index 7d7097a81253e..d70391999a023 100644 --- a/clippy_lints/src/sort_by_key_reverse.rs +++ b/clippy_lints/src/sort_by_key_reverse.rs @@ -1,11 +1,12 @@ -use crate::utils::{match_type, span_lint_and_sugg}; +use crate::utils; use crate::utils::paths; use crate::utils::sugg::Sugg; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_lint::{LateLintPass, LateContext}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_hir::*; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::Ident; declare_clippy_lint! { /// **What it does:** @@ -40,18 +41,122 @@ struct LintTrigger { unstable: bool, } +/// Detect if the two expressions are mirrored (identical, except one +/// contains a and the other replaces it with b) +fn mirrored_exprs(cx: &LateContext<'_, '_>, a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident: &Ident) -> bool { + match (&a_expr.kind, &b_expr.kind) { + // Two boxes with mirrored contents + (ExprKind::Box(left_expr), ExprKind::Box(right_expr)) => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + // Two arrays with mirrored contents + (ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) + => left_exprs.iter().zip(right_exprs.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + // The two exprs are function calls. + // Check to see that the function itself and its arguments are mirrored + (ExprKind::Call(left_expr, left_args), ExprKind::Call(right_expr, right_args)) + => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident) + && left_args.iter().zip(right_args.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + // The two exprs are method calls. + // Check to see that the function is the same and the arguments are mirrored + // This is enough because the receiver of the method is listed in the arguments + (ExprKind::MethodCall(left_segment, _, left_args), ExprKind::MethodCall(right_segment, _, right_args)) + => left_segment.ident == right_segment.ident + && left_args.iter().zip(right_args.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + // Two tuples with mirrored contents + (ExprKind::Tup(left_exprs), ExprKind::Tup(right_exprs)) + => left_exprs.iter().zip(right_exprs.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + // Two binary ops, which are the same operation and which have mirrored arguments + (ExprKind::Binary(left_op, left_left, left_right), ExprKind::Binary(right_op, right_left, right_right)) + => left_op.node == right_op.node + && mirrored_exprs(cx, left_left, a_ident, right_left, b_ident) + && mirrored_exprs(cx, left_right, a_ident, right_right, b_ident), + // Two unary ops, which are the same operation and which have the same argument + (ExprKind::Unary(left_op, left_expr), ExprKind::Unary(right_op, right_expr)) + => left_op == right_op && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + // The two exprs are literals of some kind + (ExprKind::Lit(left_lit), ExprKind::Lit(right_lit)) => left_lit.node == right_lit.node, + (ExprKind::Cast(left_expr, _), ExprKind::Cast(right_expr, _)) + => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + (ExprKind::DropTemps(left), ExprKind::DropTemps(right)) => mirrored_exprs(cx, left, a_ident, right, b_ident), + (ExprKind::Block(left, _), ExprKind::Block(right, _)) => mirrored_blocks(cx, left, a_ident, right, b_ident), + (ExprKind::Field(left_expr, left_ident), ExprKind::Field(right_expr, right_ident)) + => left_ident.name == right_ident.name && mirrored_exprs(cx, left_expr, a_ident, right_expr, right_ident), + // The two exprs are `a` and `b`, directly + (ExprKind::Path(QPath::Resolved(_, Path { segments: &[PathSegment { ident: left_ident, .. }], .. },)), + ExprKind::Path(QPath::Resolved(_, Path { segments: &[PathSegment { ident: right_ident, .. }], .. },)), + ) => &left_ident == a_ident && &right_ident == b_ident, + // The two exprs are Paths to the same name (which is neither a nor b) + (ExprKind::Path(QPath::Resolved(_, Path { segments: left_segments, .. })), + ExprKind::Path(QPath::Resolved(_, Path { segments: right_segments, .. }))) + => left_segments.iter().zip(right_segments.iter()).all(|(left, right)| left.ident == right.ident) + && left_segments.iter().all(|seg| &seg.ident != a_ident && &seg.ident != b_ident), + // Matching expressions, but one or both is borrowed + (ExprKind::AddrOf(left_kind, Mutability::Not, left_expr), ExprKind::AddrOf(right_kind, Mutability::Not, right_expr)) + => left_kind == right_kind && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + (_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) + => mirrored_exprs(cx, a_expr, a_ident, right_expr, b_ident), + (ExprKind::AddrOf(_, Mutability::Not, left_expr), _) + => mirrored_exprs(cx, left_expr, a_ident, b_expr, b_ident), + // _ => false, + (left, right) => { + println!("{:?}\n{:?}", left, right); + false + }, + } +} + +/// Detect if the two blocks are mirrored (identical, except one +/// contains a and the other replaces it with b) +fn mirrored_blocks(cx: &LateContext<'_, '_>, a_block: &Block<'_>, a_ident: &Ident, b_block: &Block<'_>, b_ident: &Ident) -> bool { + match (a_block, b_block) { + (Block { stmts: left_stmts, expr: left_expr, .. }, + Block { stmts: right_stmts, expr: right_expr, .. }) + => left_stmts.iter().zip(right_stmts.iter()).all(|(left, right)| match (&left.kind, &right.kind) { + (StmtKind::Expr(left_expr), StmtKind::Expr(right_expr)) => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + (StmtKind::Semi(left_expr), StmtKind::Semi(right_expr)) => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + (StmtKind::Item(left_item), StmtKind::Item(right_item)) => left_item.id == right_item.id, + (StmtKind::Local(left), StmtKind::Local(right)) => mirrored_locals(cx, left, a_ident, right, b_ident), + _ => false, + }) && match (left_expr, right_expr) { + (None, None) => true, + (Some(left), Some(right)) => mirrored_exprs(cx, left, a_ident, right, b_ident), + _ => false, + }, + } +} + +/// Check that the two "Local"s (let statements) are equal +fn mirrored_locals(cx: &LateContext<'_, '_>, a_local: &Local<'_>, a_ident: &Ident, b_local: &Local<'_>, b_ident: &Ident) -> bool { + match (a_local, b_local) { + (Local { pat: left_pat, init: left_expr, .. }, Local { pat: right_pat, init: right_expr, .. }) + => match (left_expr, right_expr) { + (None, None) => true, + (Some(left), Some(right)) => mirrored_exprs(cx, left, a_ident, right, b_ident), + _ => false, + }, + } +} + fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option { if_chain! { 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(_, closure_decl, closure_body_id, _, _), .. }] = args; - if closure_decl.inputs.len() == 2; - if match_type(cx, &cx.tables.expr_ty(vec), &paths::VEC); + if let [vec, Expr { kind: ExprKind::Closure(_, _, closure_body_id, _, _), .. }] = args; + if utils::match_type(cx, &cx.tables.expr_ty(vec), &paths::VEC); + if let closure_body = cx.tcx.hir().body(*closure_body_id); + if let &[ + Param { pat: Pat { kind: PatKind::Binding(_, _, a_ident, _), .. }, ..}, + Param { pat: Pat { kind: PatKind::Binding(_, _, b_ident, _), .. }, .. } + ] = &closure_body.params; + if let ExprKind::MethodCall(method_path, _, [ref b_expr, ref a_expr]) = &closure_body.value.kind; + if method_path.ident.name.to_ident_string() == "cmp"; + if mirrored_exprs(&cx, &a_expr, &a_ident, &b_expr, &b_ident); then { let vec_name = Sugg::hir(cx, &args[0], "..").to_string(); let unstable = name == "sort_unstable_by"; - Some(LintTrigger { vec_name, unstable, closure_arg: "e".to_string(), closure_reverse_body: "e".to_string() }) + let closure_arg = a_ident.name.to_ident_string(); + let closure_reverse_body = Sugg::hir(cx, &a_expr, "..").to_string(); + Some(LintTrigger { vec_name, unstable, closure_arg, closure_reverse_body }) } else { None } @@ -60,18 +165,8 @@ fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option impl LateLintPass<'_, '_> for SortByKeyReverse { fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { - println!("{:?}", expr); - span_lint_and_sugg( - cx, - SORT_BY_KEY_REVERSE, - expr.span, - "use Vec::sort_by_key here instead", - "try", - String::from("being a better person"), - Applicability::MachineApplicable, - ); if let Some(trigger) = detect_lint(cx, expr) { - span_lint_and_sugg( + utils::span_lint_and_sugg( cx, SORT_BY_KEY_REVERSE, expr.span, diff --git a/tests/ui/sort_by_key_reverse.fixed b/tests/ui/sort_by_key_reverse.fixed index e69de29bb2d1d..4b18a073e1add 100644 --- a/tests/ui/sort_by_key_reverse.fixed +++ b/tests/ui/sort_by_key_reverse.fixed @@ -0,0 +1,9 @@ +// run-rustfix +#![warn(clippy::sort_by_key_reverse)] + +fn main() { + let mut vec: Vec = vec![3, 6, 1, 2, 5]; + vec.sort_by_key(|a| Reverse(a)); + vec.sort_by_key(|a| Reverse(&(a+5).abs())); + vec.sort_by_key(|a| Reverse(&-a)); +} diff --git a/tests/ui/sort_by_key_reverse.rs b/tests/ui/sort_by_key_reverse.rs index c0350f243c7be..f4fb70b7b1dcb 100644 --- a/tests/ui/sort_by_key_reverse.rs +++ b/tests/ui/sort_by_key_reverse.rs @@ -1,6 +1,9 @@ +// run-rustfix #![warn(clippy::sort_by_key_reverse)] fn main() { - let mut vec = vec![3, 6, 1, 2, 5]; + let mut vec: Vec = vec![3, 6, 1, 2, 5]; vec.sort_by(|a, b| b.cmp(a)); + vec.sort_by(|a, b| (b + 5).abs().cmp(&(a+5).abs())); + vec.sort_by(|a, b| (-b).cmp(&-a)); } diff --git a/tests/ui/sort_by_key_reverse.stderr b/tests/ui/sort_by_key_reverse.stderr index e69de29bb2d1d..36a28c04b1c55 100644 --- a/tests/ui/sort_by_key_reverse.stderr +++ b/tests/ui/sort_by_key_reverse.stderr @@ -0,0 +1,22 @@ +error: use Vec::sort_by_key here instead + --> $DIR/sort_by_key_reverse.rs:6:5 + | +LL | vec.sort_by(|a, b| b.cmp(a)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| Reverse(a))` + | + = note: `-D clippy::sort-by-key-reverse` implied by `-D warnings` + +error: use Vec::sort_by_key here instead + --> $DIR/sort_by_key_reverse.rs:7:5 + | +LL | vec.sort_by(|a, b| (b + 5).abs().cmp(&(a+5).abs())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| Reverse(&(a+5).abs()))` + +error: use Vec::sort_by_key here instead + --> $DIR/sort_by_key_reverse.rs:8:5 + | +LL | vec.sort_by(|a, b| (-b).cmp(&-a)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| Reverse(&-a))` + +error: aborting due to 3 previous errors + From 943cb94dce8fca6f3a3f7f011a2a2f9f0a665b97 Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Tue, 19 May 2020 22:57:27 -0700 Subject: [PATCH 28/72] Passes all tests now! --- clippy_lints/src/sort_by_key_reverse.rs | 72 ++++++++----------------- tests/ui/sort_by_key_reverse.fixed | 12 +++-- tests/ui/sort_by_key_reverse.rs | 8 ++- tests/ui/sort_by_key_reverse.stderr | 14 ++--- 4 files changed, 45 insertions(+), 61 deletions(-) diff --git a/clippy_lints/src/sort_by_key_reverse.rs b/clippy_lints/src/sort_by_key_reverse.rs index d70391999a023..31629a1dbc1b4 100644 --- a/clippy_lints/src/sort_by_key_reverse.rs +++ b/clippy_lints/src/sort_by_key_reverse.rs @@ -53,8 +53,12 @@ fn mirrored_exprs(cx: &LateContext<'_, '_>, a_expr: &Expr<'_>, a_ident: &Ident, // The two exprs are function calls. // Check to see that the function itself and its arguments are mirrored (ExprKind::Call(left_expr, left_args), ExprKind::Call(right_expr, right_args)) - => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident) - && left_args.iter().zip(right_args.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + => { + // println!("{:?}\n{:?}\n", left_expr, left_args); + // println!("{:?}\n{:?}\n", right_expr, right_args); + mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident) + && left_args.iter().zip(right_args.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)) + }, // The two exprs are method calls. // Check to see that the function is the same and the arguments are mirrored // This is enough because the receiver of the method is listed in the arguments @@ -74,21 +78,17 @@ fn mirrored_exprs(cx: &LateContext<'_, '_>, a_expr: &Expr<'_>, a_ident: &Ident, => left_op == right_op && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), // The two exprs are literals of some kind (ExprKind::Lit(left_lit), ExprKind::Lit(right_lit)) => left_lit.node == right_lit.node, - (ExprKind::Cast(left_expr, _), ExprKind::Cast(right_expr, _)) + (ExprKind::Cast(left_expr, left_ty), ExprKind::Cast(right_expr, right_ty)) => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), (ExprKind::DropTemps(left), ExprKind::DropTemps(right)) => mirrored_exprs(cx, left, a_ident, right, b_ident), - (ExprKind::Block(left, _), ExprKind::Block(right, _)) => mirrored_blocks(cx, left, a_ident, right, b_ident), (ExprKind::Field(left_expr, left_ident), ExprKind::Field(right_expr, right_ident)) => left_ident.name == right_ident.name && mirrored_exprs(cx, left_expr, a_ident, right_expr, right_ident), - // The two exprs are `a` and `b`, directly - (ExprKind::Path(QPath::Resolved(_, Path { segments: &[PathSegment { ident: left_ident, .. }], .. },)), - ExprKind::Path(QPath::Resolved(_, Path { segments: &[PathSegment { ident: right_ident, .. }], .. },)), - ) => &left_ident == a_ident && &right_ident == b_ident, - // The two exprs are Paths to the same name (which is neither a nor b) + // Two paths: either one is a and the other is b, or they're identical to each other (ExprKind::Path(QPath::Resolved(_, Path { segments: left_segments, .. })), ExprKind::Path(QPath::Resolved(_, Path { segments: right_segments, .. }))) - => left_segments.iter().zip(right_segments.iter()).all(|(left, right)| left.ident == right.ident) - && left_segments.iter().all(|seg| &seg.ident != a_ident && &seg.ident != b_ident), + => (left_segments.iter().zip(right_segments.iter()).all(|(left, right)| left.ident == right.ident) + && left_segments.iter().all(|seg| &seg.ident != a_ident && &seg.ident != b_ident)) + || (left_segments.len() == 1 && &left_segments[0].ident == a_ident && right_segments.len() == 1 && &right_segments[0].ident == b_ident), // Matching expressions, but one or both is borrowed (ExprKind::AddrOf(left_kind, Mutability::Not, left_expr), ExprKind::AddrOf(right_kind, Mutability::Not, right_expr)) => left_kind == right_kind && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), @@ -96,43 +96,11 @@ fn mirrored_exprs(cx: &LateContext<'_, '_>, a_expr: &Expr<'_>, a_ident: &Ident, => mirrored_exprs(cx, a_expr, a_ident, right_expr, b_ident), (ExprKind::AddrOf(_, Mutability::Not, left_expr), _) => mirrored_exprs(cx, left_expr, a_ident, b_expr, b_ident), - // _ => false, - (left, right) => { - println!("{:?}\n{:?}", left, right); - false - }, - } -} - -/// Detect if the two blocks are mirrored (identical, except one -/// contains a and the other replaces it with b) -fn mirrored_blocks(cx: &LateContext<'_, '_>, a_block: &Block<'_>, a_ident: &Ident, b_block: &Block<'_>, b_ident: &Ident) -> bool { - match (a_block, b_block) { - (Block { stmts: left_stmts, expr: left_expr, .. }, - Block { stmts: right_stmts, expr: right_expr, .. }) - => left_stmts.iter().zip(right_stmts.iter()).all(|(left, right)| match (&left.kind, &right.kind) { - (StmtKind::Expr(left_expr), StmtKind::Expr(right_expr)) => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), - (StmtKind::Semi(left_expr), StmtKind::Semi(right_expr)) => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), - (StmtKind::Item(left_item), StmtKind::Item(right_item)) => left_item.id == right_item.id, - (StmtKind::Local(left), StmtKind::Local(right)) => mirrored_locals(cx, left, a_ident, right, b_ident), - _ => false, - }) && match (left_expr, right_expr) { - (None, None) => true, - (Some(left), Some(right)) => mirrored_exprs(cx, left, a_ident, right, b_ident), - _ => false, - }, - } -} - -/// Check that the two "Local"s (let statements) are equal -fn mirrored_locals(cx: &LateContext<'_, '_>, a_local: &Local<'_>, a_ident: &Ident, b_local: &Local<'_>, b_ident: &Ident) -> bool { - match (a_local, b_local) { - (Local { pat: left_pat, init: left_expr, .. }, Local { pat: right_pat, init: right_expr, .. }) - => match (left_expr, right_expr) { - (None, None) => true, - (Some(left), Some(right)) => mirrored_exprs(cx, left, a_ident, right, b_ident), - _ => false, - }, + _ => false, + // (left, right) => { + // println!("{:?}\n{:?}", left, right); + // false + // }, } } @@ -154,8 +122,12 @@ fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option then { let vec_name = Sugg::hir(cx, &args[0], "..").to_string(); let unstable = name == "sort_unstable_by"; - let closure_arg = a_ident.name.to_ident_string(); - let closure_reverse_body = Sugg::hir(cx, &a_expr, "..").to_string(); + let closure_arg = format!("&{}", b_ident.name.to_ident_string()); + let closure_reverse_body = Sugg::hir(cx, &b_expr, "..").to_string(); + // Get rid of parentheses, because they aren't needed anymore + // while closure_reverse_body.chars().next() == Some('(') && closure_reverse_body.chars().last() == Some(')') { + // closure_reverse_body = String::from(&closure_reverse_body[1..closure_reverse_body.len()-1]); + // } Some(LintTrigger { vec_name, unstable, closure_arg, closure_reverse_body }) } else { None diff --git a/tests/ui/sort_by_key_reverse.fixed b/tests/ui/sort_by_key_reverse.fixed index 4b18a073e1add..d536dc385d533 100644 --- a/tests/ui/sort_by_key_reverse.fixed +++ b/tests/ui/sort_by_key_reverse.fixed @@ -1,9 +1,15 @@ // run-rustfix #![warn(clippy::sort_by_key_reverse)] +use std::cmp::Reverse; + +fn id(x: isize) -> isize { + x +} + fn main() { let mut vec: Vec = vec![3, 6, 1, 2, 5]; - vec.sort_by_key(|a| Reverse(a)); - vec.sort_by_key(|a| Reverse(&(a+5).abs())); - vec.sort_by_key(|a| Reverse(&-a)); + vec.sort_by_key(|&b| Reverse(b)); + vec.sort_by_key(|&b| Reverse((b + 5).abs())); + vec.sort_by_key(|&b| Reverse(id(-b))); } diff --git a/tests/ui/sort_by_key_reverse.rs b/tests/ui/sort_by_key_reverse.rs index f4fb70b7b1dcb..9c42d401755ad 100644 --- a/tests/ui/sort_by_key_reverse.rs +++ b/tests/ui/sort_by_key_reverse.rs @@ -1,9 +1,15 @@ // run-rustfix #![warn(clippy::sort_by_key_reverse)] +use std::cmp::Reverse; + +fn id(x: isize) -> isize { + x +} + fn main() { let mut vec: Vec = vec![3, 6, 1, 2, 5]; vec.sort_by(|a, b| b.cmp(a)); vec.sort_by(|a, b| (b + 5).abs().cmp(&(a+5).abs())); - vec.sort_by(|a, b| (-b).cmp(&-a)); + vec.sort_by(|a, b| id(-b).cmp(&id(-a))); } diff --git a/tests/ui/sort_by_key_reverse.stderr b/tests/ui/sort_by_key_reverse.stderr index 36a28c04b1c55..3d26ddae78ad8 100644 --- a/tests/ui/sort_by_key_reverse.stderr +++ b/tests/ui/sort_by_key_reverse.stderr @@ -1,22 +1,22 @@ error: use Vec::sort_by_key here instead - --> $DIR/sort_by_key_reverse.rs:6:5 + --> $DIR/sort_by_key_reverse.rs:12:5 | LL | vec.sort_by(|a, b| b.cmp(a)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| Reverse(a))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse(b))` | = note: `-D clippy::sort-by-key-reverse` implied by `-D warnings` error: use Vec::sort_by_key here instead - --> $DIR/sort_by_key_reverse.rs:7:5 + --> $DIR/sort_by_key_reverse.rs:13:5 | LL | vec.sort_by(|a, b| (b + 5).abs().cmp(&(a+5).abs())); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| Reverse(&(a+5).abs()))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse((b + 5).abs()))` error: use Vec::sort_by_key here instead - --> $DIR/sort_by_key_reverse.rs:8:5 + --> $DIR/sort_by_key_reverse.rs:14:5 | -LL | vec.sort_by(|a, b| (-b).cmp(&-a)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| Reverse(&-a))` +LL | vec.sort_by(|a, b| id(-b).cmp(&id(-a))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse(id(-b)))` error: aborting due to 3 previous errors From 955a25ee7db234a8ab697176a433070702aabe59 Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Wed, 20 May 2020 09:23:00 -0700 Subject: [PATCH 29/72] Added negative test cases and ran cargo dev fmt --- clippy_lints/src/sort_by_key_reverse.rs | 125 ++++++++++++++++-------- tests/ui/sort_by_key_reverse.fixed | 7 ++ tests/ui/sort_by_key_reverse.rs | 9 +- tests/ui/sort_by_key_reverse.stderr | 4 +- 4 files changed, 100 insertions(+), 45 deletions(-) diff --git a/clippy_lints/src/sort_by_key_reverse.rs b/clippy_lints/src/sort_by_key_reverse.rs index 31629a1dbc1b4..ea850955db121 100644 --- a/clippy_lints/src/sort_by_key_reverse.rs +++ b/clippy_lints/src/sort_by_key_reverse.rs @@ -3,7 +3,7 @@ use crate::utils::paths; use crate::utils::sugg::Sugg; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::*; +use rustc_hir::{Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::Ident; @@ -43,64 +43,105 @@ struct LintTrigger { /// Detect if the two expressions are mirrored (identical, except one /// contains a and the other replaces it with b) -fn mirrored_exprs(cx: &LateContext<'_, '_>, a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident: &Ident) -> bool { +fn mirrored_exprs( + cx: &LateContext<'_, '_>, + a_expr: &Expr<'_>, + a_ident: &Ident, + b_expr: &Expr<'_>, + b_ident: &Ident, +) -> bool { match (&a_expr.kind, &b_expr.kind) { // Two boxes with mirrored contents - (ExprKind::Box(left_expr), ExprKind::Box(right_expr)) => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + (ExprKind::Box(left_expr), ExprKind::Box(right_expr)) => { + mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident) + }, // Two arrays with mirrored contents - (ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) - => left_exprs.iter().zip(right_exprs.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + (ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) => left_exprs + .iter() + .zip(right_exprs.iter()) + .all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), // The two exprs are function calls. // Check to see that the function itself and its arguments are mirrored - (ExprKind::Call(left_expr, left_args), ExprKind::Call(right_expr, right_args)) - => { - // println!("{:?}\n{:?}\n", left_expr, left_args); - // println!("{:?}\n{:?}\n", right_expr, right_args); - mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident) - && left_args.iter().zip(right_args.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)) - }, + (ExprKind::Call(left_expr, left_args), ExprKind::Call(right_expr, right_args)) => { + mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident) + && left_args + .iter() + .zip(right_args.iter()) + .all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)) + }, // The two exprs are method calls. // Check to see that the function is the same and the arguments are mirrored // This is enough because the receiver of the method is listed in the arguments - (ExprKind::MethodCall(left_segment, _, left_args), ExprKind::MethodCall(right_segment, _, right_args)) - => left_segment.ident == right_segment.ident - && left_args.iter().zip(right_args.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + (ExprKind::MethodCall(left_segment, _, left_args), ExprKind::MethodCall(right_segment, _, right_args)) => { + left_segment.ident == right_segment.ident + && left_args + .iter() + .zip(right_args.iter()) + .all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)) + }, // Two tuples with mirrored contents - (ExprKind::Tup(left_exprs), ExprKind::Tup(right_exprs)) - => left_exprs.iter().zip(right_exprs.iter()).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), + (ExprKind::Tup(left_exprs), ExprKind::Tup(right_exprs)) => left_exprs + .iter() + .zip(right_exprs.iter()) + .all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident)), // Two binary ops, which are the same operation and which have mirrored arguments - (ExprKind::Binary(left_op, left_left, left_right), ExprKind::Binary(right_op, right_left, right_right)) - => left_op.node == right_op.node + (ExprKind::Binary(left_op, left_left, left_right), ExprKind::Binary(right_op, right_left, right_right)) => { + left_op.node == right_op.node && mirrored_exprs(cx, left_left, a_ident, right_left, b_ident) - && mirrored_exprs(cx, left_right, a_ident, right_right, b_ident), + && mirrored_exprs(cx, left_right, a_ident, right_right, b_ident) + }, // Two unary ops, which are the same operation and which have the same argument - (ExprKind::Unary(left_op, left_expr), ExprKind::Unary(right_op, right_expr)) - => left_op == right_op && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + (ExprKind::Unary(left_op, left_expr), ExprKind::Unary(right_op, right_expr)) => { + left_op == right_op && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident) + }, // The two exprs are literals of some kind (ExprKind::Lit(left_lit), ExprKind::Lit(right_lit)) => left_lit.node == right_lit.node, - (ExprKind::Cast(left_expr, left_ty), ExprKind::Cast(right_expr, right_ty)) - => mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), - (ExprKind::DropTemps(left), ExprKind::DropTemps(right)) => mirrored_exprs(cx, left, a_ident, right, b_ident), - (ExprKind::Field(left_expr, left_ident), ExprKind::Field(right_expr, right_ident)) - => left_ident.name == right_ident.name && mirrored_exprs(cx, left_expr, a_ident, right_expr, right_ident), + (ExprKind::Cast(left, _), ExprKind::Cast(right, _)) => mirrored_exprs(cx, left, a_ident, right, b_ident), + (ExprKind::DropTemps(left_block), ExprKind::DropTemps(right_block)) => { + mirrored_exprs(cx, left_block, a_ident, right_block, b_ident) + }, + (ExprKind::Field(left_expr, left_ident), ExprKind::Field(right_expr, right_ident)) => { + left_ident.name == right_ident.name && mirrored_exprs(cx, left_expr, a_ident, right_expr, right_ident) + }, // Two paths: either one is a and the other is b, or they're identical to each other - (ExprKind::Path(QPath::Resolved(_, Path { segments: left_segments, .. })), - ExprKind::Path(QPath::Resolved(_, Path { segments: right_segments, .. }))) - => (left_segments.iter().zip(right_segments.iter()).all(|(left, right)| left.ident == right.ident) - && left_segments.iter().all(|seg| &seg.ident != a_ident && &seg.ident != b_ident)) - || (left_segments.len() == 1 && &left_segments[0].ident == a_ident && right_segments.len() == 1 && &right_segments[0].ident == b_ident), + ( + ExprKind::Path(QPath::Resolved( + _, + Path { + segments: left_segments, + .. + }, + )), + ExprKind::Path(QPath::Resolved( + _, + Path { + segments: right_segments, + .. + }, + )), + ) => { + (left_segments + .iter() + .zip(right_segments.iter()) + .all(|(left, right)| left.ident == right.ident) + && left_segments + .iter() + .all(|seg| &seg.ident != a_ident && &seg.ident != b_ident)) + || (left_segments.len() == 1 + && &left_segments[0].ident == a_ident + && right_segments.len() == 1 + && &right_segments[0].ident == b_ident) + }, // Matching expressions, but one or both is borrowed - (ExprKind::AddrOf(left_kind, Mutability::Not, left_expr), ExprKind::AddrOf(right_kind, Mutability::Not, right_expr)) - => left_kind == right_kind && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), - (_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) - => mirrored_exprs(cx, a_expr, a_ident, right_expr, b_ident), - (ExprKind::AddrOf(_, Mutability::Not, left_expr), _) - => mirrored_exprs(cx, left_expr, a_ident, b_expr, b_ident), + ( + ExprKind::AddrOf(left_kind, Mutability::Not, left_expr), + ExprKind::AddrOf(right_kind, Mutability::Not, right_expr), + ) => left_kind == right_kind && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident), + (_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) => { + mirrored_exprs(cx, a_expr, a_ident, right_expr, b_ident) + }, + (ExprKind::AddrOf(_, Mutability::Not, left_expr), _) => mirrored_exprs(cx, left_expr, a_ident, b_expr, b_ident), _ => false, - // (left, right) => { - // println!("{:?}\n{:?}", left, right); - // false - // }, } } diff --git a/tests/ui/sort_by_key_reverse.fixed b/tests/ui/sort_by_key_reverse.fixed index d536dc385d533..722675a6b71a8 100644 --- a/tests/ui/sort_by_key_reverse.fixed +++ b/tests/ui/sort_by_key_reverse.fixed @@ -12,4 +12,11 @@ fn main() { vec.sort_by_key(|&b| Reverse(b)); vec.sort_by_key(|&b| Reverse((b + 5).abs())); vec.sort_by_key(|&b| Reverse(id(-b))); + // Negative examples (shouldn't be changed) + let c = &7; + vec.sort_by(|a, b| (b - a).cmp(&(a - b))); + vec.sort_by(|_, b| b.cmp(&5)); + vec.sort_by(|_, b| b.cmp(c)); + vec.sort_by(|a, _| a.cmp(c)); + vec.sort_by(|a, b| a.cmp(b)); } diff --git a/tests/ui/sort_by_key_reverse.rs b/tests/ui/sort_by_key_reverse.rs index 9c42d401755ad..601621ffa9f86 100644 --- a/tests/ui/sort_by_key_reverse.rs +++ b/tests/ui/sort_by_key_reverse.rs @@ -10,6 +10,13 @@ fn id(x: isize) -> isize { fn main() { let mut vec: Vec = vec![3, 6, 1, 2, 5]; vec.sort_by(|a, b| b.cmp(a)); - vec.sort_by(|a, b| (b + 5).abs().cmp(&(a+5).abs())); + vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs())); vec.sort_by(|a, b| id(-b).cmp(&id(-a))); + // Negative examples (shouldn't be changed) + let c = &7; + vec.sort_by(|a, b| (b - a).cmp(&(a - b))); + vec.sort_by(|_, b| b.cmp(&5)); + vec.sort_by(|_, b| b.cmp(c)); + vec.sort_by(|a, _| a.cmp(c)); + vec.sort_by(|a, b| a.cmp(b)); } diff --git a/tests/ui/sort_by_key_reverse.stderr b/tests/ui/sort_by_key_reverse.stderr index 3d26ddae78ad8..b757c8a6176d1 100644 --- a/tests/ui/sort_by_key_reverse.stderr +++ b/tests/ui/sort_by_key_reverse.stderr @@ -9,8 +9,8 @@ LL | vec.sort_by(|a, b| b.cmp(a)); error: use Vec::sort_by_key here instead --> $DIR/sort_by_key_reverse.rs:13:5 | -LL | vec.sort_by(|a, b| (b + 5).abs().cmp(&(a+5).abs())); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse((b + 5).abs()))` +LL | vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse((b + 5).abs()))` error: use Vec::sort_by_key here instead --> $DIR/sort_by_key_reverse.rs:14:5 From 059e8edd15401d5544260e4058731dc8818578d5 Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Sun, 24 May 2020 19:45:41 -0700 Subject: [PATCH 30/72] Detect also a non-reversed comparison --- clippy_lints/src/lib.rs | 10 ++-- ...{sort_by_key_reverse.rs => sort_by_key.rs} | 52 +++++++++++-------- ...by_key_reverse.fixed => sort_by_key.fixed} | 8 ++- ...{sort_by_key_reverse.rs => sort_by_key.rs} | 6 ++- tests/ui/sort_by_key.stderr | 48 +++++++++++++++++ tests/ui/sort_by_key_reverse.stderr | 22 -------- 6 files changed, 94 insertions(+), 52 deletions(-) rename clippy_lints/src/{sort_by_key_reverse.rs => sort_by_key.rs} (83%) rename tests/ui/{sort_by_key_reverse.fixed => sort_by_key.fixed} (72%) rename tests/ui/{sort_by_key_reverse.rs => sort_by_key.rs} (78%) create mode 100644 tests/ui/sort_by_key.stderr delete mode 100644 tests/ui/sort_by_key_reverse.stderr diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index e7a4c1ecaa954..9e826316f2186 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -304,7 +304,7 @@ mod serde_api; mod shadow; mod single_component_path_imports; mod slow_vector_initialization; -mod sort_by_key_reverse; +mod sort_by_key; mod strings; mod suspicious_trait_impl; mod swap; @@ -780,7 +780,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &shadow::SHADOW_UNRELATED, &single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS, &slow_vector_initialization::SLOW_VECTOR_INITIALIZATION, - &sort_by_key_reverse::SORT_BY_KEY_REVERSE, + &sort_by_key::SORT_BY_KEY, &strings::STRING_ADD, &strings::STRING_ADD_ASSIGN, &strings::STRING_LIT_AS_BYTES, @@ -998,7 +998,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box ptr_offset_with_cast::PtrOffsetWithCast); store.register_late_pass(|| box redundant_clone::RedundantClone); store.register_late_pass(|| box slow_vector_initialization::SlowVectorInit); - store.register_late_pass(|| box sort_by_key_reverse::SortByKeyReverse); + store.register_late_pass(|| box sort_by_key::SortByKey); store.register_late_pass(|| box types::RefToMut); store.register_late_pass(|| box assertions_on_constants::AssertionsOnConstants); store.register_late_pass(|| box missing_const_for_fn::MissingConstForFn); @@ -1394,7 +1394,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&serde_api::SERDE_API_MISUSE), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), - LintId::of(&sort_by_key_reverse::SORT_BY_KEY_REVERSE), + LintId::of(&sort_by_key::SORT_BY_KEY), LintId::of(&strings::STRING_LIT_AS_BYTES), LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), @@ -1596,7 +1596,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&ranges::RANGE_ZIP_WITH_LEN), LintId::of(&reference::DEREF_ADDROF), LintId::of(&reference::REF_IN_DEREF), - LintId::of(&sort_by_key_reverse::SORT_BY_KEY_REVERSE), + LintId::of(&sort_by_key::SORT_BY_KEY), LintId::of(&swap::MANUAL_SWAP), LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT), LintId::of(&transmute::CROSSPOINTER_TRANSMUTE), diff --git a/clippy_lints/src/sort_by_key_reverse.rs b/clippy_lints/src/sort_by_key.rs similarity index 83% rename from clippy_lints/src/sort_by_key_reverse.rs rename to clippy_lints/src/sort_by_key.rs index ea850955db121..109845a28f446 100644 --- a/clippy_lints/src/sort_by_key_reverse.rs +++ b/clippy_lints/src/sort_by_key.rs @@ -11,33 +11,35 @@ use rustc_span::symbol::Ident; declare_clippy_lint! { /// **What it does:** /// Detects when people use `Vec::sort_by` and pass in a function - /// which compares the second argument to the first. + /// which compares the two arguments, either directly or indirectly. /// /// **Why is this bad?** - /// It is more clear to use `Vec::sort_by_key` and `std::cmp::Reverse` + /// It is more clear to use `Vec::sort_by_key` (or + /// `Vec::sort_by_key` and `std::cmp::Reverse` if necessary) than + /// using /// /// **Known problems:** None. /// /// **Example:** /// /// ```rust - /// vec.sort_by(|a, b| b.foo().cmp(&a.foo())); + /// vec.sort_by(|a, b| a.foo().cmp(b.foo())); /// ``` /// Use instead: /// ```rust - /// vec.sort_by_key(|e| Reverse(e.foo())); + /// vec.sort_by_key(|a| a.foo()); /// ``` - pub SORT_BY_KEY_REVERSE, + pub SORT_BY_KEY, complexity, "Use of `Vec::sort_by` when `Vec::sort_by_key` would be clearer" } -declare_lint_pass!(SortByKeyReverse => [SORT_BY_KEY_REVERSE]); +declare_lint_pass!(SortByKey => [SORT_BY_KEY]); struct LintTrigger { vec_name: String, closure_arg: String, - closure_reverse_body: String, + closure_body: String, unstable: bool, } @@ -154,43 +156,49 @@ fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option if utils::match_type(cx, &cx.tables.expr_ty(vec), &paths::VEC); if let closure_body = cx.tcx.hir().body(*closure_body_id); if let &[ - Param { pat: Pat { kind: PatKind::Binding(_, _, a_ident, _), .. }, ..}, - Param { pat: Pat { kind: PatKind::Binding(_, _, b_ident, _), .. }, .. } + Param { pat: Pat { kind: PatKind::Binding(_, _, left_ident, _), .. }, ..}, + Param { pat: Pat { kind: PatKind::Binding(_, _, right_ident, _), .. }, .. } ] = &closure_body.params; - if let ExprKind::MethodCall(method_path, _, [ref b_expr, ref a_expr]) = &closure_body.value.kind; + if let ExprKind::MethodCall(method_path, _, [ref left_expr, ref right_expr]) = &closure_body.value.kind; if method_path.ident.name.to_ident_string() == "cmp"; - if mirrored_exprs(&cx, &a_expr, &a_ident, &b_expr, &b_ident); then { + let (closure_body, closure_arg) = if mirrored_exprs( + &cx, + &left_expr, + &left_ident, + &right_expr, + &right_ident + ) { + (Sugg::hir(cx, &left_expr, "..").to_string(), left_ident.name.to_string()) + } else if mirrored_exprs(&cx, &left_expr, &right_ident, &right_expr, &left_ident) { + (format!("Reverse({})", Sugg::hir(cx, &left_expr, "..").to_string()), right_ident.name.to_string()) + } else { + return None; + }; let vec_name = Sugg::hir(cx, &args[0], "..").to_string(); let unstable = name == "sort_unstable_by"; - let closure_arg = format!("&{}", b_ident.name.to_ident_string()); - let closure_reverse_body = Sugg::hir(cx, &b_expr, "..").to_string(); - // Get rid of parentheses, because they aren't needed anymore - // while closure_reverse_body.chars().next() == Some('(') && closure_reverse_body.chars().last() == Some(')') { - // closure_reverse_body = String::from(&closure_reverse_body[1..closure_reverse_body.len()-1]); - // } - Some(LintTrigger { vec_name, unstable, closure_arg, closure_reverse_body }) + Some(LintTrigger { vec_name, unstable, closure_arg, closure_body }) } else { None } } } -impl LateLintPass<'_, '_> for SortByKeyReverse { +impl LateLintPass<'_, '_> for SortByKey { fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { if let Some(trigger) = detect_lint(cx, expr) { utils::span_lint_and_sugg( cx, - SORT_BY_KEY_REVERSE, + SORT_BY_KEY, expr.span, "use Vec::sort_by_key here instead", "try", format!( - "{}.sort{}_by_key(|{}| Reverse({}))", + "{}.sort{}_by_key(|&{}| {})", trigger.vec_name, if trigger.unstable { "_unstable" } else { "" }, trigger.closure_arg, - trigger.closure_reverse_body, + trigger.closure_body, ), Applicability::MachineApplicable, ); diff --git a/tests/ui/sort_by_key_reverse.fixed b/tests/ui/sort_by_key.fixed similarity index 72% rename from tests/ui/sort_by_key_reverse.fixed rename to tests/ui/sort_by_key.fixed index 722675a6b71a8..f6535c8d8f5dd 100644 --- a/tests/ui/sort_by_key_reverse.fixed +++ b/tests/ui/sort_by_key.fixed @@ -1,5 +1,5 @@ // run-rustfix -#![warn(clippy::sort_by_key_reverse)] +#![warn(clippy::sort_by_key)] use std::cmp::Reverse; @@ -9,6 +9,11 @@ fn id(x: isize) -> isize { fn main() { let mut vec: Vec = vec![3, 6, 1, 2, 5]; + // Forward examples + vec.sort_by_key(|&a| a); + vec.sort_by_key(|&a| (a + 5).abs()); + vec.sort_by_key(|&a| id(-a)); + // Reverse examples vec.sort_by_key(|&b| Reverse(b)); vec.sort_by_key(|&b| Reverse((b + 5).abs())); vec.sort_by_key(|&b| Reverse(id(-b))); @@ -18,5 +23,4 @@ fn main() { vec.sort_by(|_, b| b.cmp(&5)); vec.sort_by(|_, b| b.cmp(c)); vec.sort_by(|a, _| a.cmp(c)); - vec.sort_by(|a, b| a.cmp(b)); } diff --git a/tests/ui/sort_by_key_reverse.rs b/tests/ui/sort_by_key.rs similarity index 78% rename from tests/ui/sort_by_key_reverse.rs rename to tests/ui/sort_by_key.rs index 601621ffa9f86..953c573d406d8 100644 --- a/tests/ui/sort_by_key_reverse.rs +++ b/tests/ui/sort_by_key.rs @@ -9,6 +9,11 @@ fn id(x: isize) -> isize { fn main() { let mut vec: Vec = vec![3, 6, 1, 2, 5]; + // Forward examples + vec.sort_by(|a, b| a.cmp(b)); + vec.sort_by(|a, b| (a + 5).abs().cmp(&(b + 5).abs())); + vec.sort_by(|a, b| id(-a).cmp(&id(-b))); + // Reverse examples vec.sort_by(|a, b| b.cmp(a)); vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs())); vec.sort_by(|a, b| id(-b).cmp(&id(-a))); @@ -18,5 +23,4 @@ fn main() { vec.sort_by(|_, b| b.cmp(&5)); vec.sort_by(|_, b| b.cmp(c)); vec.sort_by(|a, _| a.cmp(c)); - vec.sort_by(|a, b| a.cmp(b)); } diff --git a/tests/ui/sort_by_key.stderr b/tests/ui/sort_by_key.stderr new file mode 100644 index 0000000000000..fa6a9a0fb10e7 --- /dev/null +++ b/tests/ui/sort_by_key.stderr @@ -0,0 +1,48 @@ +error: use Vec::sort_by_key here instead + --> $DIR/sort_by_key.rs:13:5 + | +LL | vec.sort_by(|a, b| a.cmp(b)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&a| a)` + | + = note: `-D clippy::sort-by-key` implied by `-D warnings` + +error: use Vec::sort_by_key here instead + --> $DIR/sort_by_key.rs:14:5 + | +LL | vec.sort_by(|a, b| (a + 5).abs().cmp(&(b + 5).abs())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&a| (a + 5).abs())` + +error: use Vec::sort_by_key here instead + --> $DIR/sort_by_key.rs:15:5 + | +LL | vec.sort_by(|a, b| id(-a).cmp(&id(-b))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&a| id(-a))` + +error: use Vec::sort_by_key here instead + --> $DIR/sort_by_key.rs:17:5 + | +LL | vec.sort_by(|a, b| b.cmp(a)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse(b))` + +error: use Vec::sort_by_key here instead + --> $DIR/sort_by_key.rs:18:5 + | +LL | vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse((b + 5).abs()))` + +error: use Vec::sort_by_key here instead + --> $DIR/sort_by_key.rs:19:5 + | +LL | vec.sort_by(|a, b| id(-b).cmp(&id(-a))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse(id(-b)))` + +error: unknown clippy lint: clippy::sort_by_key_reverse + --> $DIR/sort_by_key.rs:2:9 + | +LL | #![warn(clippy::sort_by_key_reverse)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::sort_by_key` + | + = note: `-D clippy::unknown-clippy-lints` implied by `-D warnings` + +error: aborting due to 7 previous errors + diff --git a/tests/ui/sort_by_key_reverse.stderr b/tests/ui/sort_by_key_reverse.stderr deleted file mode 100644 index b757c8a6176d1..0000000000000 --- a/tests/ui/sort_by_key_reverse.stderr +++ /dev/null @@ -1,22 +0,0 @@ -error: use Vec::sort_by_key here instead - --> $DIR/sort_by_key_reverse.rs:12:5 - | -LL | vec.sort_by(|a, b| b.cmp(a)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse(b))` - | - = note: `-D clippy::sort-by-key-reverse` implied by `-D warnings` - -error: use Vec::sort_by_key here instead - --> $DIR/sort_by_key_reverse.rs:13:5 - | -LL | vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs())); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse((b + 5).abs()))` - -error: use Vec::sort_by_key here instead - --> $DIR/sort_by_key_reverse.rs:14:5 - | -LL | vec.sort_by(|a, b| id(-b).cmp(&id(-a))); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse(id(-b)))` - -error: aborting due to 3 previous errors - From 07886a97640b89f72b70805f519bd9d42d7d1c4e Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Sun, 24 May 2020 20:05:58 -0700 Subject: [PATCH 31/72] Detect also when works --- clippy_lints/src/sort_by_key.rs | 49 ++++++++++++++++++++++++++++----- tests/ui/sort_by_key.fixed | 2 +- tests/ui/sort_by_key.stderr | 4 +-- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/sort_by_key.rs b/clippy_lints/src/sort_by_key.rs index 109845a28f446..f720d14473aff 100644 --- a/clippy_lints/src/sort_by_key.rs +++ b/clippy_lints/src/sort_by_key.rs @@ -3,7 +3,7 @@ use crate::utils::paths; use crate::utils::sugg::Sugg; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, QPath}; +use rustc_hir::{Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::Ident; @@ -16,7 +16,7 @@ declare_clippy_lint! { /// **Why is this bad?** /// It is more clear to use `Vec::sort_by_key` (or /// `Vec::sort_by_key` and `std::cmp::Reverse` if necessary) than - /// using + /// using /// /// **Known problems:** None. /// @@ -36,7 +36,17 @@ declare_clippy_lint! { declare_lint_pass!(SortByKey => [SORT_BY_KEY]); -struct LintTrigger { +enum LintTrigger { + Sort(SortDetection), + SortByKey(SortByKeyDetection), +} + +struct SortDetection { + vec_name: String, + unstable: bool, +} + +struct SortByKeyDetection { vec_name: String, closure_arg: String, closure_body: String, @@ -177,7 +187,18 @@ fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option }; let vec_name = Sugg::hir(cx, &args[0], "..").to_string(); let unstable = name == "sort_unstable_by"; - Some(LintTrigger { vec_name, unstable, closure_arg, closure_body }) + if_chain! { + if let ExprKind::Path(QPath::Resolved(_, Path { + segments: [PathSegment { ident: left_name, .. }], .. + })) = &left_expr.kind; + if left_name == left_ident; + then { + Some(LintTrigger::Sort(SortDetection { vec_name, unstable })) + } + else { + Some(LintTrigger::SortByKey(SortByKeyDetection { vec_name, unstable, closure_arg, closure_body })) + } + } } else { None } @@ -186,8 +207,8 @@ fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option impl LateLintPass<'_, '_> for SortByKey { fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { - if let Some(trigger) = detect_lint(cx, expr) { - utils::span_lint_and_sugg( + match detect_lint(cx, expr) { + Some(LintTrigger::SortByKey(trigger)) => utils::span_lint_and_sugg( cx, SORT_BY_KEY, expr.span, @@ -201,7 +222,21 @@ impl LateLintPass<'_, '_> for SortByKey { trigger.closure_body, ), Applicability::MachineApplicable, - ); + ), + Some(LintTrigger::Sort(trigger)) => utils::span_lint_and_sugg( + cx, + SORT_BY_KEY, + expr.span, + "use Vec::sort here instead", + "try", + format!( + "{}.sort{}()", + trigger.vec_name, + if trigger.unstable { "_unstable" } else { "" }, + ), + Applicability::MachineApplicable, + ), + None => {}, } } } diff --git a/tests/ui/sort_by_key.fixed b/tests/ui/sort_by_key.fixed index f6535c8d8f5dd..bb88df1a56c92 100644 --- a/tests/ui/sort_by_key.fixed +++ b/tests/ui/sort_by_key.fixed @@ -10,7 +10,7 @@ fn id(x: isize) -> isize { fn main() { let mut vec: Vec = vec![3, 6, 1, 2, 5]; // Forward examples - vec.sort_by_key(|&a| a); + vec.sort(); vec.sort_by_key(|&a| (a + 5).abs()); vec.sort_by_key(|&a| id(-a)); // Reverse examples diff --git a/tests/ui/sort_by_key.stderr b/tests/ui/sort_by_key.stderr index fa6a9a0fb10e7..291fd5500f790 100644 --- a/tests/ui/sort_by_key.stderr +++ b/tests/ui/sort_by_key.stderr @@ -1,8 +1,8 @@ -error: use Vec::sort_by_key here instead +error: use Vec::sort here instead --> $DIR/sort_by_key.rs:13:5 | LL | vec.sort_by(|a, b| a.cmp(b)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&a| a)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort()` | = note: `-D clippy::sort-by-key` implied by `-D warnings` From 015ab9f9259d58a48c171276f6e7190528f1a9ad Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Thu, 28 May 2020 18:18:25 -0700 Subject: [PATCH 32/72] Renamed to --- clippy_lints/src/lib.rs | 10 +++++----- .../{sort_by_key.rs => unnecessary_sort_by.rs} | 18 +++++++++--------- ..._by_key.fixed => unnecessary_sort_by.fixed} | 0 .../{sort_by_key.rs => unnecessary_sort_by.rs} | 0 ...y_key.stderr => unnecessary_sort_by.stderr} | 0 5 files changed, 14 insertions(+), 14 deletions(-) rename clippy_lints/src/{sort_by_key.rs => unnecessary_sort_by.rs} (95%) rename tests/ui/{sort_by_key.fixed => unnecessary_sort_by.fixed} (100%) rename tests/ui/{sort_by_key.rs => unnecessary_sort_by.rs} (100%) rename tests/ui/{sort_by_key.stderr => unnecessary_sort_by.stderr} (100%) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 9e826316f2186..46df743b5bfd2 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -304,7 +304,7 @@ mod serde_api; mod shadow; mod single_component_path_imports; mod slow_vector_initialization; -mod sort_by_key; +mod unnecessary_sort_by; mod strings; mod suspicious_trait_impl; mod swap; @@ -780,7 +780,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &shadow::SHADOW_UNRELATED, &single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS, &slow_vector_initialization::SLOW_VECTOR_INITIALIZATION, - &sort_by_key::SORT_BY_KEY, + &unnecessary_sort_by::UNNECESSARY_SORT_BY, &strings::STRING_ADD, &strings::STRING_ADD_ASSIGN, &strings::STRING_LIT_AS_BYTES, @@ -998,7 +998,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box ptr_offset_with_cast::PtrOffsetWithCast); store.register_late_pass(|| box redundant_clone::RedundantClone); store.register_late_pass(|| box slow_vector_initialization::SlowVectorInit); - store.register_late_pass(|| box sort_by_key::SortByKey); + store.register_late_pass(|| box unnecessary_sort_by::UnnecessarySortBy); store.register_late_pass(|| box types::RefToMut); store.register_late_pass(|| box assertions_on_constants::AssertionsOnConstants); store.register_late_pass(|| box missing_const_for_fn::MissingConstForFn); @@ -1394,7 +1394,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&serde_api::SERDE_API_MISUSE), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), - LintId::of(&sort_by_key::SORT_BY_KEY), + LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&strings::STRING_LIT_AS_BYTES), LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), @@ -1596,7 +1596,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&ranges::RANGE_ZIP_WITH_LEN), LintId::of(&reference::DEREF_ADDROF), LintId::of(&reference::REF_IN_DEREF), - LintId::of(&sort_by_key::SORT_BY_KEY), + LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&swap::MANUAL_SWAP), LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT), LintId::of(&transmute::CROSSPOINTER_TRANSMUTE), diff --git a/clippy_lints/src/sort_by_key.rs b/clippy_lints/src/unnecessary_sort_by.rs similarity index 95% rename from clippy_lints/src/sort_by_key.rs rename to clippy_lints/src/unnecessary_sort_by.rs index f720d14473aff..c0858ec4c8880 100644 --- a/clippy_lints/src/sort_by_key.rs +++ b/clippy_lints/src/unnecessary_sort_by.rs @@ -14,9 +14,9 @@ declare_clippy_lint! { /// which compares the two arguments, either directly or indirectly. /// /// **Why is this bad?** - /// It is more clear to use `Vec::sort_by_key` (or - /// `Vec::sort_by_key` and `std::cmp::Reverse` if necessary) than - /// using + /// It is more clear to use `Vec::sort_by_key` (or `Vec::sort` if + /// possible) than to use `Vec::sort_by` and and a more complicated + /// closure. /// /// **Known problems:** None. /// @@ -29,12 +29,12 @@ declare_clippy_lint! { /// ```rust /// vec.sort_by_key(|a| a.foo()); /// ``` - pub SORT_BY_KEY, + pub UNNECESSARY_SORT_BY, complexity, - "Use of `Vec::sort_by` when `Vec::sort_by_key` would be clearer" + "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer" } -declare_lint_pass!(SortByKey => [SORT_BY_KEY]); +declare_lint_pass!(UnnecessarySortBy => [UNNECESSARY_SORT_BY]); enum LintTrigger { Sort(SortDetection), @@ -205,12 +205,12 @@ fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option } } -impl LateLintPass<'_, '_> for SortByKey { +impl LateLintPass<'_, '_> for UnnecessarySortBy { fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) { match detect_lint(cx, expr) { Some(LintTrigger::SortByKey(trigger)) => utils::span_lint_and_sugg( cx, - SORT_BY_KEY, + UNNECESSARY_SORT_BY, expr.span, "use Vec::sort_by_key here instead", "try", @@ -225,7 +225,7 @@ impl LateLintPass<'_, '_> for SortByKey { ), Some(LintTrigger::Sort(trigger)) => utils::span_lint_and_sugg( cx, - SORT_BY_KEY, + UNNECESSARY_SORT_BY, expr.span, "use Vec::sort here instead", "try", diff --git a/tests/ui/sort_by_key.fixed b/tests/ui/unnecessary_sort_by.fixed similarity index 100% rename from tests/ui/sort_by_key.fixed rename to tests/ui/unnecessary_sort_by.fixed diff --git a/tests/ui/sort_by_key.rs b/tests/ui/unnecessary_sort_by.rs similarity index 100% rename from tests/ui/sort_by_key.rs rename to tests/ui/unnecessary_sort_by.rs diff --git a/tests/ui/sort_by_key.stderr b/tests/ui/unnecessary_sort_by.stderr similarity index 100% rename from tests/ui/sort_by_key.stderr rename to tests/ui/unnecessary_sort_by.stderr From 20cb512e81ad03a014b40c377a01fdebaea66963 Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Sun, 31 May 2020 12:06:32 -0700 Subject: [PATCH 33/72] Updated test cases and formatted --- clippy_lints/src/lib.rs | 2 +- tests/ui/unnecessary_sort_by.fixed | 1 - tests/ui/unnecessary_sort_by.rs | 1 - tests/ui/unnecessary_sort_by.stderr | 24 ++++++++---------------- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 46df743b5bfd2..fd832d1157772 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -304,7 +304,6 @@ mod serde_api; mod shadow; mod single_component_path_imports; mod slow_vector_initialization; -mod unnecessary_sort_by; mod strings; mod suspicious_trait_impl; mod swap; @@ -319,6 +318,7 @@ mod try_err; mod types; mod unicode; mod unnamed_address; +mod unnecessary_sort_by; mod unsafe_removed_from_name; mod unused_io_amount; mod unused_self; diff --git a/tests/ui/unnecessary_sort_by.fixed b/tests/ui/unnecessary_sort_by.fixed index bb88df1a56c92..4521ae38d4956 100644 --- a/tests/ui/unnecessary_sort_by.fixed +++ b/tests/ui/unnecessary_sort_by.fixed @@ -1,5 +1,4 @@ // run-rustfix -#![warn(clippy::sort_by_key)] use std::cmp::Reverse; diff --git a/tests/ui/unnecessary_sort_by.rs b/tests/ui/unnecessary_sort_by.rs index 953c573d406d8..fdb5a82336956 100644 --- a/tests/ui/unnecessary_sort_by.rs +++ b/tests/ui/unnecessary_sort_by.rs @@ -1,5 +1,4 @@ // run-rustfix -#![warn(clippy::sort_by_key_reverse)] use std::cmp::Reverse; diff --git a/tests/ui/unnecessary_sort_by.stderr b/tests/ui/unnecessary_sort_by.stderr index 291fd5500f790..b6365c1709db7 100644 --- a/tests/ui/unnecessary_sort_by.stderr +++ b/tests/ui/unnecessary_sort_by.stderr @@ -1,48 +1,40 @@ error: use Vec::sort here instead - --> $DIR/sort_by_key.rs:13:5 + --> $DIR/unnecessary_sort_by.rs:12:5 | LL | vec.sort_by(|a, b| a.cmp(b)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort()` | - = note: `-D clippy::sort-by-key` implied by `-D warnings` + = note: `-D clippy::unnecessary-sort-by` implied by `-D warnings` error: use Vec::sort_by_key here instead - --> $DIR/sort_by_key.rs:14:5 + --> $DIR/unnecessary_sort_by.rs:13:5 | LL | vec.sort_by(|a, b| (a + 5).abs().cmp(&(b + 5).abs())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&a| (a + 5).abs())` error: use Vec::sort_by_key here instead - --> $DIR/sort_by_key.rs:15:5 + --> $DIR/unnecessary_sort_by.rs:14:5 | LL | vec.sort_by(|a, b| id(-a).cmp(&id(-b))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&a| id(-a))` error: use Vec::sort_by_key here instead - --> $DIR/sort_by_key.rs:17:5 + --> $DIR/unnecessary_sort_by.rs:16:5 | LL | vec.sort_by(|a, b| b.cmp(a)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse(b))` error: use Vec::sort_by_key here instead - --> $DIR/sort_by_key.rs:18:5 + --> $DIR/unnecessary_sort_by.rs:17:5 | LL | vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse((b + 5).abs()))` error: use Vec::sort_by_key here instead - --> $DIR/sort_by_key.rs:19:5 + --> $DIR/unnecessary_sort_by.rs:18:5 | LL | vec.sort_by(|a, b| id(-b).cmp(&id(-a))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse(id(-b)))` -error: unknown clippy lint: clippy::sort_by_key_reverse - --> $DIR/sort_by_key.rs:2:9 - | -LL | #![warn(clippy::sort_by_key_reverse)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::sort_by_key` - | - = note: `-D clippy::unknown-clippy-lints` implied by `-D warnings` - -error: aborting due to 7 previous errors +error: aborting due to 6 previous errors From 32fde0b5116b3a1115d11c49a9bf2af2ebdd5773 Mon Sep 17 00:00:00 2001 From: Ericko Samudera Date: Mon, 25 May 2020 23:22:01 +0700 Subject: [PATCH 34/72] New lint: iter_next_slice --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 3 + clippy_lints/src/loops.rs | 26 ++++----- clippy_lints/src/methods/mod.rs | 84 ++++++++++++++++++++++++++- clippy_lints/src/needless_continue.rs | 2 +- src/lintlist/mod.rs | 7 +++ tests/ui/into_iter_on_ref.fixed | 2 + tests/ui/into_iter_on_ref.rs | 2 + tests/ui/into_iter_on_ref.stderr | 8 ++- tests/ui/iter_next_slice.fixed | 24 ++++++++ tests/ui/iter_next_slice.rs | 24 ++++++++ tests/ui/iter_next_slice.stderr | 28 +++++++++ tests/ui/needless_collect.fixed | 2 +- tests/ui/needless_collect.stderr | 16 ++--- 14 files changed, 204 insertions(+), 25 deletions(-) create mode 100644 tests/ui/iter_next_slice.fixed create mode 100644 tests/ui/iter_next_slice.rs create mode 100644 tests/ui/iter_next_slice.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ac9057199ff3..714e25a32ea64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1401,6 +1401,7 @@ Released 2018-09-13 [`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements [`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect [`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop +[`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice [`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth [`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero [`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 057d39d4c8252..7c16dbd8f2676 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -669,6 +669,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &methods::INTO_ITER_ON_REF, &methods::ITERATOR_STEP_BY_ZERO, &methods::ITER_CLONED_COLLECT, + &methods::ITER_NEXT_SLICE, &methods::ITER_NTH, &methods::ITER_NTH_ZERO, &methods::ITER_SKIP_NEXT, @@ -1303,6 +1304,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&methods::INTO_ITER_ON_REF), LintId::of(&methods::ITERATOR_STEP_BY_ZERO), LintId::of(&methods::ITER_CLONED_COLLECT), + LintId::of(&methods::ITER_NEXT_SLICE), LintId::of(&methods::ITER_NTH), LintId::of(&methods::ITER_NTH_ZERO), LintId::of(&methods::ITER_SKIP_NEXT), @@ -1483,6 +1485,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&methods::CHARS_NEXT_CMP), LintId::of(&methods::INTO_ITER_ON_REF), LintId::of(&methods::ITER_CLONED_COLLECT), + LintId::of(&methods::ITER_NEXT_SLICE), LintId::of(&methods::ITER_NTH_ZERO), LintId::of(&methods::ITER_SKIP_NEXT), LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC), diff --git a/clippy_lints/src/loops.rs b/clippy_lints/src/loops.rs index 38a5829b3f745..dbe41823a9cf6 100644 --- a/clippy_lints/src/loops.rs +++ b/clippy_lints/src/loops.rs @@ -27,7 +27,7 @@ use rustc_middle::middle::region; use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; -use rustc_span::BytePos; +use rustc_span::symbol::Symbol; use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, Place, PlaceBase}; use std::iter::{once, Iterator}; use std::mem; @@ -2381,32 +2381,32 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, ' match_type(cx, ty, &paths::BTREEMAP) || is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) { if method.ident.name == sym!(len) { - let span = shorten_needless_collect_span(expr); + let span = shorten_span(expr, sym!(collect)); span_lint_and_sugg( cx, NEEDLESS_COLLECT, span, NEEDLESS_COLLECT_MSG, "replace with", - ".count()".to_string(), + "count()".to_string(), Applicability::MachineApplicable, ); } if method.ident.name == sym!(is_empty) { - let span = shorten_needless_collect_span(expr); + let span = shorten_span(expr, sym!(iter)); span_lint_and_sugg( cx, NEEDLESS_COLLECT, span, NEEDLESS_COLLECT_MSG, "replace with", - ".next().is_none()".to_string(), + "get(0).is_none()".to_string(), Applicability::MachineApplicable, ); } if method.ident.name == sym!(contains) { let contains_arg = snippet(cx, args[1].span, "??"); - let span = shorten_needless_collect_span(expr); + let span = shorten_span(expr, sym!(collect)); span_lint_and_then( cx, NEEDLESS_COLLECT, @@ -2422,7 +2422,7 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, ' span, "replace with", format!( - ".any(|{}| x == {})", + "any(|{}| x == {})", arg, pred ), Applicability::MachineApplicable, @@ -2435,13 +2435,13 @@ fn check_needless_collect<'a, 'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'a, ' } } -fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span { - if_chain! { - if let ExprKind::MethodCall(_, _, ref args) = expr.kind; - if let ExprKind::MethodCall(_, ref span, _) = args[0].kind; - then { - return expr.span.with_lo(span.lo() - BytePos(1)); +fn shorten_span(expr: &Expr<'_>, target_fn_name: Symbol) -> Span { + let mut current_expr = expr; + while let ExprKind::MethodCall(ref path, ref span, ref args) = current_expr.kind { + if path.ident.name == target_fn_name { + return expr.span.with_lo(span.lo()); } + current_expr = &args[0]; } unreachable!() } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 32b3b7f7947f6..7cb04d4d81c4c 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -26,7 +26,7 @@ use rustc_span::symbol::{sym, SymbolStr}; use crate::consts::{constant, Constant}; use crate::utils::usage::mutated_variables; use crate::utils::{ - get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, in_macro, is_copy, + get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, in_macro, is_copy, is_ctor_or_promotable_const_function, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment, match_def_path, match_qpath, match_trait_method, match_type, match_var, method_calls, method_chain_args, paths, remove_blocks, return_ty, same_tys, single_segment_path, snippet, snippet_with_applicability, @@ -1242,6 +1242,32 @@ declare_clippy_lint! { "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`" } +declare_clippy_lint! { + /// **What it does:** Checks for usage of `iter().next()` on a Slice or an Array + /// + /// **Why is this bad?** These can be shortened into `.get()` + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let a = [1, 2, 3]; + /// # let b = vec![1, 2, 3]; + /// a[2..].iter().next(); + /// b.iter().next(); + /// ``` + /// should be written as: + /// ```rust + /// # let a = [1, 2, 3]; + /// # let b = vec![1, 2, 3]; + /// a.get(2); + /// b.get(0); + /// ``` + pub ITER_NEXT_SLICE, + style, + "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`" +} + declare_lint_pass!(Methods => [ UNWRAP_USED, EXPECT_USED, @@ -1273,6 +1299,7 @@ declare_lint_pass!(Methods => [ FIND_MAP, MAP_FLATTEN, ITERATOR_STEP_BY_ZERO, + ITER_NEXT_SLICE, ITER_NTH, ITER_NTH_ZERO, ITER_SKIP_NEXT, @@ -1320,6 +1347,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods { }, ["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]), ["next", "skip_while"] => lint_skip_while_next(cx, expr, arg_lists[1]), + ["next", "iter"] => lint_iter_next(cx, expr, arg_lists[1]), ["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]), ["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]), ["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1]), @@ -2184,6 +2212,60 @@ fn lint_step_by<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &hir::Expr<'_>, args } } +fn lint_iter_next<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>, iter_args: &'tcx [hir::Expr<'_>]) { + let caller_expr = &iter_args[0]; + + // Skip lint if the `iter().next()` expression is a for loop argument, + // since it is already covered by `&loops::ITER_NEXT_LOOP` + let mut parent_expr_opt = get_parent_expr(cx, expr); + while let Some(parent_expr) = parent_expr_opt { + if higher::for_loop(parent_expr).is_some() { + return; + } + parent_expr_opt = get_parent_expr(cx, parent_expr); + } + + if derefs_to_slice(cx, caller_expr, cx.tables.expr_ty(caller_expr)).is_some() { + // caller is a Slice + if_chain! { + if let hir::ExprKind::Index(ref caller_var, ref index_expr) = &caller_expr.kind; + if let Some(higher::Range { start: Some(start_expr), end: None, limits: ast::RangeLimits::HalfOpen }) + = higher::range(cx, index_expr); + if let hir::ExprKind::Lit(ref start_lit) = &start_expr.kind; + if let ast::LitKind::Int(start_idx, _) = start_lit.node; + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + ITER_NEXT_SLICE, + expr.span, + "Using `.iter().next()` on a Slice without end index.", + "try calling", + format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx), + applicability, + ); + } + } + } else if is_type_diagnostic_item(cx, cx.tables.expr_ty(caller_expr), sym!(vec_type)) + || matches!(&walk_ptrs_ty(cx.tables.expr_ty(caller_expr)).kind, ty::Array(_, _)) + { + // caller is a Vec or an Array + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + ITER_NEXT_SLICE, + expr.span, + "Using `.iter().next()` on an array", + "try calling", + format!( + "{}.get(0)", + snippet_with_applicability(cx, caller_expr.span, "..", &mut applicability) + ), + applicability, + ); + } +} + fn lint_iter_nth<'a, 'tcx>( cx: &LateContext<'a, 'tcx>, expr: &hir::Expr<'_>, diff --git a/clippy_lints/src/needless_continue.rs b/clippy_lints/src/needless_continue.rs index 28183810df489..a971d041ca661 100644 --- a/clippy_lints/src/needless_continue.rs +++ b/clippy_lints/src/needless_continue.rs @@ -424,7 +424,7 @@ fn erode_from_back(s: &str) -> String { } fn span_of_first_expr_in_block(block: &ast::Block) -> Option { - block.stmts.iter().next().map(|stmt| stmt.span) + block.stmts.get(0).map(|stmt| stmt.span) } #[cfg(test)] diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 8211a57b56439..79da1f3702e35 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -934,6 +934,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "loops", }, + Lint { + name: "iter_next_slice", + group: "style", + desc: "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`", + deprecation: None, + module: "methods", + }, Lint { name: "iter_nth", group: "perf", diff --git a/tests/ui/into_iter_on_ref.fixed b/tests/ui/into_iter_on_ref.fixed index c30d23de3f869..7f92d0dbdc973 100644 --- a/tests/ui/into_iter_on_ref.fixed +++ b/tests/ui/into_iter_on_ref.fixed @@ -40,4 +40,6 @@ fn main() { let _ = (&HashSet::::new()).iter(); //~ WARN equivalent to .iter() let _ = std::path::Path::new("12/34").iter(); //~ WARN equivalent to .iter() let _ = std::path::PathBuf::from("12/34").iter(); //~ ERROR equivalent to .iter() + + let _ = (&[1, 2, 3]).iter().next(); //~ WARN equivalent to .iter() } diff --git a/tests/ui/into_iter_on_ref.rs b/tests/ui/into_iter_on_ref.rs index 94bc1689619a2..416056d3fdb9c 100644 --- a/tests/ui/into_iter_on_ref.rs +++ b/tests/ui/into_iter_on_ref.rs @@ -40,4 +40,6 @@ fn main() { let _ = (&HashSet::::new()).into_iter(); //~ WARN equivalent to .iter() let _ = std::path::Path::new("12/34").into_iter(); //~ WARN equivalent to .iter() let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter() + + let _ = (&[1, 2, 3]).into_iter().next(); //~ WARN equivalent to .iter() } diff --git a/tests/ui/into_iter_on_ref.stderr b/tests/ui/into_iter_on_ref.stderr index 80e2d104f824f..1cd6400b0195b 100644 --- a/tests/ui/into_iter_on_ref.stderr +++ b/tests/ui/into_iter_on_ref.stderr @@ -156,5 +156,11 @@ error: this `.into_iter()` call is equivalent to `.iter()` and will not move the LL | let _ = std::path::PathBuf::from("12/34").into_iter(); //~ ERROR equivalent to .iter() | ^^^^^^^^^ help: call directly: `iter` -error: aborting due to 26 previous errors +error: this `.into_iter()` call is equivalent to `.iter()` and will not move the `array` + --> $DIR/into_iter_on_ref.rs:44:26 + | +LL | let _ = (&[1, 2, 3]).into_iter().next(); //~ WARN equivalent to .iter() + | ^^^^^^^^^ help: call directly: `iter` + +error: aborting due to 27 previous errors diff --git a/tests/ui/iter_next_slice.fixed b/tests/ui/iter_next_slice.fixed new file mode 100644 index 0000000000000..79c1db87ac3c4 --- /dev/null +++ b/tests/ui/iter_next_slice.fixed @@ -0,0 +1,24 @@ +// run-rustfix +#![warn(clippy::iter_next_slice)] + +fn main() { + // test code goes here + let s = [1, 2, 3]; + let v = vec![1, 2, 3]; + + s.get(0); + // Should be replaced by s.get(0) + + s.get(2); + // Should be replaced by s.get(2) + + v.get(5); + // Should be replaced by v.get(5) + + v.get(0); + // Should be replaced by v.get(0) + + let o = Some(5); + o.iter().next(); + // Shouldn't be linted since this is not a Slice or an Array +} diff --git a/tests/ui/iter_next_slice.rs b/tests/ui/iter_next_slice.rs new file mode 100644 index 0000000000000..ef9a55f3d997c --- /dev/null +++ b/tests/ui/iter_next_slice.rs @@ -0,0 +1,24 @@ +// run-rustfix +#![warn(clippy::iter_next_slice)] + +fn main() { + // test code goes here + let s = [1, 2, 3]; + let v = vec![1, 2, 3]; + + s.iter().next(); + // Should be replaced by s.get(0) + + s[2..].iter().next(); + // Should be replaced by s.get(2) + + v[5..].iter().next(); + // Should be replaced by v.get(5) + + v.iter().next(); + // Should be replaced by v.get(0) + + let o = Some(5); + o.iter().next(); + // Shouldn't be linted since this is not a Slice or an Array +} diff --git a/tests/ui/iter_next_slice.stderr b/tests/ui/iter_next_slice.stderr new file mode 100644 index 0000000000000..bbf61df0cda68 --- /dev/null +++ b/tests/ui/iter_next_slice.stderr @@ -0,0 +1,28 @@ +error: Using `.iter().next()` on an array + --> $DIR/iter_next_slice.rs:9:5 + | +LL | s.iter().next(); + | ^^^^^^^^^^^^^^^ help: try calling: `s.get(0)` + | + = note: `-D clippy::iter-next-slice` implied by `-D warnings` + +error: Using `.iter().next()` on a Slice without end index. + --> $DIR/iter_next_slice.rs:12:5 + | +LL | s[2..].iter().next(); + | ^^^^^^^^^^^^^^^^^^^^ help: try calling: `s.get(2)` + +error: Using `.iter().next()` on a Slice without end index. + --> $DIR/iter_next_slice.rs:15:5 + | +LL | v[5..].iter().next(); + | ^^^^^^^^^^^^^^^^^^^^ help: try calling: `v.get(5)` + +error: Using `.iter().next()` on an array + --> $DIR/iter_next_slice.rs:18:5 + | +LL | v.iter().next(); + | ^^^^^^^^^^^^^^^ help: try calling: `v.get(0)` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/needless_collect.fixed b/tests/ui/needless_collect.fixed index b4227eaf2f8bb..be37dc16b9a3e 100644 --- a/tests/ui/needless_collect.fixed +++ b/tests/ui/needless_collect.fixed @@ -9,7 +9,7 @@ use std::collections::{BTreeSet, HashMap, HashSet}; fn main() { let sample = [1; 5]; let len = sample.iter().count(); - if sample.iter().next().is_none() { + if sample.get(0).is_none() { // Empty } sample.iter().cloned().any(|x| x == 1); diff --git a/tests/ui/needless_collect.stderr b/tests/ui/needless_collect.stderr index 8884c8e161293..9113aad90dd7c 100644 --- a/tests/ui/needless_collect.stderr +++ b/tests/ui/needless_collect.stderr @@ -1,28 +1,28 @@ error: avoid using `collect()` when not needed - --> $DIR/needless_collect.rs:11:28 + --> $DIR/needless_collect.rs:11:29 | LL | let len = sample.iter().collect::>().len(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.count()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` | = note: `-D clippy::needless-collect` implied by `-D warnings` error: avoid using `collect()` when not needed - --> $DIR/needless_collect.rs:12:21 + --> $DIR/needless_collect.rs:12:15 | LL | if sample.iter().collect::>().is_empty() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.next().is_none()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `get(0).is_none()` error: avoid using `collect()` when not needed - --> $DIR/needless_collect.rs:15:27 + --> $DIR/needless_collect.rs:15:28 | LL | sample.iter().cloned().collect::>().contains(&1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.any(|x| x == 1)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)` error: avoid using `collect()` when not needed - --> $DIR/needless_collect.rs:16:34 + --> $DIR/needless_collect.rs:16:35 | LL | sample.iter().map(|x| (x, x)).collect::>().len(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `.count()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` error: aborting due to 4 previous errors From 7727c4ac7f3d4c977866bd8e6659a3e27f0bb6aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Kr=C3=BCger?= Date: Thu, 28 May 2020 21:42:01 +0200 Subject: [PATCH 35/72] CONTRIBUTING: explain how to use cargo dev ra-setup Fixes #5514 --- CONTRIBUTING.md | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f47ac98fd20a..9f7bdcb1be7e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,14 +12,16 @@ anything, feel free to ask questions on issues or visit the `#clippy` on [Discor All contributors are expected to follow the [Rust Code of Conduct]. -* [Getting started](#getting-started) - * [Finding something to fix/improve](#finding-something-to-fiximprove) -* [Writing code](#writing-code) -* [How Clippy works](#how-clippy-works) -* [Fixing nightly build failures](#fixing-build-failures-caused-by-rust) -* [Issue and PR Triage](#issue-and-pr-triage) -* [Bors and Homu](#bors-and-homu) -* [Contributions](#contributions) +- [Contributing to Clippy](#contributing-to-clippy) + - [Getting started](#getting-started) + - [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) + - [How Clippy works](#how-clippy-works) + - [Fixing build failures caused by Rust](#fixing-build-failures-caused-by-rust) + - [Issue and PR triage](#issue-and-pr-triage) + - [Bors and Homu](#bors-and-homu) + - [Contributions](#contributions) [Discord]: https://discord.gg/rust-lang [Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct @@ -91,6 +93,24 @@ quick read. [rfc_stability]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#stability-guarantees [rfc_lint_cats]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#lint-audit-and-categories +## Getting code-completion for rustc internals to work + +Unfortunately, [`rust-analyzer`][ra_homepage] does not (yet?) understand how Clippy uses compiler-internals +using `extern crate` and it also needs to be able to read the source files of the rustc-compiler which are not +available via a `rustup` component at the time of writing. +To work around this, you need to have a copy of the [rustc-repo][rustc_repo] available which can be obtained via +`git clone https://github.com/rust-lang/rust/`. +Then you can run a `cargo dev` command to automatically make Clippy use the rustc-repo via path-dependencies +which rust-analyzer will be able to understand. +Run `cargo dev ra-setup --repo-path ` where `` is an absolute path to the rustc repo +you just cloned. +The command will add path-dependencies pointing towards rustc-crates inside the rustc repo to +Clippys `Cargo.toml`s and should allow rust-analyzer to understand most of the types that Clippy uses. +Just make sure to remove the dependencies again before finally making a pull request! + +[ra_homepage]: https://rust-analyzer.github.io/ +[rustc_repo]: https://github.com/rust-lang/rust/ + ## How Clippy works [`clippy_lints/src/lib.rs`][lint_crate_entry] imports all the different lint modules and registers in the [`LintStore`]. From 9a5baed482b68e0d9806e19eb9e8676d7ff3e1c2 Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Sun, 31 May 2020 15:09:12 -0700 Subject: [PATCH 36/72] Implement suggestions from phansch --- clippy_lints/src/unnecessary_sort_by.rs | 41 ++++++++++++++++++++----- tests/ui/unnecessary_sort_by.fixed | 7 +++-- tests/ui/unnecessary_sort_by.rs | 7 +++-- tests/ui/unnecessary_sort_by.stderr | 26 ++++++++++------ 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/unnecessary_sort_by.rs b/clippy_lints/src/unnecessary_sort_by.rs index c0858ec4c8880..33d8331c2923c 100644 --- a/clippy_lints/src/unnecessary_sort_by.rs +++ b/clippy_lints/src/unnecessary_sort_by.rs @@ -18,15 +18,25 @@ declare_clippy_lint! { /// possible) than to use `Vec::sort_by` and and a more complicated /// closure. /// - /// **Known problems:** None. + /// **Known problems:** + /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't + /// imported by a use statement in the current frame, then a `use` + /// statement that imports it will need to be added (which this lint + /// can't do). /// /// **Example:** /// /// ```rust - /// vec.sort_by(|a, b| a.foo().cmp(b.foo())); + /// # struct A; + /// # impl A { fn foo(&self) {} } + /// # let mut vec: Vec = Vec::new(); + /// vec.sort_by(|a, b| a.foo().cmp(&b.foo())); /// ``` /// Use instead: /// ```rust + /// # struct A; + /// # impl A { fn foo(&self) {} } + /// # let mut vec: Vec = Vec::new(); /// vec.sort_by_key(|a| a.foo()); /// ``` pub UNNECESSARY_SORT_BY, @@ -50,6 +60,7 @@ struct SortByKeyDetection { vec_name: String, closure_arg: String, closure_body: String, + reverse: bool, unstable: bool, } @@ -172,16 +183,16 @@ fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option if let ExprKind::MethodCall(method_path, _, [ref left_expr, ref right_expr]) = &closure_body.value.kind; if method_path.ident.name.to_ident_string() == "cmp"; then { - let (closure_body, closure_arg) = if mirrored_exprs( + let (closure_body, closure_arg, reverse) = if mirrored_exprs( &cx, &left_expr, &left_ident, &right_expr, &right_ident ) { - (Sugg::hir(cx, &left_expr, "..").to_string(), left_ident.name.to_string()) + (Sugg::hir(cx, &left_expr, "..").to_string(), left_ident.name.to_string(), false) } else if mirrored_exprs(&cx, &left_expr, &right_ident, &right_expr, &left_ident) { - (format!("Reverse({})", Sugg::hir(cx, &left_expr, "..").to_string()), right_ident.name.to_string()) + (Sugg::hir(cx, &left_expr, "..").to_string(), right_ident.name.to_string(), true) } else { return None; }; @@ -196,7 +207,13 @@ fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option Some(LintTrigger::Sort(SortDetection { vec_name, unstable })) } else { - Some(LintTrigger::SortByKey(SortByKeyDetection { vec_name, unstable, closure_arg, closure_body })) + Some(LintTrigger::SortByKey(SortByKeyDetection { + vec_name, + unstable, + closure_arg, + closure_body, + reverse + })) } } } else { @@ -219,9 +236,17 @@ impl LateLintPass<'_, '_> for UnnecessarySortBy { trigger.vec_name, if trigger.unstable { "_unstable" } else { "" }, trigger.closure_arg, - trigger.closure_body, + if trigger.reverse { + format!("Reverse({})", trigger.closure_body) + } else { + trigger.closure_body.to_string() + }, ), - Applicability::MachineApplicable, + if trigger.reverse { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }, ), Some(LintTrigger::Sort(trigger)) => utils::span_lint_and_sugg( cx, diff --git a/tests/ui/unnecessary_sort_by.fixed b/tests/ui/unnecessary_sort_by.fixed index 4521ae38d4956..779fd57707ad4 100644 --- a/tests/ui/unnecessary_sort_by.fixed +++ b/tests/ui/unnecessary_sort_by.fixed @@ -10,16 +10,17 @@ fn main() { let mut vec: Vec = vec![3, 6, 1, 2, 5]; // Forward examples vec.sort(); + vec.sort_unstable(); vec.sort_by_key(|&a| (a + 5).abs()); - vec.sort_by_key(|&a| id(-a)); + vec.sort_unstable_by_key(|&a| id(-a)); // Reverse examples vec.sort_by_key(|&b| Reverse(b)); vec.sort_by_key(|&b| Reverse((b + 5).abs())); - vec.sort_by_key(|&b| Reverse(id(-b))); + vec.sort_unstable_by_key(|&b| Reverse(id(-b))); // Negative examples (shouldn't be changed) let c = &7; vec.sort_by(|a, b| (b - a).cmp(&(a - b))); vec.sort_by(|_, b| b.cmp(&5)); vec.sort_by(|_, b| b.cmp(c)); - vec.sort_by(|a, _| a.cmp(c)); + vec.sort_unstable_by(|a, _| a.cmp(c)); } diff --git a/tests/ui/unnecessary_sort_by.rs b/tests/ui/unnecessary_sort_by.rs index fdb5a82336956..0485a5630afef 100644 --- a/tests/ui/unnecessary_sort_by.rs +++ b/tests/ui/unnecessary_sort_by.rs @@ -10,16 +10,17 @@ fn main() { let mut vec: Vec = vec![3, 6, 1, 2, 5]; // Forward examples vec.sort_by(|a, b| a.cmp(b)); + vec.sort_unstable_by(|a, b| a.cmp(b)); vec.sort_by(|a, b| (a + 5).abs().cmp(&(b + 5).abs())); - vec.sort_by(|a, b| id(-a).cmp(&id(-b))); + vec.sort_unstable_by(|a, b| id(-a).cmp(&id(-b))); // Reverse examples vec.sort_by(|a, b| b.cmp(a)); vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs())); - vec.sort_by(|a, b| id(-b).cmp(&id(-a))); + vec.sort_unstable_by(|a, b| id(-b).cmp(&id(-a))); // Negative examples (shouldn't be changed) let c = &7; vec.sort_by(|a, b| (b - a).cmp(&(a - b))); vec.sort_by(|_, b| b.cmp(&5)); vec.sort_by(|_, b| b.cmp(c)); - vec.sort_by(|a, _| a.cmp(c)); + vec.sort_unstable_by(|a, _| a.cmp(c)); } diff --git a/tests/ui/unnecessary_sort_by.stderr b/tests/ui/unnecessary_sort_by.stderr index b6365c1709db7..903b6e5099ce8 100644 --- a/tests/ui/unnecessary_sort_by.stderr +++ b/tests/ui/unnecessary_sort_by.stderr @@ -6,35 +6,41 @@ LL | vec.sort_by(|a, b| a.cmp(b)); | = note: `-D clippy::unnecessary-sort-by` implied by `-D warnings` -error: use Vec::sort_by_key here instead +error: use Vec::sort here instead --> $DIR/unnecessary_sort_by.rs:13:5 | +LL | vec.sort_unstable_by(|a, b| a.cmp(b)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable()` + +error: use Vec::sort_by_key here instead + --> $DIR/unnecessary_sort_by.rs:14:5 + | LL | vec.sort_by(|a, b| (a + 5).abs().cmp(&(b + 5).abs())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&a| (a + 5).abs())` error: use Vec::sort_by_key here instead - --> $DIR/unnecessary_sort_by.rs:14:5 + --> $DIR/unnecessary_sort_by.rs:15:5 | -LL | vec.sort_by(|a, b| id(-a).cmp(&id(-b))); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&a| id(-a))` +LL | vec.sort_unstable_by(|a, b| id(-a).cmp(&id(-b))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|&a| id(-a))` error: use Vec::sort_by_key here instead - --> $DIR/unnecessary_sort_by.rs:16:5 + --> $DIR/unnecessary_sort_by.rs:17:5 | LL | vec.sort_by(|a, b| b.cmp(a)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse(b))` error: use Vec::sort_by_key here instead - --> $DIR/unnecessary_sort_by.rs:17:5 + --> $DIR/unnecessary_sort_by.rs:18:5 | LL | vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse((b + 5).abs()))` error: use Vec::sort_by_key here instead - --> $DIR/unnecessary_sort_by.rs:18:5 + --> $DIR/unnecessary_sort_by.rs:19:5 | -LL | vec.sort_by(|a, b| id(-b).cmp(&id(-a))); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|&b| Reverse(id(-b)))` +LL | vec.sort_unstable_by(|a, b| id(-b).cmp(&id(-a))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|&b| Reverse(id(-b)))` -error: aborting due to 6 previous errors +error: aborting due to 7 previous errors From b89880a30ce4dd7887614f305a565b6779dc4825 Mon Sep 17 00:00:00 2001 From: JarredAllen Date: Sun, 31 May 2020 15:19:31 -0700 Subject: [PATCH 37/72] Ran update_lints --- CHANGELOG.md | 2 +- clippy_lints/src/lib.rs | 6 +++--- src/lintlist/mod.rs | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c00f84bdb8570..086a1141be552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1555,7 +1555,6 @@ Released 2018-09-13 [`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else [`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 -[`sort_by_key_reverse`]: https://rust-lang.github.io/rust-clippy/master/index.html#sort_by_key_reverse [`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 @@ -1602,6 +1601,7 @@ Released 2018-09-13 [`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold [`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed [`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation +[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by [`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap [`unneeded_field_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_field_pattern [`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index fd832d1157772..03addf1f4a40c 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -780,7 +780,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &shadow::SHADOW_UNRELATED, &single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS, &slow_vector_initialization::SLOW_VECTOR_INITIALIZATION, - &unnecessary_sort_by::UNNECESSARY_SORT_BY, &strings::STRING_ADD, &strings::STRING_ADD_ASSIGN, &strings::STRING_LIT_AS_BYTES, @@ -835,6 +834,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &unicode::ZERO_WIDTH_SPACE, &unnamed_address::FN_ADDRESS_COMPARISONS, &unnamed_address::VTABLE_ADDRESS_COMPARISONS, + &unnecessary_sort_by::UNNECESSARY_SORT_BY, &unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME, &unused_io_amount::UNUSED_IO_AMOUNT, &unused_self::UNUSED_SELF, @@ -1394,7 +1394,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&serde_api::SERDE_API_MISUSE), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), - LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&strings::STRING_LIT_AS_BYTES), LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), @@ -1431,6 +1430,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&unicode::ZERO_WIDTH_SPACE), LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS), LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS), + LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), LintId::of(&unwrap::PANICKING_UNWRAP), @@ -1596,7 +1596,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&ranges::RANGE_ZIP_WITH_LEN), LintId::of(&reference::DEREF_ADDROF), LintId::of(&reference::REF_IN_DEREF), - LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&swap::MANUAL_SWAP), LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT), LintId::of(&transmute::CROSSPOINTER_TRANSMUTE), @@ -1613,6 +1612,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::UNIT_ARG), LintId::of(&types::UNNECESSARY_CAST), LintId::of(&types::VEC_BOX), + LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&unwrap::UNNECESSARY_UNWRAP), LintId::of(&useless_conversion::USELESS_CONVERSION), LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO), diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index b5d9ef0110e25..ab9542a7b9c8c 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1984,13 +1984,6 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "slow_vector_initialization", }, - Lint { - name: "sort_by_key_reverse", - group: "complexity", - desc: "Use of `Vec::sort_by` when `Vec::sort_by_key` would be clearer", - deprecation: None, - module: "sort_by_key_reverse", - }, Lint { name: "string_add", group: "restriction", @@ -2299,6 +2292,13 @@ pub static ref ALL_LINTS: Vec = vec![ deprecation: None, module: "no_effect", }, + Lint { + name: "unnecessary_sort_by", + group: "complexity", + desc: "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer", + deprecation: None, + module: "unnecessary_sort_by", + }, Lint { name: "unnecessary_unwrap", group: "complexity", From 6955420ace822ec888cc999a623c67c51ced839f Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Mon, 1 Jun 2020 00:28:58 +0200 Subject: [PATCH 38/72] Update changelog for stable:1.44 beta:1.45 --- CHANGELOG.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7dae3dcfff0f..fcc9895dd901d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,87 @@ document. ## Unreleased / In Rust Nightly -[891e1a8...master](https://github.com/rust-lang/rust-clippy/compare/891e1a8...master) +[7ea7cd1...master](https://github.com/rust-lang/rust-clippy/compare/7ea7cd1...master) + +## Rust 1.45 + +Current beta, release 2020-07-16 + +[891e1a8...7ea7cd1](https://github.com/rust-lang/rust-clippy/compare/891e1a8...7ea7cd1) + +### New lints + +* [`match_wildcard_for_single_variants`] [#5582](https://github.com/rust-lang/rust-clippy/pull/5582) +* [`unsafe_derive_deserialize`] [#5493](https://github.com/rust-lang/rust-clippy/pull/5493) +* [`if_let_mutex`] [#5332](https://github.com/rust-lang/rust-clippy/pull/5332) +* [`mismatched_target_os`] [#5506](https://github.com/rust-lang/rust-clippy/pull/5506) +* [`await_holding_lock`] [#5439](https://github.com/rust-lang/rust-clippy/pull/5439) +* [`match_on_vec_items`] [#5522](https://github.com/rust-lang/rust-clippy/pull/5522) +* [`manual_async_fn`] [#5576](https://github.com/rust-lang/rust-clippy/pull/5576) +* [`reversed_empty_ranges`] [#5583](https://github.com/rust-lang/rust-clippy/pull/5583) +* [`manual_non_exhaustive`] [#5550](https://github.com/rust-lang/rust-clippy/pull/5550) + +### Moves and Deprecations + +* Downgrade [`match_bool`] to pedantic [#5408](https://github.com/rust-lang/rust-clippy/pull/5408) +* Downgrade [`match_wild_err_arm`] to pedantic and update help messages. [#5622](https://github.com/rust-lang/rust-clippy/pull/5622) +* Downgrade [`useless_let_if_seq`] to nursery. [#5599](https://github.com/rust-lang/rust-clippy/pull/5599) +* Generalize [`option_and_then_some`] and rename to [`bind_instead_of_map`]. [#5529](https://github.com/rust-lang/rust-clippy/pull/5529) +* Rename [`identity_conversion`] to [`useless_conversion`]. [#5568](https://github.com/rust-lang/rust-clippy/pull/5568) +* Merge [`block_in_if_condition_expr`] and [`block_in_if_condition_stmt`] into [`blocks_in_if_conditions`]. +[#5563](https://github.com/rust-lang/rust-clippy/pull/5563) +* Merge [`option_map_unwrap_or`], [`option_map_unwrap_or_else`] and [`result_map_unwrap_or_else`] into [`map_unwrap_or`]. +[#5563](https://github.com/rust-lang/rust-clippy/pull/5563) +* Merge [`option_unwrap_used`] and [`result_unwrap_used`] into [`unwrap_used`]. +[#5563](https://github.com/rust-lang/rust-clippy/pull/5563) +* Merge [`option_expect_used`] and [`result_expect_used`] into [`expect_used`]. +[#5563](https://github.com/rust-lang/rust-clippy/pull/5563) +* Merge [`for_loop_over_option`] and [`for_loop_over_result`] into [`for_loops_over_fallibles`]. +[#5563](https://github.com/rust-lang/rust-clippy/pull/5563) + +### Enhancements + +* Avoid running cargo/internal lints when not enabled. [#5505](https://github.com/rust-lang/rust-clippy/pull/5505) +* Extend [`useless_conversion`] with `TryFrom` and `TryInto`. [#5631](https://github.com/rust-lang/rust-clippy/pull/5631) +* Lint also in type parameters and where clauses in [`unused_unit`]. [#5592](https://github.com/rust-lang/rust-clippy/pull/5592) +* Do not suggest deriving `Default` in [`new_without_default`]. [#5616](https://github.com/rust-lang/rust-clippy/pull/5616) + +### False Positive Fixes + +* [`while_let_on_iterator`] [#5525](https://github.com/rust-lang/rust-clippy/pull/5525) +* [`empty_line_after_outer_attr`] [#5609](https://github.com/rust-lang/rust-clippy/pull/5609) +* [`unnecessary_unwrap`] [#5558](https://github.com/rust-lang/rust-clippy/pull/5558) +* [`comparison_chain`] [#5596](https://github.com/rust-lang/rust-clippy/pull/5596) +* Don't trigger [`used_underscore_binding`] in await desugaring. [#5535](https://github.com/rust-lang/rust-clippy/pull/5535) +* Don't trigger [`borrowed_box`] on mutable references. [#5491](https://github.com/rust-lang/rust-clippy/pull/5491) +* Allow `1 << 0` in [`identity_op`]. [#5602](https://github.com/rust-lang/rust-clippy/pull/5602) +* Allow `use super::*;` glob imports in [`wildcard_imports`]. [#5564](https://github.com/rust-lang/rust-clippy/pull/5564) +* Add ignores to the list of words of [`clippy::doc_markdown`]. [#5611](https://github.com/rust-lang/rust-clippy/pull/5611) +* Skip dev and build deps in [`multiple_crate_versions`]. [#5636](https://github.com/rust-lang/rust-clippy/pull/5636) +* Honor `allow` attribute on arguments in [`ptr_arg`]. [#5647](https://github.com/rust-lang/rust-clippy/pull/5647) +* Honor lint level attributes for [`redundant_field_names`] and [`non_expressive_names`]. [#5651](https://github.com/rust-lang/rust-clippy/pull/5651) +* Ignore calls to `len` in [`or_fun_call`]. [#4429](https://github.com/rust-lang/rust-clippy/pull/4429) + +### Suggestion Improvements + +* Simplify suggestions in [`manual_memcpy`]. [#5536](https://github.com/rust-lang/rust-clippy/pull/5536) +* Fix suggestion in [`redundant_pattern_matching`] for macros. [#5511](https://github.com/rust-lang/rust-clippy/pull/5511) +* Avoid suggesting `copied()` for mutable references in [`map_clone`]. [#5530](https://github.com/rust-lang/rust-clippy/pull/5530) +* Improve help message for [`clone_double_ref`]. [#5547](https://github.com/rust-lang/rust-clippy/pull/5547) + +### ICE Fixes + +* Fix ICE caused in unwrap module. [#5590](https://github.com/rust-lang/rust-clippy/pull/5590) +* Fix crash on rustc test issue-69020-assoc-const-arith-overflow.rs [#5499](https://github.com/rust-lang/rust-clippy/pull/5499) + +### Documentation + +* Clarify the documentation of [`unnecessary_mut_passed`]. [#5639](https://github.com/rust-lang/rust-clippy/pull/5639) +* Extend example for [`unneeded_field_pattern`]. [#5541](https://github.com/rust-lang/rust-clippy/pull/5541) ## Rust 1.44 -Current beta, release 2020-06-04 +Current stable, released 2020-06-04 [204bb9b...891e1a8](https://github.com/rust-lang/rust-clippy/compare/204bb9b...891e1a8) @@ -93,7 +169,7 @@ Current beta, release 2020-06-04 ## Rust 1.43 -Current stable, released 2020-04-23 +Released 2020-04-23 [4ee1206...204bb9b](https://github.com/rust-lang/rust-clippy/compare/4ee1206...204bb9b) From ae0ce2255aea7e896cbfc0330c9d4f17ed66b55f Mon Sep 17 00:00:00 2001 From: Philipp Hansch Date: Mon, 1 Jun 2020 09:58:42 +0200 Subject: [PATCH 39/72] Add regression test for string_lit_as_bytes issue --- tests/ui/string_lit_as_bytes.fixed | 2 ++ tests/ui/string_lit_as_bytes.rs | 2 ++ tests/ui/string_lit_as_bytes.stderr | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/ui/string_lit_as_bytes.fixed b/tests/ui/string_lit_as_bytes.fixed index 7ad272ade5f9c..ccf8f61c4a92c 100644 --- a/tests/ui/string_lit_as_bytes.fixed +++ b/tests/ui/string_lit_as_bytes.fixed @@ -14,6 +14,8 @@ fn str_lit_as_bytes() { let strify = stringify!(foobar).as_bytes(); + let current_version = env!("CARGO_PKG_VERSION").as_bytes(); + let includestr = include_bytes!("entry_unfixable.rs"); let _ = b"string with newline\t\n"; diff --git a/tests/ui/string_lit_as_bytes.rs b/tests/ui/string_lit_as_bytes.rs index 1bf4538b7c94f..178df08e249ef 100644 --- a/tests/ui/string_lit_as_bytes.rs +++ b/tests/ui/string_lit_as_bytes.rs @@ -14,6 +14,8 @@ fn str_lit_as_bytes() { let strify = stringify!(foobar).as_bytes(); + let current_version = env!("CARGO_PKG_VERSION").as_bytes(); + let includestr = include_str!("entry_unfixable.rs").as_bytes(); let _ = "string with newline\t\n".as_bytes(); diff --git a/tests/ui/string_lit_as_bytes.stderr b/tests/ui/string_lit_as_bytes.stderr index ff6e3346dfc75..99c512354d589 100644 --- a/tests/ui/string_lit_as_bytes.stderr +++ b/tests/ui/string_lit_as_bytes.stderr @@ -13,13 +13,13 @@ LL | let bs = r###"raw string with 3# plus " ""###.as_bytes(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using a byte string literal instead: `br###"raw string with 3# plus " ""###` error: calling `as_bytes()` on `include_str!(..)` - --> $DIR/string_lit_as_bytes.rs:17:22 + --> $DIR/string_lit_as_bytes.rs:19:22 | LL | let includestr = include_str!("entry_unfixable.rs").as_bytes(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `include_bytes!(..)` instead: `include_bytes!("entry_unfixable.rs")` error: calling `as_bytes()` on a string literal - --> $DIR/string_lit_as_bytes.rs:19:13 + --> $DIR/string_lit_as_bytes.rs:21:13 | LL | let _ = "string with newline/t/n".as_bytes(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using a byte string literal instead: `b"string with newline/t/n"` From 861b897c54200becd6767ad6e091abef61f15344 Mon Sep 17 00:00:00 2001 From: Philipp Hansch Date: Mon, 1 Jun 2020 10:20:17 +0200 Subject: [PATCH 40/72] Add regression test for endless loop This was fixed in pulldown_cmark 0.7.1, specifically https://github.com/raphlinus/pulldown-cmark/pull/438 --- clippy_lints/Cargo.toml | 2 +- tests/ui/crashes/regressions.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 98391732d1893..e959c1a651122 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -21,7 +21,7 @@ cargo_metadata = "0.9.1" if_chain = "1.0.0" itertools = "0.9" lazy_static = "1.0.2" -pulldown-cmark = { version = "0.7", default-features = false } +pulldown-cmark = { version = "0.7.1", default-features = false } quine-mc_cluskey = "0.2.2" regex-syntax = "0.6" serde = { version = "1.0", features = ["derive"] } diff --git a/tests/ui/crashes/regressions.rs b/tests/ui/crashes/regressions.rs index 623ae51f9f08c..3d5063d1a3a7d 100644 --- a/tests/ui/crashes/regressions.rs +++ b/tests/ui/crashes/regressions.rs @@ -6,4 +6,8 @@ pub fn foo(bar: *const u8) { println!("{:#p}", bar); } +// Regression test for https://github.com/rust-lang/rust-clippy/issues/4917 +/// Date: Wed, 27 May 2020 16:24:53 +0200 Subject: [PATCH 41/72] Fix some code examples in doc --- clippy_lints/src/assign_ops.rs | 4 +++ clippy_lints/src/double_parens.rs | 18 +++++++++++-- clippy_lints/src/drop_bounds.rs | 4 +++ clippy_lints/src/duration_subsec.rs | 6 +++++ clippy_lints/src/enum_variants.rs | 32 +++++++++++++++++++---- clippy_lints/src/eq_op.rs | 4 +++ clippy_lints/src/escape.rs | 7 +++++ clippy_lints/src/eta_reduction.rs | 4 +++ clippy_lints/src/eval_order_dependence.rs | 6 +++++ 9 files changed, 78 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/assign_ops.rs b/clippy_lints/src/assign_ops.rs index 05e2650d0b715..13e61fe98bac1 100644 --- a/clippy_lints/src/assign_ops.rs +++ b/clippy_lints/src/assign_ops.rs @@ -24,7 +24,11 @@ declare_clippy_lint! { /// let mut a = 5; /// let b = 0; /// // ... + /// // Bad /// a = a + b; + /// + /// // Good + /// a += b; /// ``` pub ASSIGN_OP_PATTERN, style, diff --git a/clippy_lints/src/double_parens.rs b/clippy_lints/src/double_parens.rs index 7f2ff8b9b26f6..05517f6f9f0cc 100644 --- a/clippy_lints/src/double_parens.rs +++ b/clippy_lints/src/double_parens.rs @@ -13,10 +13,24 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust + /// // Bad + /// fn simple_double_parens() -> i32 { + /// ((0)) + /// } + /// + /// // Good + /// fn simple_no_parens() -> i32 { + /// 0 + /// } + /// + /// // or + /// /// # fn foo(bar: usize) {} - /// ((0)); + /// // Bad /// foo((0)); - /// ((1, 2)); + /// + /// // Good + /// foo(0); /// ``` pub DOUBLE_PARENS, complexity, diff --git a/clippy_lints/src/drop_bounds.rs b/clippy_lints/src/drop_bounds.rs index f496680827913..4ef963ac3148e 100644 --- a/clippy_lints/src/drop_bounds.rs +++ b/clippy_lints/src/drop_bounds.rs @@ -27,6 +27,10 @@ declare_clippy_lint! { /// ```rust /// fn foo() {} /// ``` + /// Could be written as: + /// ```rust + /// fn foo() {} + /// ``` pub DROP_BOUNDS, correctness, "Bounds of the form `T: Drop` are useless" diff --git a/clippy_lints/src/duration_subsec.rs b/clippy_lints/src/duration_subsec.rs index b35a8facf8b99..afefa2506381b 100644 --- a/clippy_lints/src/duration_subsec.rs +++ b/clippy_lints/src/duration_subsec.rs @@ -22,8 +22,14 @@ declare_clippy_lint! { /// ```rust /// # use std::time::Duration; /// let dur = Duration::new(5, 0); + /// + /// // Bad /// let _micros = dur.subsec_nanos() / 1_000; /// let _millis = dur.subsec_nanos() / 1_000_000; + /// + /// // Good + /// let _micros = dur.subsec_micros(); + /// let _millis = dur.subsec_millis(); /// ``` pub DURATION_SUBSEC, complexity, diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs index a5871cf0cd4dd..cb0fd59a2d407 100644 --- a/clippy_lints/src/enum_variants.rs +++ b/clippy_lints/src/enum_variants.rs @@ -25,31 +25,47 @@ declare_clippy_lint! { /// BattenbergCake, /// } /// ``` + /// Could be written as: + /// ```rust + /// enum Cake { + /// BlackForest, + /// Hummingbird, + /// Battenberg, + /// } + /// ``` pub ENUM_VARIANT_NAMES, style, "enums where all variants share a prefix/postfix" } declare_clippy_lint! { - /// **What it does:** Detects enumeration variants that are prefixed or suffixed - /// by the same characters. + /// **What it does:** Detects public enumeration variants that are + /// prefixed or suffixed by the same characters. /// - /// **Why is this bad?** Enumeration variant names should specify their variant, + /// **Why is this bad?** Public enumeration variant names should specify their variant, /// not repeat the enumeration name. /// /// **Known problems:** None. /// /// **Example:** /// ```rust - /// enum Cake { + /// pub enum Cake { /// BlackForestCake, /// HummingbirdCake, /// BattenbergCake, /// } /// ``` + /// Could be written as: + /// ```rust + /// pub enum Cake { + /// BlackForest, + /// Hummingbird, + /// Battenberg, + /// } + /// ``` pub PUB_ENUM_VARIANT_NAMES, pedantic, - "enums where all variants share a prefix/postfix" + "public enums where all variants share a prefix/postfix" } declare_clippy_lint! { @@ -66,6 +82,12 @@ declare_clippy_lint! { /// struct BlackForestCake; /// } /// ``` + /// Could be written as: + /// ```rust + /// mod cake { + /// struct BlackForest; + /// } + /// ``` pub MODULE_NAME_REPETITIONS, pedantic, "type names prefixed/postfixed with their containing module's name" diff --git a/clippy_lints/src/eq_op.rs b/clippy_lints/src/eq_op.rs index 4e1c1f131405f..d7819d737ea04 100644 --- a/clippy_lints/src/eq_op.rs +++ b/clippy_lints/src/eq_op.rs @@ -39,7 +39,11 @@ declare_clippy_lint! { /// /// **Example:** /// ```ignore + /// // Bad /// &x == y + /// + /// // Good + /// x == *y /// ``` pub OP_REF, style, diff --git a/clippy_lints/src/escape.rs b/clippy_lints/src/escape.rs index 1ec60a0e6e67a..7227683aa5ac2 100644 --- a/clippy_lints/src/escape.rs +++ b/clippy_lints/src/escape.rs @@ -28,9 +28,16 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// # fn foo(bar: usize) {} + /// + /// // Bad /// let x = Box::new(1); /// foo(*x); /// println!("{}", *x); + /// + /// // Good + /// let x = 1; + /// foo(x); + /// println!("{}", x); /// ``` pub BOXED_LOCAL, perf, diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index e3e1136b67693..5f0cd1ec72cee 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -26,7 +26,11 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust,ignore + /// // Bad /// xs.map(|x| foo(x)) + /// + /// // Good + /// foo(xs) /// ``` /// where `foo(_)` is a plain function that takes the exact argument type of /// `x`. diff --git a/clippy_lints/src/eval_order_dependence.rs b/clippy_lints/src/eval_order_dependence.rs index 5206266ccf2a6..37e24ff34f7ae 100644 --- a/clippy_lints/src/eval_order_dependence.rs +++ b/clippy_lints/src/eval_order_dependence.rs @@ -21,11 +21,17 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// let mut x = 0; + /// + /// // Bad /// let a = { /// x = 1; /// 1 /// } + x; /// // Unclear whether a is 1 or 2. + /// + /// // Good + /// x = 1; + /// let a = 1 + x; /// ``` pub EVAL_ORDER_DEPENDENCE, complexity, From 262c9dc025042646610df879dd9708eea625534d Mon Sep 17 00:00:00 2001 From: ThibsG Date: Fri, 29 May 2020 18:15:42 +0200 Subject: [PATCH 42/72] Fix more code examples --- clippy_lints/src/fallible_impl_from.rs | 15 +++++++++++++-- clippy_lints/src/floating_point_arithmetic.rs | 2 -- clippy_lints/src/format.rs | 6 +++++- clippy_lints/src/functions.rs | 14 ++++++++++---- clippy_lints/src/implicit_saturating_sub.rs | 7 ------- clippy_lints/src/int_plus_one.rs | 1 - clippy_lints/src/integer_division.rs | 11 +++++++---- clippy_lints/src/items_after_statements.rs | 16 ++++++++++++++++ 8 files changed, 51 insertions(+), 21 deletions(-) diff --git a/clippy_lints/src/fallible_impl_from.rs b/clippy_lints/src/fallible_impl_from.rs index 17639cc2a0643..575462f25e61d 100644 --- a/clippy_lints/src/fallible_impl_from.rs +++ b/clippy_lints/src/fallible_impl_from.rs @@ -18,13 +18,24 @@ declare_clippy_lint! { /// **Known problems:** None. /// /// **Example:** - /// ```rust + /// ```rust,ignore /// struct Foo(i32); + /// + /// // Bad /// impl From for Foo { /// fn from(s: String) -> Self { /// Foo(s.parse().unwrap()) /// } /// } + /// + /// // Good + /// use std::convert::TryFrom; + /// impl TryFrom for Foo { + /// type Error = (); + /// fn try_from(s: String) -> Result { + /// s.parse() + /// } + /// } /// ``` pub FALLIBLE_IMPL_FROM, nursery, @@ -120,7 +131,7 @@ fn lint_impl_body<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, impl_span: Span, impl_it move |diag| { diag.help( "`From` is intended for infallible conversions only. \ - Use `TryFrom` if there's a possibility for the conversion to fail."); + Use `TryFrom` if there's a possibility for the conversion to fail."); diag.span_note(fpu.result, "potential failure(s)"); }); } diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index 86317fb8bd5c4..3a912d928375d 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -28,7 +28,6 @@ declare_clippy_lint! { /// **Example:** /// /// ```rust - /// /// let a = 3f32; /// let _ = a.powf(1.0 / 3.0); /// let _ = (1.0 + a).ln(); @@ -38,7 +37,6 @@ declare_clippy_lint! { /// is better expressed as /// /// ```rust - /// /// let a = 3f32; /// let _ = a.cbrt(); /// let _ = a.ln_1p(); diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index 5b092526ce4f2..1530538aa7d13 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -25,9 +25,13 @@ declare_clippy_lint! { /// /// **Examples:** /// ```rust + /// + /// // Bad /// # let foo = "foo"; - /// format!("foo"); /// format!("{}", foo); + /// + /// // Good + /// format!("foo"); /// ``` pub USELESS_FORMAT, complexity, diff --git a/clippy_lints/src/functions.rs b/clippy_lints/src/functions.rs index c24a24733d7f3..9b5f1dee7f44a 100644 --- a/clippy_lints/src/functions.rs +++ b/clippy_lints/src/functions.rs @@ -49,11 +49,11 @@ declare_clippy_lint! { /// **Known problems:** None. /// /// **Example:** - /// ``` rust + /// ```rust /// fn im_too_long() { - /// println!(""); + /// println!(""); /// // ... 100 more LoC - /// println!(""); + /// println!(""); /// } /// ``` pub TOO_MANY_LINES, @@ -79,10 +79,16 @@ declare_clippy_lint! { /// `some_argument.get_raw_ptr()`). /// /// **Example:** - /// ```rust + /// ```rust,ignore + /// // Bad /// pub fn foo(x: *const u8) { /// println!("{}", unsafe { *x }); /// } + /// + /// // Good + /// pub unsafe fn foo(x: *const u8) { + /// println!("{}", unsafe { *x }); + /// } /// ``` pub NOT_UNSAFE_PTR_ARG_DEREF, correctness, diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index 155a93de4facf..fdaf37e5e08fa 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -25,13 +25,6 @@ declare_clippy_lint! { /// if i != 0 { /// i -= 1; /// } - /// ``` - /// Use instead: - /// ```rust - /// let end: u32 = 10; - /// let start: u32 = 5; - /// - /// let mut i: u32 = end - start; /// /// // Good /// i = i.saturating_sub(1); diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs index d5dbd495680b2..e91fb0c2f27cd 100644 --- a/clippy_lints/src/int_plus_one.rs +++ b/clippy_lints/src/int_plus_one.rs @@ -10,7 +10,6 @@ use crate::utils::{snippet_opt, span_lint_and_sugg}; declare_clippy_lint! { /// **What it does:** Checks for usage of `x >= y + 1` or `x - 1 >= y` (and `<=`) in a block /// - /// /// **Why is this bad?** Readability -- better to use `> y` instead of `>= y + 1`. /// /// **Known problems:** None. diff --git a/clippy_lints/src/integer_division.rs b/clippy_lints/src/integer_division.rs index fe34d33fe652c..d537ef3f3238e 100644 --- a/clippy_lints/src/integer_division.rs +++ b/clippy_lints/src/integer_division.rs @@ -15,10 +15,13 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust - /// fn main() { - /// let x = 3 / 2; - /// println!("{}", x); - /// } + /// // Bad + /// let x = 3 / 2; + /// println!("{}", x); + /// + /// // Good + /// let x = 3f32 / 2f32; + /// println!("{}", x); /// ``` pub INTEGER_DIVISION, restriction, diff --git a/clippy_lints/src/items_after_statements.rs b/clippy_lints/src/items_after_statements.rs index e7062b7c16bb9..c8576bcfcb444 100644 --- a/clippy_lints/src/items_after_statements.rs +++ b/clippy_lints/src/items_after_statements.rs @@ -16,6 +16,7 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust + /// // Bad /// fn foo() { /// println!("cake"); /// } @@ -28,6 +29,21 @@ declare_clippy_lint! { /// foo(); // prints "foo" /// } /// ``` + /// + /// ```rust + /// // Good + /// fn foo() { + /// println!("cake"); + /// } + /// + /// fn main() { + /// fn foo() { + /// println!("foo"); + /// } + /// foo(); // prints "foo" + /// foo(); // prints "foo" + /// } + /// ``` pub ITEMS_AFTER_STATEMENTS, pedantic, "blocks where an item comes after a statement" From 19339334cb4e9c6db5a1f7dced38edcb16707bc7 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Sun, 31 May 2020 11:38:48 +0200 Subject: [PATCH 43/72] Give more corrected code examples in doc --- clippy_lints/src/literal_representation.rs | 12 +++++ clippy_lints/src/matches.rs | 44 ++++++++++++++++--- clippy_lints/src/misc.rs | 31 ++++++++++++- clippy_lints/src/misc_early.rs | 34 +++++++++++--- clippy_lints/src/mut_reference.rs | 4 ++ clippy_lints/src/mutex_atomic.rs | 12 ++++- clippy_lints/src/needless_bool.rs | 3 +- clippy_lints/src/needless_borrow.rs | 8 +++- clippy_lints/src/needless_pass_by_value.rs | 3 +- clippy_lints/src/needless_update.rs | 10 +++++ clippy_lints/src/ptr.rs | 23 ++++++---- clippy_lints/src/question_mark.rs | 2 +- clippy_lints/src/reference.rs | 5 +++ clippy_lints/src/regex.rs | 12 ++--- clippy_lints/src/shadow.rs | 10 +++++ .../src/single_component_path_imports.rs | 4 +- .../src/slow_vector_initialization.rs | 8 +++- clippy_lints/src/strings.rs | 8 ++++ 18 files changed, 195 insertions(+), 38 deletions(-) diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index ec7c4531ed716..7ba43562d7d44 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -24,7 +24,11 @@ declare_clippy_lint! { /// **Example:** /// /// ```rust + /// // Bad /// let x: u64 = 61864918973511; + /// + /// // Good + /// let x: u64 = 61_864_918_973_511; /// ``` pub UNREADABLE_LITERAL, pedantic, @@ -44,7 +48,11 @@ declare_clippy_lint! { /// **Example:** /// /// ```rust + /// // Probably mistyped /// 2_32; + /// + /// // Good + /// 2_i32; /// ``` pub MISTYPED_LITERAL_SUFFIXES, correctness, @@ -63,7 +71,11 @@ declare_clippy_lint! { /// **Example:** /// /// ```rust + /// // Bad /// let x: u64 = 618_64_9189_73_511; + /// + /// // Good + /// let x: u64 = 61_864_918_973_511; /// ``` pub INCONSISTENT_DIGIT_GROUPING, style, diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 94380acfcfd4c..146212cb2c7a1 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -36,10 +36,17 @@ declare_clippy_lint! { /// ```rust /// # fn bar(stool: &str) {} /// # let x = Some("abc"); + /// + /// // Bad /// match x { /// Some(ref foo) => bar(foo), /// _ => (), /// } + /// + /// // Good + /// if let Some(ref foo) = x { + /// bar(foo); + /// } /// ``` pub SINGLE_MATCH, style, @@ -97,11 +104,19 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust,ignore + /// // Bad /// match x { /// &A(ref y) => foo(y), /// &B => bar(), /// _ => frob(&x), /// } + /// + /// // Good + /// match *x { + /// A(ref y) => foo(y), + /// B => bar(), + /// _ => frob(x), + /// } /// ``` pub MATCH_REF_PATS, style, @@ -197,10 +212,15 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// let x: Option<()> = None; + /// + /// // Bad /// let r: Option<&()> = match x { /// None => None, /// Some(ref v) => Some(v), /// }; + /// + /// // Good + /// let r: Option<&()> = x.as_ref(); /// ``` pub MATCH_AS_REF, complexity, @@ -219,10 +239,18 @@ declare_clippy_lint! { /// ```rust /// # enum Foo { A(usize), B(usize) } /// # let x = Foo::B(1); + /// + /// // Bad /// match x { /// Foo::A(_) => {}, /// _ => {}, /// } + /// + /// // Good + /// match x { + /// Foo::A(_) => {}, + /// Foo::B(_) => {}, + /// } /// ``` pub WILDCARD_ENUM_MATCH_ARM, restriction, @@ -242,16 +270,15 @@ declare_clippy_lint! { /// ```rust /// # enum Foo { A, B, C } /// # let x = Foo::B; + /// + /// // Bad /// match x { /// Foo::A => {}, /// Foo::B => {}, /// _ => {}, /// } - /// ``` - /// Use instead: - /// ```rust - /// # enum Foo { A, B, C } - /// # let x = Foo::B; + /// + /// // Good /// match x { /// Foo::A => {}, /// Foo::B => {}, @@ -273,10 +300,17 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust + /// // Bad /// match "foo" { /// "a" => {}, /// "bar" | _ => {}, /// } + /// + /// // Good + /// match "foo" { + /// "a" => {}, + /// _ => {}, + /// } /// ``` pub WILDCARD_IN_OR_PATTERNS, complexity, diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index e1d524c2231e4..a3b7134a376d5 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -38,10 +38,16 @@ declare_clippy_lint! { /// dereferences, e.g., changing `*x` to `x` within the function. /// /// **Example:** - /// ```rust + /// ```rust,ignore + /// // Bad /// fn foo(ref x: u8) -> bool { /// true /// } + /// + /// // Good + /// fn foo(x: &u8) -> bool { + /// true + /// } /// ``` pub TOPLEVEL_REF_ARG, style, @@ -60,7 +66,11 @@ declare_clippy_lint! { /// ```rust /// # let x = 1.0; /// + /// // Bad /// if x == f32::NAN { } + /// + /// // Good + /// if x.is_nan() { } /// ``` pub CMP_NAN, correctness, @@ -83,8 +93,15 @@ declare_clippy_lint! { /// ```rust /// let x = 1.2331f64; /// let y = 1.2332f64; + /// + /// // Bad /// if y == 1.23f64 { } /// if y != x {} // where both are floats + /// + /// // Good + /// let error = 0.01f64; // Use an epsilon for comparison + /// if (y - 1.23f64).abs() < error { } + /// if (y - x).abs() > error { } /// ``` pub FLOAT_CMP, correctness, @@ -191,7 +208,11 @@ declare_clippy_lint! { /// **Example:** /// /// ```rust + /// // Bad /// let a = 0 as *const u32; + /// + /// // Good + /// let a = std::ptr::null::(); /// ``` pub ZERO_PTR, style, @@ -214,7 +235,13 @@ declare_clippy_lint! { /// ```rust /// let x: f64 = 1.0; /// const ONE: f64 = 1.00; - /// x == ONE; // where both are floats + /// + /// // Bad + /// if x == ONE { } // where both are floats + /// + /// // Good + /// let error = 0.1f64; // Use an epsilon for comparison + /// if (x - ONE).abs() < error { } /// ``` pub FLOAT_CMP_CONST, restriction, diff --git a/clippy_lints/src/misc_early.rs b/clippy_lints/src/misc_early.rs index 552222eba2ee2..ad39e59d0678a 100644 --- a/clippy_lints/src/misc_early.rs +++ b/clippy_lints/src/misc_early.rs @@ -59,7 +59,11 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust + /// // Bad /// fn foo(a: i32, _a: i32) {} + /// + /// // Good + /// fn bar(a: i32, _b: i32) {} /// ``` pub DUPLICATE_UNDERSCORE_ARGUMENT, style, @@ -77,7 +81,11 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust,ignore - /// (|| 42)() + /// // Bad + /// let a = (|| 42)() + /// + /// // Good + /// let a = 42 /// ``` pub REDUNDANT_CLOSURE_CALL, complexity, @@ -112,7 +120,11 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust + /// // Bad /// let y = 0x1a9BAcD; + /// + /// // Good + /// let y = 0x1A9BACD; /// ``` pub MIXED_CASE_HEX_LITERALS, style, @@ -129,7 +141,11 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust + /// // Bad /// let y = 123832i32; + /// + /// // Good + /// let y = 123832_i32; /// ``` pub UNSEPARATED_LITERAL_SUFFIX, pedantic, @@ -207,9 +223,16 @@ declare_clippy_lint! { /// ```rust /// # let v = Some("abc"); /// + /// // Bad + /// match v { + /// Some(x) => (), + /// y @ _ => (), + /// } + /// + /// // Good /// match v { /// Some(x) => (), - /// y @ _ => (), // easier written as `y`, + /// y => (), /// } /// ``` pub REDUNDANT_PATTERN, @@ -235,16 +258,13 @@ declare_clippy_lint! { /// # struct TupleStruct(u32, u32, u32); /// # let t = TupleStruct(1, 2, 3); /// + /// // Bad /// match t { /// TupleStruct(0, .., _) => (), /// _ => (), /// } - /// ``` - /// can be written as - /// ```rust - /// # struct TupleStruct(u32, u32, u32); - /// # let t = TupleStruct(1, 2, 3); /// + /// // Good /// match t { /// TupleStruct(0, ..) => (), /// _ => (), diff --git a/clippy_lints/src/mut_reference.rs b/clippy_lints/src/mut_reference.rs index 67a1ac78a6777..58a8e1a1064ae 100644 --- a/clippy_lints/src/mut_reference.rs +++ b/clippy_lints/src/mut_reference.rs @@ -16,7 +16,11 @@ declare_clippy_lint! { /// /// **Example:** /// ```ignore + /// // Bad /// my_vec.push(&mut value) + /// + /// // Good + /// my_vec.push(&value) /// ``` pub UNNECESSARY_MUT_PASSED, style, diff --git a/clippy_lints/src/mutex_atomic.rs b/clippy_lints/src/mutex_atomic.rs index 4e1a8be4892e6..78b15afc5a7fa 100644 --- a/clippy_lints/src/mutex_atomic.rs +++ b/clippy_lints/src/mutex_atomic.rs @@ -22,9 +22,15 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust + /// # let y = true; + /// + /// // Bad /// # use std::sync::Mutex; - /// # let y = 1; /// let x = Mutex::new(&y); + /// + /// // Good + /// # use std::sync::atomic::AtomicBool; + /// let x = AtomicBool::new(y); /// ``` pub MUTEX_ATOMIC, perf, @@ -46,6 +52,10 @@ declare_clippy_lint! { /// ```rust /// # use std::sync::Mutex; /// let x = Mutex::new(0usize); + /// + /// // Good + /// # use std::sync::atomic::AtomicUsize; + /// let x = AtomicUsize::new(0usize); /// ``` pub MUTEX_INTEGER, nursery, diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index efa77db822dd0..15b129fa09802 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -15,8 +15,7 @@ use rustc_span::Span; declare_clippy_lint! { /// **What it does:** Checks for expressions of the form `if c { true } else { - /// false }` - /// (or vice versa) and suggest using the condition directly. + /// false }` (or vice versa) and suggests using the condition directly. /// /// **Why is this bad?** Redundant code. /// diff --git a/clippy_lints/src/needless_borrow.rs b/clippy_lints/src/needless_borrow.rs index 9ee875d7516eb..5880d1d610206 100644 --- a/clippy_lints/src/needless_borrow.rs +++ b/clippy_lints/src/needless_borrow.rs @@ -18,12 +18,16 @@ declare_clippy_lint! { /// **Why is this bad?** Suggests that the receiver of the expression borrows /// the expression. /// + /// **Known problems:** None. + /// /// **Example:** /// ```rust + /// // Bad /// let x: &i32 = &&&&&&5; - /// ``` /// - /// **Known problems:** None. + /// // Good + /// let x: &i32 = &5; + /// ``` pub NEEDLESS_BORROW, nursery, "taking a reference that is going to be automatically dereferenced" diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 9c508fc0e4a1a..fbdf927b82476 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -40,9 +40,8 @@ declare_clippy_lint! { /// assert_eq!(v.len(), 42); /// } /// ``` - /// + /// should be /// ```rust - /// // should be /// fn foo(v: &[i32]) { /// assert_eq!(v.len(), 42); /// } diff --git a/clippy_lints/src/needless_update.rs b/clippy_lints/src/needless_update.rs index 4b2586877e562..d866bab2f642c 100644 --- a/clippy_lints/src/needless_update.rs +++ b/clippy_lints/src/needless_update.rs @@ -21,6 +21,16 @@ declare_clippy_lint! { /// # z: i32, /// # } /// # let zero_point = Point { x: 0, y: 0, z: 0 }; + /// + /// // Bad + /// Point { + /// x: 1, + /// y: 1, + /// z: 1, + /// ..zero_point + /// }; + /// + /// // Ok /// Point { /// x: 1, /// y: 1, diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 4eac571f96620..c77b44e0c99c7 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -47,7 +47,11 @@ declare_clippy_lint! { /// /// **Example:** /// ```ignore + /// // Bad /// fn foo(&Vec) { .. } + /// + /// // Good + /// fn foo(&[u32]) { .. } /// ``` pub PTR_ARG, style, @@ -65,9 +69,15 @@ declare_clippy_lint! { /// /// **Example:** /// ```ignore + /// // Bad /// if x == ptr::null { /// .. /// } + /// + /// // Good + /// if x.is_null() { + /// .. + /// } /// ``` pub CMP_NULL, style, @@ -76,19 +86,16 @@ declare_clippy_lint! { declare_clippy_lint! { /// **What it does:** This lint checks for functions that take immutable - /// references and return - /// mutable ones. + /// references and return mutable ones. /// /// **Why is this bad?** This is trivially unsound, as one can create two - /// mutable references - /// from the same (immutable!) source. This - /// [error](https://github.com/rust-lang/rust/issues/39465) + /// mutable references from the same (immutable!) source. + /// This [error](https://github.com/rust-lang/rust/issues/39465) /// actually lead to an interim Rust release 1.15.1. /// /// **Known problems:** To be on the conservative side, if there's at least one - /// mutable reference - /// with the output lifetime, this lint will not trigger. In practice, this - /// case is unlikely anyway. + /// mutable reference with the output lifetime, this lint will not trigger. + /// In practice, this case is unlikely anyway. /// /// **Example:** /// ```ignore diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index ea654467b8668..e4361b00fb4c2 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -88,7 +88,7 @@ impl QuestionMark { replacement_str, applicability, ) - } + } } } } diff --git a/clippy_lints/src/reference.rs b/clippy_lints/src/reference.rs index d5797468e9d53..fe457aad50e36 100644 --- a/clippy_lints/src/reference.rs +++ b/clippy_lints/src/reference.rs @@ -16,8 +16,13 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust,ignore + /// // Bad /// let a = f(*&mut b); /// let c = *&d; + /// + /// // Good + /// let a = f(b); + /// let c = d; /// ``` pub DEREF_ADDROF, complexity, diff --git a/clippy_lints/src/regex.rs b/clippy_lints/src/regex.rs index 30084e3e1ffce..a2c35c4267344 100644 --- a/clippy_lints/src/regex.rs +++ b/clippy_lints/src/regex.rs @@ -86,11 +86,13 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Regex { if let Some(span) = is_expn_of(expr.span, "regex"); then { if !self.spans.contains(&span) { - span_lint(cx, - REGEX_MACRO, - span, - "`regex!(_)` found. \ - Please use `Regex::new(_)`, which is faster for now."); + span_lint( + cx, + REGEX_MACRO, + span, + "`regex!(_)` found. \ + Please use `Regex::new(_)`, which is faster for now." + ); self.spans.insert(span); } self.last = Some(block.hir_id); diff --git a/clippy_lints/src/shadow.rs b/clippy_lints/src/shadow.rs index 11360b0ef8495..68c36f9189184 100644 --- a/clippy_lints/src/shadow.rs +++ b/clippy_lints/src/shadow.rs @@ -25,7 +25,12 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// # let x = 1; + /// + /// // Bad /// let x = &x; + /// + /// // Good + /// let y = &x; // use different variable name /// ``` pub SHADOW_SAME, restriction, @@ -77,7 +82,12 @@ declare_clippy_lint! { /// # let y = 1; /// # let z = 2; /// let x = y; + /// + /// // Bad /// let x = z; // shadows the earlier binding + /// + /// // Good + /// let w = z; // use different variable name /// ``` pub SHADOW_UNRELATED, pedantic, diff --git a/clippy_lints/src/single_component_path_imports.rs b/clippy_lints/src/single_component_path_imports.rs index 8d767a7fec88d..2e853e8301d69 100644 --- a/clippy_lints/src/single_component_path_imports.rs +++ b/clippy_lints/src/single_component_path_imports.rs @@ -16,7 +16,7 @@ declare_clippy_lint! { /// /// **Example:** /// - /// ```rust, ignore + /// ```rust,ignore /// use regex; /// /// fn main() { @@ -24,7 +24,7 @@ declare_clippy_lint! { /// } /// ``` /// Better as - /// ```rust, ignore + /// ```rust,ignore /// fn main() { /// regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); /// } diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index fb3706be1c213..a7c4f2c2291f1 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -22,11 +22,17 @@ declare_clippy_lint! { /// ```rust /// # use core::iter::repeat; /// # let len = 4; + /// + /// // Bad /// let mut vec1 = Vec::with_capacity(len); /// vec1.resize(len, 0); /// /// let mut vec2 = Vec::with_capacity(len); - /// vec2.extend(repeat(0).take(len)) + /// vec2.extend(repeat(0).take(len)); + /// + /// // Good + /// let mut vec1 = vec![0; len]; + /// let mut vec2 = vec![0; len]; /// ``` pub SLOW_VECTOR_INITIALIZATION, perf, diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 2c51271e312de..f84566ef707a8 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -24,6 +24,10 @@ declare_clippy_lint! { /// ```rust /// let mut x = "Hello".to_owned(); /// x = x + ", World"; + /// + /// // More readable + /// x += ", World"; + /// x.push_str(", World"); /// ``` pub STRING_ADD_ASSIGN, pedantic, @@ -69,7 +73,11 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust + /// // Bad /// let bs = "a byte string".as_bytes(); + /// + /// // Good + /// let bs = b"a byte string"; /// ``` pub STRING_LIT_AS_BYTES, style, From 9893254dff38c2644612c8465ae9abfa553f4ea3 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Sun, 31 May 2020 12:08:41 +0200 Subject: [PATCH 44/72] Add more corrected code for doc --- clippy_lints/src/methods/mod.rs | 65 ++++++++++++++++++++------ clippy_lints/src/misc.rs | 2 +- clippy_lints/src/vec.rs | 10 +++- clippy_lints/src/verbose_file_reads.rs | 1 + clippy_lints/src/wildcard_imports.rs | 12 +++-- clippy_lints/src/write.rs | 22 ++++++++- clippy_lints/src/zero_div_zero.rs | 6 ++- src/lintlist/mod.rs | 2 +- 8 files changed, 95 insertions(+), 25 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 52ca962e7ef97..fbc29efdeb26b 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -218,7 +218,12 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// # let x = Ok::<_, ()>(()); - /// x.ok().expect("why did I do this again?") + /// + /// // Bad + /// x.ok().expect("why did I do this again?"); + /// + /// // Good + /// x.expect("why did I do this again?"); /// ``` pub OK_EXPECT, style, @@ -273,8 +278,12 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// # let opt = Some(1); - /// opt.map_or(None, |a| Some(a + 1)) - /// # ; + /// + /// // Bad + /// opt.map_or(None, |a| Some(a + 1)); + /// + /// // Good + /// opt.and_then(|a| Some(a + 1)); /// ``` pub OPTION_MAP_OR_NONE, style, @@ -390,14 +399,19 @@ declare_clippy_lint! { /// **What it does:** Checks for usage of `_.map(_).flatten(_)`, /// /// **Why is this bad?** Readability, this can be written more concisely as a - /// single method call. + /// single method call using `_.flat_map(_)` /// /// **Known problems:** /// /// **Example:** /// ```rust /// let vec = vec![vec![1]]; + /// + /// // Bad /// vec.iter().map(|x| x.iter()).flatten(); + /// + /// // Good + /// vec.iter().flat_map(|x| x.iter()); /// ``` pub MAP_FLATTEN, pedantic, @@ -417,7 +431,12 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// let vec = vec![1]; + /// + /// // Bad /// vec.iter().filter(|x| **x == 0).map(|x| *x * 2); + /// + /// // Good + /// vec.iter().filter_map(|x| Some(*x * 2)); /// ``` pub FILTER_MAP, pedantic, @@ -634,7 +653,12 @@ declare_clippy_lint! { /// ```rust /// # use std::rc::Rc; /// let x = Rc::new(1); + /// + /// // Bad /// x.clone(); + /// + /// // Good + /// Rc::clone(&x); /// ``` pub CLONE_ON_REF_PTR, restriction, @@ -741,7 +765,12 @@ declare_clippy_lint! { /// **Known problems:** Does not catch multi-byte unicode characters. /// /// **Example:** - /// `_.split("x")` could be `_.split('x')` + /// ```rust,ignore + /// // Bad + /// _.split("x"); + /// + /// // Good + /// _.split('x'); pub SINGLE_CHAR_PATTERN, perf, "using a single-character str where a char could be used, e.g., `_.split(\"x\")`" @@ -964,8 +993,8 @@ declare_clippy_lint! { } declare_clippy_lint! { - /// **What it does:** Checks for usage of `.chars().last()` or - /// `.chars().next_back()` on a `str` to check if it ends with a given char. + /// **What it does:** Checks for usage of `_.chars().last()` or + /// `_.chars().next_back()` on a `str` to check if it ends with a given char. /// /// **Why is this bad?** Readability, this can be written more concisely as /// `_.ends_with(_)`. @@ -975,8 +1004,12 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// # let name = "_"; - /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-') - /// # ; + /// + /// // Bad + /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-'); + /// + /// // Good + /// name.ends_with('_') || name.ends_with('-'); /// ``` pub CHARS_LAST_CMP, style, @@ -1044,17 +1077,15 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None }); - /// ``` - /// As there is no transformation of the argument this could be written as: - /// ```rust + /// + /// // As there is no transformation of the argument this could be written as: /// let _ = (0..3).filter(|&x| x > 2); /// ``` /// /// ```rust /// let _ = (0..4).filter_map(|x| Some(x + 1)); - /// ``` - /// As there is no conditional check on the argument this could be written as: - /// ```rust + /// + /// // As there is no conditional check on the argument this could be written as: /// let _ = (0..4).map(|x| x + 1); /// ``` pub UNNECESSARY_FILTER_MAP, @@ -1075,7 +1106,11 @@ declare_clippy_lint! { /// **Example:** /// /// ```rust + /// // Bad /// let _ = (&vec![3, 4, 5]).into_iter(); + /// + /// // Good + /// let _ = (&vec![3, 4, 5]).iter(); /// ``` pub INTO_ITER_ON_REF, style, diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index a3b7134a376d5..51282ab93ef6e 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -93,7 +93,7 @@ declare_clippy_lint! { /// ```rust /// let x = 1.2331f64; /// let y = 1.2332f64; - /// + /// /// // Bad /// if y == 1.23f64 { } /// if y != x {} // where both are floats diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/vec.rs index 1174f42157749..a8d4c7620b1ef 100644 --- a/clippy_lints/src/vec.rs +++ b/clippy_lints/src/vec.rs @@ -17,8 +17,14 @@ declare_clippy_lint! { /// **Known problems:** None. /// /// **Example:** - /// ```rust,ignore - /// foo(&vec![1, 2]) + /// ```rust + /// # fn foo(my_vec: &[u8]) {} + /// + /// // Bad + /// foo(&vec![1, 2]); + /// + /// // Good + /// foo(&[1, 2]); /// ``` pub USELESS_VEC, perf, diff --git a/clippy_lints/src/verbose_file_reads.rs b/clippy_lints/src/verbose_file_reads.rs index 4d8d4438d881d..7247518e19b9d 100644 --- a/clippy_lints/src/verbose_file_reads.rs +++ b/clippy_lints/src/verbose_file_reads.rs @@ -9,6 +9,7 @@ declare_clippy_lint! { /// /// **Why is this bad?** `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) + /// /// **Known problems:** None. /// /// **Example:** diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index 32d9a45c37d78..b637253bd0264 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -19,8 +19,14 @@ declare_clippy_lint! { /// still around. /// /// **Example:** - /// ```rust + /// ```rust,ignore + /// // Bad /// use std::cmp::Ordering::*; + /// foo(Less); + /// + /// // Good + /// use std::cmp::Ordering; + /// foo(Ordering::Less) /// ``` pub ENUM_GLOB_USE, pedantic, @@ -60,15 +66,15 @@ declare_clippy_lint! { /// /// **Example:** /// - /// Bad: /// ```rust,ignore + /// // Bad /// use crate1::*; /// /// foo(); /// ``` /// - /// Good: /// ```rust,ignore + /// // Good /// use crate1::foo; /// /// foo(); diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index 5f794598052f6..22ce484b24e40 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -23,7 +23,11 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust + /// // Bad /// println!(""); + /// + /// // Good + /// println!(); /// ``` pub PRINTLN_EMPTY_STRING, style, @@ -32,8 +36,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// **What it does:** This lint warns when you use `print!()` with a format - /// string that - /// ends in a newline. + /// string that ends in a newline. /// /// **Why is this bad?** You should use `println!()` instead, which appends the /// newline. @@ -125,7 +128,12 @@ declare_clippy_lint! { /// ```rust /// # use std::fmt::Write; /// # let mut buf = String::new(); + /// + /// // Bad /// writeln!(buf, ""); + /// + /// // Good + /// writeln!(buf); /// ``` pub WRITELN_EMPTY_STRING, style, @@ -147,7 +155,12 @@ declare_clippy_lint! { /// # use std::fmt::Write; /// # let mut buf = String::new(); /// # let name = "World"; + /// + /// // Bad /// write!(buf, "Hello {}!\n", name); + /// + /// // Good + /// writeln!(buf, "Hello {}!", name); /// ``` pub WRITE_WITH_NEWLINE, style, @@ -168,7 +181,12 @@ declare_clippy_lint! { /// ```rust /// # use std::fmt::Write; /// # let mut buf = String::new(); + /// + /// // Bad /// writeln!(buf, "{}", "foo"); + /// + /// // Good + /// writeln!(buf, "foo"); /// ``` pub WRITE_LITERAL, style, diff --git a/clippy_lints/src/zero_div_zero.rs b/clippy_lints/src/zero_div_zero.rs index fb4700d8743fd..0820385e01bb5 100644 --- a/clippy_lints/src/zero_div_zero.rs +++ b/clippy_lints/src/zero_div_zero.rs @@ -14,7 +14,11 @@ declare_clippy_lint! { /// /// **Example:** /// ```rust - /// 0.0f32 / 0.0; + /// // Bad + /// let nan = 0.0f32 / 0.0; + /// + /// // Good + /// let nan = f32::NAN; /// ``` pub ZERO_DIVIDED_BY_ZERO, complexity, diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index ab9542a7b9c8c..6b6e2c7324c26 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1735,7 +1735,7 @@ pub static ref ALL_LINTS: Vec = vec![ Lint { name: "pub_enum_variant_names", group: "pedantic", - desc: "enums where all variants share a prefix/postfix", + desc: "public enums where all variants share a prefix/postfix", deprecation: None, module: "enum_variants", }, From 137a3b4d3242cfe331f8f9b87c51ac0c431fe160 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Mon, 1 Jun 2020 10:16:01 +0200 Subject: [PATCH 45/72] Corrected doc PR fixes --- clippy_lints/src/drop_bounds.rs | 2 +- clippy_lints/src/eta_reduction.rs | 2 +- clippy_lints/src/eval_order_dependence.rs | 7 +++++-- clippy_lints/src/fallible_impl_from.rs | 12 ++++++++++-- clippy_lints/src/functions.rs | 2 +- clippy_lints/src/methods/mod.rs | 6 +++++- 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/drop_bounds.rs b/clippy_lints/src/drop_bounds.rs index 4ef963ac3148e..5a7f759486edd 100644 --- a/clippy_lints/src/drop_bounds.rs +++ b/clippy_lints/src/drop_bounds.rs @@ -29,7 +29,7 @@ declare_clippy_lint! { /// ``` /// Could be written as: /// ```rust - /// fn foo() {} + /// fn foo() {} /// ``` pub DROP_BOUNDS, correctness, diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 5f0cd1ec72cee..d093025fd3d7a 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -30,7 +30,7 @@ declare_clippy_lint! { /// xs.map(|x| foo(x)) /// /// // Good - /// foo(xs) + /// xs.map(foo) /// ``` /// where `foo(_)` is a plain function that takes the exact argument type of /// `x`. diff --git a/clippy_lints/src/eval_order_dependence.rs b/clippy_lints/src/eval_order_dependence.rs index 37e24ff34f7ae..74144fb299de2 100644 --- a/clippy_lints/src/eval_order_dependence.rs +++ b/clippy_lints/src/eval_order_dependence.rs @@ -30,8 +30,11 @@ declare_clippy_lint! { /// // Unclear whether a is 1 or 2. /// /// // Good - /// x = 1; - /// let a = 1 + x; + /// let tmp = { + /// x = 1; + /// 1 + /// }; + /// let a = tmp + x; /// ``` pub EVAL_ORDER_DEPENDENCE, complexity, diff --git a/clippy_lints/src/fallible_impl_from.rs b/clippy_lints/src/fallible_impl_from.rs index 575462f25e61d..92812816461c5 100644 --- a/clippy_lints/src/fallible_impl_from.rs +++ b/clippy_lints/src/fallible_impl_from.rs @@ -18,7 +18,7 @@ declare_clippy_lint! { /// **Known problems:** None. /// /// **Example:** - /// ```rust,ignore + /// ```rust /// struct Foo(i32); /// /// // Bad @@ -27,13 +27,21 @@ declare_clippy_lint! { /// Foo(s.parse().unwrap()) /// } /// } + /// ``` /// + /// ```rust /// // Good + /// struct Foo(i32); + /// /// use std::convert::TryFrom; /// impl TryFrom for Foo { /// type Error = (); /// fn try_from(s: String) -> Result { - /// s.parse() + /// if let Ok(parsed) = s.parse() { + /// Ok(Foo(parsed)) + /// } else { + /// Err(()) + /// } /// } /// } /// ``` diff --git a/clippy_lints/src/functions.rs b/clippy_lints/src/functions.rs index 9b5f1dee7f44a..325b6cf32a3d2 100644 --- a/clippy_lints/src/functions.rs +++ b/clippy_lints/src/functions.rs @@ -52,7 +52,7 @@ declare_clippy_lint! { /// ```rust /// fn im_too_long() { /// println!(""); - /// // ... 100 more LoC + /// // ... 100 more LoC /// println!(""); /// } /// ``` diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index fbc29efdeb26b..a8d5c10d5daac 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -436,7 +436,11 @@ declare_clippy_lint! { /// vec.iter().filter(|x| **x == 0).map(|x| *x * 2); /// /// // Good - /// vec.iter().filter_map(|x| Some(*x * 2)); + /// vec.iter().filter_map(|x| if *x == 0 { + /// Some(*x * 2) + /// } else { + /// None + /// }); /// ``` pub FILTER_MAP, pedantic, From 9e89ba93fda29b4dc707cd14bd518b73e676d895 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Sun, 31 May 2020 15:09:58 +0200 Subject: [PATCH 46/72] Add doc for checking if type defines certain method --- doc/common_tools_writing_lints.md | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/doc/common_tools_writing_lints.md b/doc/common_tools_writing_lints.md index ed33b37c6bd1b..dbc434505947d 100644 --- a/doc/common_tools_writing_lints.md +++ b/doc/common_tools_writing_lints.md @@ -4,7 +4,9 @@ You may need following tooltips to catch up with common operations. - [Common tools for writing lints](#common-tools-for-writing-lints) - [Retrieving the type of an expression](#retrieving-the-type-of-an-expression) + - [Checking if an expression is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method) - [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait) + - [Checking if a type defines a method](#checking-if-a-type-defines-a-method) - [Dealing with macros](#dealing-with-macros) Useful Rustc dev guide links: @@ -49,6 +51,26 @@ Two noticeable items here: - `tables` is [`TypeckTables`][TypeckTables] and is created by type checking step, it includes useful information such as types of expressions, ways to resolve methods and so on. +# Checking if an expr is calling a specific method + +Starting with an `expr`, you can check whether it is calling a specific method `some_method`: + +```rust +impl LateLintPass<'_, '_> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr<'_>) { + if_chain! { + // Check our expr is calling a method + if let hir::ExprKind::MethodCall(path, _, _args) = &expr.kind; + // Check the name of this method is `some_method` + if path.ident.name == sym!(some_method); + then { + // ... + } + } + } +} +``` + # Checking if a type implements a specific trait There are two ways to do this, depending if the target trait is part of lang items. @@ -83,6 +105,32 @@ A list of defined paths for Clippy can be found in [paths.rs][paths] We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate. +# Checking if a type defines a specific method + +To check if our type defines a method called `some_method`: + +```rust +use crate::utils::{is_type_diagnostic_item, return_ty}; + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MyTypeImpl { + fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, impl_item: &'tcx ImplItem<'_>) { + if_chain! { + // Check if item is a method/function + if let ImplItemKind::Fn(ref signature, _) = impl_item.kind; + // Check the method is named `some_method` + if impl_item.ident.name == sym!(some_method); + // We can also check it has a parameter `self` + if signature.decl.implicit_self.has_implicit_self(); + // We can go further and even check if its return type is `String` + if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type)); + then { + // ... + } + } + } +} +``` + # Dealing with macros There are several helpers in Clippy's utils to deal with macros: From a44fa387efff44414c7baac249dcd148b93e2eb1 Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Mon, 1 Jun 2020 09:26:40 +0200 Subject: [PATCH 47/72] Update documentation on changelog updates --- CHANGELOG.md | 19 ++++++++++--------- doc/changelog_update.md | 9 +++++---- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcc9895dd901d..dd0905e9f3990 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,17 +31,17 @@ Current beta, release 2020-07-16 * Downgrade [`match_bool`] to pedantic [#5408](https://github.com/rust-lang/rust-clippy/pull/5408) * Downgrade [`match_wild_err_arm`] to pedantic and update help messages. [#5622](https://github.com/rust-lang/rust-clippy/pull/5622) * Downgrade [`useless_let_if_seq`] to nursery. [#5599](https://github.com/rust-lang/rust-clippy/pull/5599) -* Generalize [`option_and_then_some`] and rename to [`bind_instead_of_map`]. [#5529](https://github.com/rust-lang/rust-clippy/pull/5529) -* Rename [`identity_conversion`] to [`useless_conversion`]. [#5568](https://github.com/rust-lang/rust-clippy/pull/5568) -* Merge [`block_in_if_condition_expr`] and [`block_in_if_condition_stmt`] into [`blocks_in_if_conditions`]. +* Generalize `option_and_then_some` and rename to [`bind_instead_of_map`]. [#5529](https://github.com/rust-lang/rust-clippy/pull/5529) +* Rename `identity_conversion` to [`useless_conversion`]. [#5568](https://github.com/rust-lang/rust-clippy/pull/5568) +* Merge `block_in_if_condition_expr` and `block_in_if_condition_stmt` into [`blocks_in_if_conditions`]. [#5563](https://github.com/rust-lang/rust-clippy/pull/5563) -* Merge [`option_map_unwrap_or`], [`option_map_unwrap_or_else`] and [`result_map_unwrap_or_else`] into [`map_unwrap_or`]. +* Merge `option_map_unwrap_or`, `option_map_unwrap_or_else` and `result_map_unwrap_or_else` into [`map_unwrap_or`]. [#5563](https://github.com/rust-lang/rust-clippy/pull/5563) -* Merge [`option_unwrap_used`] and [`result_unwrap_used`] into [`unwrap_used`]. +* Merge `option_unwrap_used` and `result_unwrap_used` into [`unwrap_used`]. [#5563](https://github.com/rust-lang/rust-clippy/pull/5563) -* Merge [`option_expect_used`] and [`result_expect_used`] into [`expect_used`]. +* Merge `option_expect_used` and `result_expect_used` into [`expect_used`]. [#5563](https://github.com/rust-lang/rust-clippy/pull/5563) -* Merge [`for_loop_over_option`] and [`for_loop_over_result`] into [`for_loops_over_fallibles`]. +* Merge `for_loop_over_option` and `for_loop_over_result` into [`for_loops_over_fallibles`]. [#5563](https://github.com/rust-lang/rust-clippy/pull/5563) ### Enhancements @@ -61,10 +61,11 @@ Current beta, release 2020-07-16 * Don't trigger [`borrowed_box`] on mutable references. [#5491](https://github.com/rust-lang/rust-clippy/pull/5491) * Allow `1 << 0` in [`identity_op`]. [#5602](https://github.com/rust-lang/rust-clippy/pull/5602) * Allow `use super::*;` glob imports in [`wildcard_imports`]. [#5564](https://github.com/rust-lang/rust-clippy/pull/5564) -* Add ignores to the list of words of [`clippy::doc_markdown`]. [#5611](https://github.com/rust-lang/rust-clippy/pull/5611) +* Add ignores to the list of words of [`doc_markdown`]. [#5611](https://github.com/rust-lang/rust-clippy/pull/5611) * Skip dev and build deps in [`multiple_crate_versions`]. [#5636](https://github.com/rust-lang/rust-clippy/pull/5636) * Honor `allow` attribute on arguments in [`ptr_arg`]. [#5647](https://github.com/rust-lang/rust-clippy/pull/5647) -* Honor lint level attributes for [`redundant_field_names`] and [`non_expressive_names`]. [#5651](https://github.com/rust-lang/rust-clippy/pull/5651) +* Honor lint level attributes for [`redundant_field_names`], [`just_underscores_and_digits`], [`many_single_char_names`] +and [`similar_names`]. [#5651](https://github.com/rust-lang/rust-clippy/pull/5651) * Ignore calls to `len` in [`or_fun_call`]. [#4429](https://github.com/rust-lang/rust-clippy/pull/4429) ### Suggestion Improvements diff --git a/doc/changelog_update.md b/doc/changelog_update.md index 0b80cce6d23ea..edba3b4741cb9 100644 --- a/doc/changelog_update.md +++ b/doc/changelog_update.md @@ -32,8 +32,9 @@ bullet points might be helpful: need to select the Rust release tag from the dropdown and then check the commit of the Clippy directory: - ![Explanation of how to find the commit hash](https://user-images.githubusercontent.com/2042399/62846160-1f8b0480-bcce-11e9-9da8-7964ca034e7a.png) - +To find the commit hash, click on "History" of the relevant branch in github +and search for the latest "Merge commit '' into " commit. +The part is then the most recent commit in the clippy repo. ### 2. Fetching the PRs between those commits @@ -74,5 +75,5 @@ relevant commit ranges. [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 -[rust_beta_tools]: https://github.com/rust-lang/rust/tree/beta/src/tools +[rust_master_tools]: https://github.com/rust-lang/rust/tree/master/src/tools/clippy +[rust_beta_tools]: https://github.com/rust-lang/rust/tree/beta/src/tools/clippy From ea06c72fddd6d3b12a5a9f955f80faadac9918e0 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Sat, 23 May 2020 11:12:06 -0700 Subject: [PATCH 48/72] Call `skip_binder` or `no_bound_vars` before `self_ty` --- clippy_lints/src/future_not_send.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/future_not_send.rs b/clippy_lints/src/future_not_send.rs index 0a02aa7533c17..17dd3cd5493e7 100644 --- a/clippy_lints/src/future_not_send.rs +++ b/clippy_lints/src/future_not_send.rs @@ -95,7 +95,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FutureNotSend { let trait_ref = trait_pred.to_poly_trait_ref(); db.note(&*format!( "`{}` doesn't implement `{}`", - trait_ref.self_ty(), + trait_ref.skip_binder().self_ty(), trait_ref.print_only_trait_path(), )); } From fbf0b84b32aab798258838d5e932dbc56c4a1813 Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Tue, 2 Jun 2020 21:42:33 +0700 Subject: [PATCH 49/72] Make use of slice pattern --- clippy_lints/src/utils/mod.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index 6c1488664bf00..7e07e7751e34a 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -165,8 +165,8 @@ pub fn match_trait_method(cx: &LateContext<'_, '_>, expr: &Expr<'_>, path: &[&st /// Checks if an expression references a variable of the given name. pub fn match_var(expr: &Expr<'_>, var: Name) -> bool { if let ExprKind::Path(QPath::Resolved(None, ref path)) = expr.kind { - if path.segments.len() == 1 && path.segments[0].ident.name == var { - return true; + if let [p] = path.segments { + return p.ident.name == var; } } false @@ -181,8 +181,7 @@ pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> { pub fn single_segment_path<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx PathSegment<'tcx>> { match *path { - QPath::Resolved(_, ref path) if path.segments.len() == 1 => Some(&path.segments[0]), - QPath::Resolved(..) => None, + QPath::Resolved(_, ref path) => path.segments.get(0), QPath::TypeRelative(_, ref seg) => Some(seg), } } @@ -201,9 +200,12 @@ pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool { QPath::Resolved(_, ref path) => match_path(path, segments), QPath::TypeRelative(ref ty, ref segment) => match ty.kind { TyKind::Path(ref inner_path) => { - !segments.is_empty() - && match_qpath(inner_path, &segments[..(segments.len() - 1)]) - && segment.ident.name.as_str() == segments[segments.len() - 1] + if let [prefix @ .., end] = segments { + if match_qpath(inner_path, prefix) { + return segment.ident.name.as_str() == *end; + } + } + false }, _ => false, }, From 2b79413491ed02b7bdc8236f43811aedde0fe325 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Tue, 2 Jun 2020 20:19:49 +0300 Subject: [PATCH 50/72] Rename the crates in source code --- clippy_lints/src/write.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index dfa6223f1b9dd..006503b546edf 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -352,7 +352,7 @@ impl Write { tts: &TokenStream, is_write: bool, ) -> (Option, Option) { - use fmt_macros::{ + use rustc_parse_format::{ AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, ParseMode, Parser, Piece, }; From dcd480678236d3829f3af3ae9c3339e3e10aebad Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Tue, 2 Jun 2020 20:45:57 +0200 Subject: [PATCH 51/72] Apply suggestions from PR review --- CHANGELOG.md | 6 +++--- doc/changelog_update.md | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd0905e9f3990..a8ec5fa67b62e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,7 @@ Current beta, release 2020-07-16 ### Enhancements -* Avoid running cargo/internal lints when not enabled. [#5505](https://github.com/rust-lang/rust-clippy/pull/5505) +* Avoid running cargo lints when not enabled to improve performance. [#5505](https://github.com/rust-lang/rust-clippy/pull/5505) * Extend [`useless_conversion`] with `TryFrom` and `TryInto`. [#5631](https://github.com/rust-lang/rust-clippy/pull/5631) * Lint also in type parameters and where clauses in [`unused_unit`]. [#5592](https://github.com/rust-lang/rust-clippy/pull/5592) * Do not suggest deriving `Default` in [`new_without_default`]. [#5616](https://github.com/rust-lang/rust-clippy/pull/5616) @@ -61,7 +61,7 @@ Current beta, release 2020-07-16 * Don't trigger [`borrowed_box`] on mutable references. [#5491](https://github.com/rust-lang/rust-clippy/pull/5491) * Allow `1 << 0` in [`identity_op`]. [#5602](https://github.com/rust-lang/rust-clippy/pull/5602) * Allow `use super::*;` glob imports in [`wildcard_imports`]. [#5564](https://github.com/rust-lang/rust-clippy/pull/5564) -* Add ignores to the list of words of [`doc_markdown`]. [#5611](https://github.com/rust-lang/rust-clippy/pull/5611) +* Whitelist more words in [`doc_markdown`]. [#5611](https://github.com/rust-lang/rust-clippy/pull/5611) * Skip dev and build deps in [`multiple_crate_versions`]. [#5636](https://github.com/rust-lang/rust-clippy/pull/5636) * Honor `allow` attribute on arguments in [`ptr_arg`]. [#5647](https://github.com/rust-lang/rust-clippy/pull/5647) * Honor lint level attributes for [`redundant_field_names`], [`just_underscores_and_digits`], [`many_single_char_names`] @@ -78,7 +78,7 @@ and [`similar_names`]. [#5651](https://github.com/rust-lang/rust-clippy/pull/565 ### ICE Fixes * Fix ICE caused in unwrap module. [#5590](https://github.com/rust-lang/rust-clippy/pull/5590) -* Fix crash on rustc test issue-69020-assoc-const-arith-overflow.rs [#5499](https://github.com/rust-lang/rust-clippy/pull/5499) +* Fix ICE on rustc test issue-69020-assoc-const-arith-overflow.rs [#5499](https://github.com/rust-lang/rust-clippy/pull/5499) ### Documentation diff --git a/doc/changelog_update.md b/doc/changelog_update.md index edba3b4741cb9..1ec07b9dbbe93 100644 --- a/doc/changelog_update.md +++ b/doc/changelog_update.md @@ -32,9 +32,10 @@ bullet points might be helpful: need to select the Rust release tag from the dropdown and then check the commit of the Clippy directory: -To find the commit hash, click on "History" of the relevant branch in github -and search for the latest "Merge commit '' into " commit. -The part is then the most recent commit in the clippy repo. +To find the commit hash, issue the following command when in a `rust-lang/rust` checkout: +``` +git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g" +``` ### 2. Fetching the PRs between those commits From bd3fc11416bf03b0b8c97ea8dc27ed97a729e9d0 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Tue, 2 Jun 2020 22:46:42 +0300 Subject: [PATCH 52/72] Update fulldeps tests and clippy --- clippy_lints/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 902f3d56c1e4f..b7d928d249f96 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -16,8 +16,6 @@ // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) #[allow(unused_extern_crates)] -extern crate fmt_macros; -#[allow(unused_extern_crates)] extern crate rustc_ast; #[allow(unused_extern_crates)] extern crate rustc_ast_pretty; @@ -48,6 +46,8 @@ extern crate rustc_mir; #[allow(unused_extern_crates)] extern crate rustc_parse; #[allow(unused_extern_crates)] +extern crate rustc_parse_format; +#[allow(unused_extern_crates)] extern crate rustc_session; #[allow(unused_extern_crates)] extern crate rustc_span; From b39fd5f62f80cb9bb47ac44d7100f694e0c7301c Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Wed, 3 Jun 2020 09:04:24 +0700 Subject: [PATCH 53/72] Fix false negative of checked_conversion lint --- clippy_lints/src/checked_conversions.rs | 78 ++++++++--------- tests/ui/checked_conversions.fixed | 106 +++++++++--------------- tests/ui/checked_conversions.rs | 106 +++++++++--------------- tests/ui/checked_conversions.stderr | 98 ++++++++++++++++------ tests/ui/checked_conversions.stdout | 0 5 files changed, 188 insertions(+), 200 deletions(-) delete mode 100644 tests/ui/checked_conversions.stdout diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index d9776dd50a836..e845ef99c7cc0 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -58,24 +58,18 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CheckedConversions { } }; - if_chain! { - if let Some(cv) = result; - if let Some(to_type) = cv.to_type; - - then { + if let Some(cv) = result { + if let Some(to_type) = cv.to_type { let mut applicability = Applicability::MachineApplicable; - let snippet = snippet_with_applicability(cx, cv.expr_to_cast.span, "_", &mut - applicability); + let snippet = snippet_with_applicability(cx, cv.expr_to_cast.span, "_", &mut applicability); span_lint_and_sugg( cx, CHECKED_CONVERSIONS, item.span, "Checked cast can be simplified.", "try", - format!("{}::try_from({}).is_ok()", - to_type, - snippet), - applicability + format!("{}::try_from({}).is_ok()", to_type, snippet), + applicability, ); } } @@ -184,7 +178,7 @@ fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option> { if_chain! { if let ExprKind::Binary(ref op, ref left, ref right) = &expr.kind; if let Some((candidate, check)) = normalize_le_ge(op, left, right); - if let Some((from, to)) = get_types_from_cast(check, MAX_VALUE, INTS); + if let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX"); then { Conversion::try_new(candidate, from, to) @@ -224,7 +218,7 @@ fn check_lower_bound_zero<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> O /// Check for `expr >= (to_type::MIN as from_type)` fn check_lower_bound_min<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Option> { - if let Some((from, to)) = get_types_from_cast(check, MIN_VALUE, SINTS) { + if let Some((from, to)) = get_types_from_cast(check, SINTS, "min_value", "MIN") { Conversion::try_new(candidate, from, to) } else { None @@ -232,10 +226,16 @@ fn check_lower_bound_min<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Op } /// Tries to extract the from- and to-type from a cast expression -fn get_types_from_cast<'a>(expr: &'a Expr<'_>, func: &'a str, types: &'a [&str]) -> Option<(&'a str, &'a str)> { - // `to_type::maxmin_value() as from_type` +fn get_types_from_cast<'a>( + expr: &'a Expr<'_>, + types: &'a [&str], + func: &'a str, + assoc_const: &'a str, +) -> Option<(&'a str, &'a str)> { + // `to_type::max_value() as from_type` + // or `to_type::MAX as from_type` let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! { - // to_type::maxmin_value(), from_type + // to_type::max_value(), from_type if let ExprKind::Cast(ref limit, ref from_type) = &expr.kind; if let TyKind::Path(ref from_type_path) = &from_type.kind; if let Some(from_sym) = int_ty_to_sym(from_type_path); @@ -247,17 +247,17 @@ fn get_types_from_cast<'a>(expr: &'a Expr<'_>, func: &'a str, types: &'a [&str]) } }; - // `from_type::from(to_type::maxmin_value())` + // `from_type::from(to_type::max_value())` let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| { if_chain! { - // `from_type::from, to_type::maxmin_value()` + // `from_type::from, to_type::max_value()` if let ExprKind::Call(ref from_func, ref args) = &expr.kind; - // `to_type::maxmin_value()` + // `to_type::max_value()` if args.len() == 1; if let limit = &args[0]; // `from_type::from` if let ExprKind::Path(ref path) = &from_func.kind; - if let Some(from_sym) = get_implementing_type(path, INTS, FROM); + if let Some(from_sym) = get_implementing_type(path, INTS, "from"); then { Some((limit, from_sym)) @@ -268,22 +268,26 @@ fn get_types_from_cast<'a>(expr: &'a Expr<'_>, func: &'a str, types: &'a [&str]) }); if let Some((limit, from_type)) = limit_from { - if_chain! { - if let ExprKind::Call(ref fun_name, _) = &limit.kind; - // `to_type, maxmin_value` - if let ExprKind::Path(ref path) = &fun_name.kind; - // `to_type` - if let Some(to_type) = get_implementing_type(path, types, func); - - then { - Some((from_type, to_type)) - } else { - None - } + match limit.kind { + // `from_type::from(_)` + ExprKind::Call(path, _) => { + if let ExprKind::Path(ref path) = path.kind { + // `to_type` + if let Some(to_type) = get_implementing_type(path, types, func) { + return Some((from_type, to_type)); + } + } + }, + // `to_type::MAX` + ExprKind::Path(ref path) => { + if let Some(to_type) = get_implementing_type(path, types, assoc_const) { + return Some((from_type, to_type)); + } + }, + _ => {}, } - } else { - None - } + }; + None } /// Gets the type which implements the called function @@ -336,10 +340,6 @@ fn normalize_le_ge<'a>(op: &BinOp, left: &'a Expr<'a>, right: &'a Expr<'a>) -> O } // Constants -const FROM: &str = "from"; -const MAX_VALUE: &str = "max_value"; -const MIN_VALUE: &str = "min_value"; - const UINTS: &[&str] = &["u8", "u16", "u32", "u64", "usize"]; const SINTS: &[&str] = &["i8", "i16", "i32", "i64", "isize"]; const INTS: &[&str] = &["u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize"]; diff --git a/tests/ui/checked_conversions.fixed b/tests/ui/checked_conversions.fixed index 7febd6f376135..12290db3dcf52 100644 --- a/tests/ui/checked_conversions.fixed +++ b/tests/ui/checked_conversions.fixed @@ -1,106 +1,76 @@ // run-rustfix +#![allow( + clippy::cast_lossless, + // Int::max_value will be deprecated in the future + deprecated, +)] #![warn(clippy::checked_conversions)] -#![allow(clippy::cast_lossless)] -#![allow(dead_code)] + use std::convert::TryFrom; // Positive tests // Signed to unsigned -fn i64_to_u32(value: i64) -> Option { - if u32::try_from(value).is_ok() { - Some(value as u32) - } else { - None - } +pub fn i64_to_u32(value: i64) { + let _ = u32::try_from(value).is_ok(); + let _ = u32::try_from(value).is_ok(); } -fn i64_to_u16(value: i64) -> Option { - if u16::try_from(value).is_ok() { - Some(value as u16) - } else { - None - } +pub fn i64_to_u16(value: i64) { + let _ = u16::try_from(value).is_ok(); + let _ = u16::try_from(value).is_ok(); } -fn isize_to_u8(value: isize) -> Option { - if u8::try_from(value).is_ok() { - Some(value as u8) - } else { - None - } +pub fn isize_to_u8(value: isize) { + let _ = u8::try_from(value).is_ok(); + let _ = u8::try_from(value).is_ok(); } // Signed to signed -fn i64_to_i32(value: i64) -> Option { - if i32::try_from(value).is_ok() { - Some(value as i32) - } else { - None - } +pub fn i64_to_i32(value: i64) { + let _ = i32::try_from(value).is_ok(); + let _ = i32::try_from(value).is_ok(); } -fn i64_to_i16(value: i64) -> Option { - if i16::try_from(value).is_ok() { - Some(value as i16) - } else { - None - } +pub fn i64_to_i16(value: i64) { + let _ = i16::try_from(value).is_ok(); + let _ = i16::try_from(value).is_ok(); } // Unsigned to X -fn u32_to_i32(value: u32) -> Option { - if i32::try_from(value).is_ok() { - Some(value as i32) - } else { - None - } +pub fn u32_to_i32(value: u32) { + let _ = i32::try_from(value).is_ok(); + let _ = i32::try_from(value).is_ok(); } -fn usize_to_isize(value: usize) -> isize { - if isize::try_from(value).is_ok() && value as i32 == 5 { - 5 - } else { - 1 - } +pub fn usize_to_isize(value: usize) { + let _ = isize::try_from(value).is_ok() && value as i32 == 5; + let _ = isize::try_from(value).is_ok() && value as i32 == 5; } -fn u32_to_u16(value: u32) -> isize { - if u16::try_from(value).is_ok() && value as i32 == 5 { - 5 - } else { - 1 - } +pub fn u32_to_u16(value: u32) { + let _ = u16::try_from(value).is_ok() && value as i32 == 5; + let _ = u16::try_from(value).is_ok() && value as i32 == 5; } // Negative tests -fn no_i64_to_i32(value: i64) -> Option { - if value <= (i32::max_value() as i64) && value >= 0 { - Some(value as i32) - } else { - None - } +pub fn no_i64_to_i32(value: i64) { + let _ = value <= (i32::max_value() as i64) && value >= 0; + let _ = value <= (i32::MAX as i64) && value >= 0; } -fn no_isize_to_u8(value: isize) -> Option { - if value <= (u8::max_value() as isize) && value >= (u8::min_value() as isize) { - Some(value as u8) - } else { - None - } +pub fn no_isize_to_u8(value: isize) { + let _ = value <= (u8::max_value() as isize) && value >= (u8::min_value() as isize); + let _ = value <= (u8::MAX as isize) && value >= (u8::MIN as isize); } -fn i8_to_u8(value: i8) -> Option { - if value >= 0 { - Some(value as u8) - } else { - None - } +pub fn i8_to_u8(value: i8) { + let _ = value >= 0; } fn main() {} diff --git a/tests/ui/checked_conversions.rs b/tests/ui/checked_conversions.rs index a643354e2438f..895a301e82126 100644 --- a/tests/ui/checked_conversions.rs +++ b/tests/ui/checked_conversions.rs @@ -1,106 +1,76 @@ // run-rustfix +#![allow( + clippy::cast_lossless, + // Int::max_value will be deprecated in the future + deprecated, +)] #![warn(clippy::checked_conversions)] -#![allow(clippy::cast_lossless)] -#![allow(dead_code)] + use std::convert::TryFrom; // Positive tests // Signed to unsigned -fn i64_to_u32(value: i64) -> Option { - if value <= (u32::max_value() as i64) && value >= 0 { - Some(value as u32) - } else { - None - } +pub fn i64_to_u32(value: i64) { + let _ = value <= (u32::max_value() as i64) && value >= 0; + let _ = value <= (u32::MAX as i64) && value >= 0; } -fn i64_to_u16(value: i64) -> Option { - if value <= i64::from(u16::max_value()) && value >= 0 { - Some(value as u16) - } else { - None - } +pub fn i64_to_u16(value: i64) { + let _ = value <= i64::from(u16::max_value()) && value >= 0; + let _ = value <= i64::from(u16::MAX) && value >= 0; } -fn isize_to_u8(value: isize) -> Option { - if value <= (u8::max_value() as isize) && value >= 0 { - Some(value as u8) - } else { - None - } +pub fn isize_to_u8(value: isize) { + let _ = value <= (u8::max_value() as isize) && value >= 0; + let _ = value <= (u8::MAX as isize) && value >= 0; } // Signed to signed -fn i64_to_i32(value: i64) -> Option { - if value <= (i32::max_value() as i64) && value >= (i32::min_value() as i64) { - Some(value as i32) - } else { - None - } +pub fn i64_to_i32(value: i64) { + let _ = value <= (i32::max_value() as i64) && value >= (i32::min_value() as i64); + let _ = value <= (i32::MAX as i64) && value >= (i32::MIN as i64); } -fn i64_to_i16(value: i64) -> Option { - if value <= i64::from(i16::max_value()) && value >= i64::from(i16::min_value()) { - Some(value as i16) - } else { - None - } +pub fn i64_to_i16(value: i64) { + let _ = value <= i64::from(i16::max_value()) && value >= i64::from(i16::min_value()); + let _ = value <= i64::from(i16::MAX) && value >= i64::from(i16::MIN); } // Unsigned to X -fn u32_to_i32(value: u32) -> Option { - if value <= i32::max_value() as u32 { - Some(value as i32) - } else { - None - } +pub fn u32_to_i32(value: u32) { + let _ = value <= i32::max_value() as u32; + let _ = value <= i32::MAX as u32; } -fn usize_to_isize(value: usize) -> isize { - if value <= isize::max_value() as usize && value as i32 == 5 { - 5 - } else { - 1 - } +pub fn usize_to_isize(value: usize) { + let _ = value <= isize::max_value() as usize && value as i32 == 5; + let _ = value <= isize::MAX as usize && value as i32 == 5; } -fn u32_to_u16(value: u32) -> isize { - if value <= u16::max_value() as u32 && value as i32 == 5 { - 5 - } else { - 1 - } +pub fn u32_to_u16(value: u32) { + let _ = value <= u16::max_value() as u32 && value as i32 == 5; + let _ = value <= u16::MAX as u32 && value as i32 == 5; } // Negative tests -fn no_i64_to_i32(value: i64) -> Option { - if value <= (i32::max_value() as i64) && value >= 0 { - Some(value as i32) - } else { - None - } +pub fn no_i64_to_i32(value: i64) { + let _ = value <= (i32::max_value() as i64) && value >= 0; + let _ = value <= (i32::MAX as i64) && value >= 0; } -fn no_isize_to_u8(value: isize) -> Option { - if value <= (u8::max_value() as isize) && value >= (u8::min_value() as isize) { - Some(value as u8) - } else { - None - } +pub fn no_isize_to_u8(value: isize) { + let _ = value <= (u8::max_value() as isize) && value >= (u8::min_value() as isize); + let _ = value <= (u8::MAX as isize) && value >= (u8::MIN as isize); } -fn i8_to_u8(value: i8) -> Option { - if value >= 0 { - Some(value as u8) - } else { - None - } +pub fn i8_to_u8(value: i8) { + let _ = value >= 0; } fn main() {} diff --git a/tests/ui/checked_conversions.stderr b/tests/ui/checked_conversions.stderr index f678f009621f8..648ba3ccd01db 100644 --- a/tests/ui/checked_conversions.stderr +++ b/tests/ui/checked_conversions.stderr @@ -1,52 +1,100 @@ error: Checked cast can be simplified. - --> $DIR/checked_conversions.rs:13:8 + --> $DIR/checked_conversions.rs:17:13 | -LL | if value <= (u32::max_value() as i64) && value >= 0 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()` +LL | let _ = value <= (u32::max_value() as i64) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()` | = note: `-D clippy::checked-conversions` implied by `-D warnings` error: Checked cast can be simplified. - --> $DIR/checked_conversions.rs:21:8 + --> $DIR/checked_conversions.rs:18:13 | -LL | if value <= i64::from(u16::max_value()) && value >= 0 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` +LL | let _ = value <= (u32::MAX as i64) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u32::try_from(value).is_ok()` error: Checked cast can be simplified. - --> $DIR/checked_conversions.rs:29:8 + --> $DIR/checked_conversions.rs:22:13 | -LL | if value <= (u8::max_value() as isize) && value >= 0 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u8::try_from(value).is_ok()` +LL | let _ = value <= i64::from(u16::max_value()) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` error: Checked cast can be simplified. - --> $DIR/checked_conversions.rs:39:8 + --> $DIR/checked_conversions.rs:23:13 | -LL | if value <= (i32::max_value() as i64) && value >= (i32::min_value() as i64) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` +LL | let _ = value <= i64::from(u16::MAX) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` error: Checked cast can be simplified. - --> $DIR/checked_conversions.rs:47:8 + --> $DIR/checked_conversions.rs:27:13 | -LL | if value <= i64::from(i16::max_value()) && value >= i64::from(i16::min_value()) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i16::try_from(value).is_ok()` +LL | let _ = value <= (u8::max_value() as isize) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u8::try_from(value).is_ok()` error: Checked cast can be simplified. - --> $DIR/checked_conversions.rs:57:8 + --> $DIR/checked_conversions.rs:28:13 | -LL | if value <= i32::max_value() as u32 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` +LL | let _ = value <= (u8::MAX as isize) && value >= 0; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u8::try_from(value).is_ok()` error: Checked cast can be simplified. - --> $DIR/checked_conversions.rs:65:8 + --> $DIR/checked_conversions.rs:34:13 | -LL | if value <= isize::max_value() as usize && value as i32 == 5 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `isize::try_from(value).is_ok()` +LL | let _ = value <= (i32::max_value() as i64) && value >= (i32::min_value() as i64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` error: Checked cast can be simplified. - --> $DIR/checked_conversions.rs:73:8 + --> $DIR/checked_conversions.rs:35:13 | -LL | if value <= u16::max_value() as u32 && value as i32 == 5 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` +LL | let _ = value <= (i32::MAX as i64) && value >= (i32::MIN as i64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` -error: aborting due to 8 previous errors +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:39:13 + | +LL | let _ = value <= i64::from(i16::max_value()) && value >= i64::from(i16::min_value()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i16::try_from(value).is_ok()` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:40:13 + | +LL | let _ = value <= i64::from(i16::MAX) && value >= i64::from(i16::MIN); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i16::try_from(value).is_ok()` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:46:13 + | +LL | let _ = value <= i32::max_value() as u32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:47:13 + | +LL | let _ = value <= i32::MAX as u32; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:51:13 + | +LL | let _ = value <= isize::max_value() as usize && value as i32 == 5; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `isize::try_from(value).is_ok()` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:52:13 + | +LL | let _ = value <= isize::MAX as usize && value as i32 == 5; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `isize::try_from(value).is_ok()` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:56:13 + | +LL | let _ = value <= u16::max_value() as u32 && value as i32 == 5; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` + +error: Checked cast can be simplified. + --> $DIR/checked_conversions.rs:57:13 + | +LL | let _ = value <= u16::MAX as u32 && value as i32 == 5; + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::try_from(value).is_ok()` + +error: aborting due to 16 previous errors diff --git a/tests/ui/checked_conversions.stdout b/tests/ui/checked_conversions.stdout deleted file mode 100644 index e69de29bb2d1d..0000000000000 From 0c470b3339532e063d3cedc517b7952779376d78 Mon Sep 17 00:00:00 2001 From: Mark Rousskov Date: Wed, 3 Jun 2020 15:15:53 -0400 Subject: [PATCH 54/72] Bump to 1.46 --- clippy_lints/src/write.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index dfa6223f1b9dd..37fbc2bad468f 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -279,13 +279,13 @@ impl EarlyLintPass for Write { if let (Some(fmt_str), expr) = self.check_tts(cx, &mac.args.inner_tokens(), true) { if fmt_str.symbol == Symbol::intern("") { let mut applicability = Applicability::MachineApplicable; - let suggestion = expr.map_or_else( - move || { + let suggestion = match expr { + Some(expr) => snippet_with_applicability(cx, expr.span, "v", &mut applicability), + None => { applicability = Applicability::HasPlaceholders; Cow::Borrowed("v") }, - move |expr| snippet_with_applicability(cx, expr.span, "v", &mut applicability), - ); + }; span_lint_and_sugg( cx, From 7654125d27d95d5c329e554115b510efc1ab1e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Kr=C3=BCger?= Date: Thu, 4 Jun 2020 03:34:22 +0200 Subject: [PATCH 55/72] match_wildcard_for_single_variants: remove empty line at start of lint example. See https://rust-lang.github.io/rust-clippy/master/index.html#match_wildcard_for_single_variants changelog: none --- clippy_lints/src/matches.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 146212cb2c7a1..6d7af45a47224 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -270,7 +270,6 @@ declare_clippy_lint! { /// ```rust /// # enum Foo { A, B, C } /// # let x = Foo::B; - /// /// // Bad /// match x { /// Foo::A => {}, From 9ef15ae7c86a08c96a368f6728b25e1c55f6e10b Mon Sep 17 00:00:00 2001 From: flip1995 Date: Fri, 5 Jun 2020 13:56:07 +0200 Subject: [PATCH 56/72] Reorder sections of release documentation Before tagging a commit the beta branch has to be remerged --- doc/release.md | 86 +++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/doc/release.md b/doc/release.md index 9d69fa8a7f69a..f0a7fe52149c6 100644 --- a/doc/release.md +++ b/doc/release.md @@ -7,11 +7,11 @@ Clippy is released together with stable Rust releases. The dates for these releases can be found at the [Rust Forge]. This document explains the necessary steps to create a Clippy release. -1. [Find the Clippy commit](#find-the-clippy-commit) -2. [Tag the stable commit](#tag-the-stable-commit) -3. [Update `CHANGELOG.md`](#update-changelogmd) -4. [Remerge the `beta` branch](#remerge-the-beta-branch) -5. [Update the `beta` branch](#update-the-beta-branch) +1. [Remerge the `beta` branch](#remerge-the-beta-branch) +2. [Update the `beta` branch](#update-the-beta-branch) +3. [Find the Clippy commit](#find-the-clippy-commit) +4. [Tag the stable commit](#tag-the-stable-commit) +5. [Update `CHANGELOG.md`](#update-changelogmd) _NOTE: This document is for stable Rust releases, not for point releases. For point releases, step 1. and 2. should be enough._ @@ -19,43 +19,6 @@ point releases, step 1. and 2. should be enough._ [Rust Forge]: https://forge.rust-lang.org/ -## Find the Clippy commit - -The first step is to tag the Clippy commit, that is included in the stable Rust -release. This commit can be found in the Rust repository. - -```bash -# Assuming the current directory corresponds to the Rust repository -$ git fetch upstream # `upstream` is the `rust-lang/rust` remote -$ git checkout 1.XX.0 # XX should be exchanged with the corresponding version -$ git submodule update -$ SHA=$(git submodule status src/tools/clippy | awk '{print $1}') -``` - - -## Tag the stable commit - -After finding the Clippy commit, it can be tagged with the release number. - -```bash -# Assuming the current directory corresponds to the Clippy repository -$ git checkout $SHA -$ git tag rust-1.XX.0 # XX should be exchanged with the corresponding version -$ git push upstream master --tags # `upstream` is the `rust-lang/rust-clippy` remote -``` - -After this, the release should be available on the Clippy [release page]. - -[release page]: https://github.com/rust-lang/rust-clippy/releases - - -## Update `CHANGELOG.md` - -For this see the document on [how to update the changelog]. - -[how to update the changelog]: https://github.com/rust-lang/rust-clippy/blob/master/doc/changelog_update.md - - ## Remerge the `beta` branch This step is only necessary, if since the last release something was backported @@ -76,7 +39,7 @@ If this command outputs `master`, this step is **not** necessary. ```bash # Assuming `HEAD` is the current `master` branch of rust-lang/rust-clippy $ git checkout -b backport_remerge -$ git merge beta +$ git merge upstream/beta $ git diff # This diff has to be empty, otherwise something with the remerge failed $ git push origin backport_remerge # This can be pushed to your fork ``` @@ -109,3 +72,40 @@ $ git checkout beta $ git rebase $BETA_SHA $ git push upstream beta ``` + + +## Find the Clippy commit + +The first step is to tag the Clippy commit, that is included in the stable Rust +release. This commit can be found in the Rust repository. + +```bash +# Assuming the current directory corresponds to the Rust repository +$ git fetch upstream # `upstream` is the `rust-lang/rust` remote +$ git checkout 1.XX.0 # XX should be exchanged with the corresponding version +$ git submodule update +$ SHA=$(git submodule status src/tools/clippy | awk '{print $1}') +``` + + +## Tag the stable commit + +After finding the Clippy commit, it can be tagged with the release number. + +```bash +# Assuming the current directory corresponds to the Clippy repository +$ git checkout $SHA +$ git tag rust-1.XX.0 # XX should be exchanged with the corresponding version +$ git push upstream master --tags # `upstream` is the `rust-lang/rust-clippy` remote +``` + +After this, the release should be available on the Clippy [release page]. + +[release page]: https://github.com/rust-lang/rust-clippy/releases + + +## Update `CHANGELOG.md` + +For this see the document on [how to update the changelog]. + +[how to update the changelog]: https://github.com/rust-lang/rust-clippy/blob/master/doc/changelog_update.md From 6b9e2e90bf7688bfbcf357fda6e0ef74e11ba1ff Mon Sep 17 00:00:00 2001 From: flip1995 Date: Fri, 5 Jun 2020 14:47:12 +0200 Subject: [PATCH 57/72] Replace all remaining occurrences of submodule with subtree --- doc/changelog_update.md | 2 +- doc/release.md | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/changelog_update.md b/doc/changelog_update.md index 0b80cce6d23ea..6657ab4187b8c 100644 --- a/doc/changelog_update.md +++ b/doc/changelog_update.md @@ -18,7 +18,7 @@ been very rare that Clippy changes were included in a patch release. ### 1. Finding the relevant Clippy commits -Each Rust release ships with its own version of Clippy. The Clippy submodule can +Each Rust release ships with its own version of Clippy. The Clippy subtree can be found in the `tools` directory of the Rust repository. Depending on the current time and what exactly you want to update, the following diff --git a/doc/release.md b/doc/release.md index f0a7fe52149c6..391952ea6b14f 100644 --- a/doc/release.md +++ b/doc/release.md @@ -59,8 +59,7 @@ determined. ```bash # Assuming the current directory corresponds to the Rust repository $ git checkout beta -$ git submodule update -$ BETA_SHA=$(git submodule status src/tools/clippy | awk '{print $1}') +$ BETA_SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g") ``` After finding the Clippy commit, the `beta` branch in the Clippy repository can @@ -83,8 +82,7 @@ release. This commit can be found in the Rust repository. # Assuming the current directory corresponds to the Rust repository $ git fetch upstream # `upstream` is the `rust-lang/rust` remote $ git checkout 1.XX.0 # XX should be exchanged with the corresponding version -$ git submodule update -$ SHA=$(git submodule status src/tools/clippy | awk '{print $1}') +$ SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g") ``` From 413713c8848ec94d5f1709a41537c300da398806 Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Fri, 5 Jun 2020 22:28:58 +0200 Subject: [PATCH 58/72] Add error info when cargo metadata fails to run --- clippy_lints/src/cargo_common_metadata.rs | 13 ++----------- clippy_lints/src/multiple_crate_versions.rs | 10 ++-------- clippy_lints/src/utils/mod.rs | 18 ++++++++++++++++++ clippy_lints/src/wildcard_dependencies.rs | 7 +------ 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/clippy_lints/src/cargo_common_metadata.rs b/clippy_lints/src/cargo_common_metadata.rs index 16b46423c8f01..c40a387d29797 100644 --- a/clippy_lints/src/cargo_common_metadata.rs +++ b/clippy_lints/src/cargo_common_metadata.rs @@ -36,13 +36,9 @@ declare_clippy_lint! { "common metadata is defined in `Cargo.toml`" } -fn warning(cx: &LateContext<'_, '_>, message: &str) { - span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, message); -} - fn missing_warning(cx: &LateContext<'_, '_>, package: &cargo_metadata::Package, field: &str) { let message = format!("package `{}` is missing `{}` metadata", package.name, field); - warning(cx, &message); + span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, &message); } fn is_empty_str(value: &Option) -> bool { @@ -66,12 +62,7 @@ impl LateLintPass<'_, '_> for CargoCommonMetadata { return; } - let metadata = if let Ok(metadata) = cargo_metadata::MetadataCommand::new().no_deps().exec() { - metadata - } else { - warning(cx, "could not read cargo metadata"); - return; - }; + let metadata = unwrap_cargo_metadata!(cx, CARGO_COMMON_METADATA, false); for package in metadata.packages { if is_empty_vec(&package.authors) { diff --git a/clippy_lints/src/multiple_crate_versions.rs b/clippy_lints/src/multiple_crate_versions.rs index b6770804e1801..6c42014b4c8a1 100644 --- a/clippy_lints/src/multiple_crate_versions.rs +++ b/clippy_lints/src/multiple_crate_versions.rs @@ -7,7 +7,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::DUMMY_SP; -use cargo_metadata::{DependencyKind, MetadataCommand, Node, Package, PackageId}; +use cargo_metadata::{DependencyKind, Node, Package, PackageId}; use if_chain::if_chain; use itertools::Itertools; @@ -42,13 +42,7 @@ impl LateLintPass<'_, '_> for MultipleCrateVersions { return; } - let metadata = if let Ok(metadata) = MetadataCommand::new().exec() { - metadata - } else { - span_lint(cx, MULTIPLE_CRATE_VERSIONS, DUMMY_SP, "could not read cargo metadata"); - return; - }; - + let metadata = unwrap_cargo_metadata!(cx, MULTIPLE_CRATE_VERSIONS, true); let local_name = cx.tcx.crate_name(LOCAL_CRATE).as_str(); let mut packages = metadata.packages; packages.sort_by(|a, b| a.name.cmp(&b.name)); diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index 7e07e7751e34a..2cdb6486fcb42 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -1405,6 +1405,24 @@ pub fn run_lints(cx: &LateContext<'_, '_>, lints: &[&'static Lint], id: HirId) - }) } +#[macro_export] +macro_rules! unwrap_cargo_metadata { + ($cx: ident, $lint: ident, $deps: expr) => {{ + let mut command = cargo_metadata::MetadataCommand::new(); + if !$deps { + command.no_deps(); + } + + match command.exec() { + Ok(metadata) => metadata, + Err(err) => { + span_lint($cx, $lint, DUMMY_SP, &format!("could not read cargo metadata: {}", err)); + return; + }, + } + }}; +} + #[cfg(test)] mod test { use super::{trim_multiline, without_block_comments}; diff --git a/clippy_lints/src/wildcard_dependencies.rs b/clippy_lints/src/wildcard_dependencies.rs index d8d48eb15358d..511518082bec1 100644 --- a/clippy_lints/src/wildcard_dependencies.rs +++ b/clippy_lints/src/wildcard_dependencies.rs @@ -34,12 +34,7 @@ impl LateLintPass<'_, '_> for WildcardDependencies { return; } - let metadata = if let Ok(metadata) = cargo_metadata::MetadataCommand::new().no_deps().exec() { - metadata - } else { - span_lint(cx, WILDCARD_DEPENDENCIES, DUMMY_SP, "could not read cargo metadata"); - return; - }; + let metadata = unwrap_cargo_metadata!(cx, WILDCARD_DEPENDENCIES, false); for dep in &metadata.packages[0].dependencies { // VersionReq::any() does not work From c325c120c21657acb1b131ded41261889e51a62b Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Fri, 5 Jun 2020 22:30:14 +0200 Subject: [PATCH 59/72] Fix cargo ui tests when running inside rust repo --- clippy_dev/src/new_lint.rs | 2 ++ tests/compile-test.rs | 4 ---- tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml | 2 ++ tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml | 2 ++ .../multiple_crate_versions/5041_allow_dev_build/Cargo.toml | 2 ++ tests/ui-cargo/multiple_crate_versions/fail/Cargo.toml | 2 ++ tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml | 2 ++ tests/ui-cargo/wildcard_dependencies/fail/Cargo.toml | 2 ++ tests/ui-cargo/wildcard_dependencies/pass/Cargo.toml | 2 ++ 9 files changed, 16 insertions(+), 4 deletions(-) diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index c0b2dac2f60ff..1e032a7bc20cd 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -147,6 +147,8 @@ fn get_manifest_contents(lint_name: &str, hint: &str) -> String { name = "{}" version = "0.1.0" publish = false + +[workspace] "#, hint, lint_name ) diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 194354b291fda..11b3f69a828c4 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -220,10 +220,6 @@ fn run_ui_cargo(config: &mut compiletest::Config) { Ok(result) } - if cargo::is_rustc_test_suite() { - return; - } - config.mode = TestMode::Ui; config.src_base = Path::new("tests").join("ui-cargo").canonicalize().unwrap(); diff --git a/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml b/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml index c64adcf7c0131..ae0a603299629 100644 --- a/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml +++ b/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml @@ -2,3 +2,5 @@ name = "cargo_common_metadata" version = "0.1.0" publish = false + +[workspace] diff --git a/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml b/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml index c8233f328bb05..737e84e963c95 100644 --- a/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml +++ b/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml @@ -9,3 +9,5 @@ readme = "README.md" license = "MIT OR Apache-2.0" keywords = ["metadata", "lint", "clippy"] categories = ["development-tools::testing"] + +[workspace] diff --git a/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/Cargo.toml b/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/Cargo.toml index 72731fbc75d00..278bebbbd9e83 100644 --- a/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/Cargo.toml +++ b/tests/ui-cargo/multiple_crate_versions/5041_allow_dev_build/Cargo.toml @@ -5,6 +5,8 @@ name = "multiple_crate_versions" version = "0.1.0" publish = false +[workspace] + # One of the versions of winapi is only a dev dependency: allowed [dependencies] ctrlc = "=3.1.0" diff --git a/tests/ui-cargo/multiple_crate_versions/fail/Cargo.toml b/tests/ui-cargo/multiple_crate_versions/fail/Cargo.toml index 3a94b723f3fdb..4f97b01133402 100644 --- a/tests/ui-cargo/multiple_crate_versions/fail/Cargo.toml +++ b/tests/ui-cargo/multiple_crate_versions/fail/Cargo.toml @@ -3,6 +3,8 @@ name = "multiple_crate_versions" version = "0.1.0" publish = false +[workspace] + [dependencies] ctrlc = "=3.1.0" ansi_term = "=0.11.0" diff --git a/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml b/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml index a9b06420b333d..b4b49bb369acd 100644 --- a/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml +++ b/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml @@ -3,6 +3,8 @@ name = "cargo_common_metadata" version = "0.1.0" publish = false +[workspace] + [dependencies] regex = "1.3.7" serde = "1.0.110" diff --git a/tests/ui-cargo/wildcard_dependencies/fail/Cargo.toml b/tests/ui-cargo/wildcard_dependencies/fail/Cargo.toml index fd2a341485683..3e1a02cbb3c5b 100644 --- a/tests/ui-cargo/wildcard_dependencies/fail/Cargo.toml +++ b/tests/ui-cargo/wildcard_dependencies/fail/Cargo.toml @@ -3,5 +3,7 @@ name = "wildcard_dependencies" version = "0.1.0" publish = false +[workspace] + [dependencies] regex = "*" diff --git a/tests/ui-cargo/wildcard_dependencies/pass/Cargo.toml b/tests/ui-cargo/wildcard_dependencies/pass/Cargo.toml index 38cb139146e05..f844cab09ba7c 100644 --- a/tests/ui-cargo/wildcard_dependencies/pass/Cargo.toml +++ b/tests/ui-cargo/wildcard_dependencies/pass/Cargo.toml @@ -3,5 +3,7 @@ name = "wildcard_dependencies" version = "0.1.0" publish = false +[workspace] + [dependencies] regex = "1" From 623faac84ec56fa545163ab81d7e3b759a392353 Mon Sep 17 00:00:00 2001 From: Philipp Hansch Date: Sat, 6 Jun 2020 11:50:59 +0200 Subject: [PATCH 60/72] Cleanup: Use rustc's `same_types` instead of our `same_tys` --- clippy_lints/src/copies.rs | 14 +++++--------- clippy_lints/src/loops.rs | 8 ++++---- clippy_lints/src/methods/mod.rs | 12 ++++++------ clippy_lints/src/new_without_default.rs | 6 +++--- clippy_lints/src/types.rs | 6 +++--- clippy_lints/src/useless_conversion.rs | 14 +++++++------- clippy_lints/src/utils/mod.rs | 16 +--------------- 7 files changed, 29 insertions(+), 47 deletions(-) diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 66722786eab49..b6d50bdfa1466 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -1,9 +1,9 @@ -use crate::utils::{get_parent_expr, higher, if_sequence, same_tys, snippet, span_lint_and_note, span_lint_and_then}; +use crate::utils::{get_parent_expr, higher, if_sequence, snippet, span_lint_and_note, span_lint_and_then}; use crate::utils::{SpanlessEq, SpanlessHash}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::{Arm, Block, Expr, ExprKind, MatchSource, Pat, PatKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{Ty, TyS}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::Symbol; use std::collections::hash_map::Entry; @@ -242,15 +242,11 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_, '_>, conds: &[&Expr<'_>]) { /// Implementation of `MATCH_SAME_ARMS`. fn lint_match_arms<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &Expr<'_>) { - fn same_bindings<'tcx>( - cx: &LateContext<'_, 'tcx>, - lhs: &FxHashMap>, - rhs: &FxHashMap>, - ) -> bool { + fn same_bindings<'tcx>(lhs: &FxHashMap>, rhs: &FxHashMap>) -> bool { lhs.len() == rhs.len() && lhs .iter() - .all(|(name, l_ty)| rhs.get(name).map_or(false, |r_ty| same_tys(cx, l_ty, r_ty))) + .all(|(name, l_ty)| rhs.get(name).map_or(false, |r_ty| TyS::same_type(l_ty, r_ty))) } if let ExprKind::Match(_, ref arms, MatchSource::Normal) = expr.kind { @@ -269,7 +265,7 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &Expr<'_>) { (min_index..=max_index).all(|index| arms[index].guard.is_none()) && SpanlessEq::new(cx).eq_expr(&lhs.body, &rhs.body) && // all patterns should have the same bindings - same_bindings(cx, &bindings(cx, &lhs.pat), &bindings(cx, &rhs.pat)) + same_bindings(&bindings(cx, &lhs.pat), &bindings(cx, &rhs.pat)) }; let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); diff --git a/clippy_lints/src/loops.rs b/clippy_lints/src/loops.rs index dbe41823a9cf6..57c62d739640f 100644 --- a/clippy_lints/src/loops.rs +++ b/clippy_lints/src/loops.rs @@ -8,7 +8,7 @@ use crate::utils::{ multispan_sugg, snippet, snippet_opt, snippet_with_applicability, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, SpanlessEq, }; -use crate::utils::{is_type_diagnostic_item, qpath_res, same_tys, sugg}; +use crate::utils::{is_type_diagnostic_item, qpath_res, sugg}; use if_chain::if_chain; use rustc_ast::ast; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -24,7 +24,7 @@ use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; use rustc_middle::lint::in_external_macro; use rustc_middle::middle::region; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::{self, Ty, TyS}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::symbol::Symbol; @@ -1256,7 +1256,7 @@ fn check_for_loop_arg(cx: &LateContext<'_, '_>, pat: &Pat<'_>, arg: &Expr<'_>, e } else if method_name == "into_iter" && match_trait_method(cx, arg, &paths::INTO_ITERATOR) { let receiver_ty = cx.tables.expr_ty(&args[0]); let receiver_ty_adjusted = cx.tables.expr_ty_adjusted(&args[0]); - if same_tys(cx, receiver_ty, receiver_ty_adjusted) { + if TyS::same_type(receiver_ty, receiver_ty_adjusted) { let mut applicability = Applicability::MachineApplicable; let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); span_lint_and_sugg( @@ -1277,7 +1277,7 @@ fn check_for_loop_arg(cx: &LateContext<'_, '_>, pat: &Pat<'_>, arg: &Expr<'_>, e mutbl: Mutability::Not, }, ); - if same_tys(cx, receiver_ty_adjusted, ref_receiver_ty) { + if TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) { lint_iter_method(cx, args, arg, method_name) } } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index a9c0ff24fa602..78d69690c2d7c 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -18,7 +18,7 @@ use rustc_lint::{LateContext, LateLintPass, Lint, LintContext}; use rustc_middle::hir::map::Map; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::subst::GenericArgKind; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::{self, Ty, TyS}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::symbol::{sym, SymbolStr}; @@ -29,9 +29,9 @@ use crate::utils::{ get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, in_macro, is_copy, is_ctor_or_promotable_const_function, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment, match_def_path, match_qpath, match_trait_method, match_type, match_var, method_calls, method_chain_args, paths, - remove_blocks, return_ty, same_tys, single_segment_path, snippet, snippet_with_applicability, - snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, - span_lint_and_then, sugg, walk_ptrs_ty, walk_ptrs_ty_depth, SpanlessEq, + remove_blocks, return_ty, single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite, + span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, sugg, walk_ptrs_ty, + walk_ptrs_ty_depth, SpanlessEq, }; declare_clippy_lint! { @@ -1548,7 +1548,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods { let contains_self_ty = |ty: Ty<'tcx>| { ty.walk().any(|inner| match inner.unpack() { - GenericArgKind::Type(inner_ty) => same_tys(cx, self_ty, inner_ty), + GenericArgKind::Type(inner_ty) => TyS::same_type(self_ty, inner_ty), GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, }) @@ -1575,7 +1575,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods { } } - if name == "new" && !same_tys(cx, ret_ty, self_ty) { + if name == "new" && !TyS::same_type(ret_ty, self_ty) { span_lint( cx, NEW_RET_NO_SELF, diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index e556e5d59c188..dd236535c18ad 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -1,13 +1,13 @@ use crate::utils::paths; use crate::utils::sugg::DiagnosticBuilderExt; -use crate::utils::{get_trait_def_id, return_ty, same_tys, span_lint_hir_and_then}; +use crate::utils::{get_trait_def_id, return_ty, span_lint_hir_and_then}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::HirIdSet; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{Ty, TyS}; use rustc_session::{declare_tool_lint, impl_lint_pass}; declare_clippy_lint! { @@ -93,7 +93,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NewWithoutDefault { let self_def_id = cx.tcx.hir().local_def_id(cx.tcx.hir().get_parent_item(id)); let self_ty = cx.tcx.type_of(self_def_id); if_chain! { - if same_tys(cx, self_ty, return_ty(cx, id)); + if TyS::same_type(self_ty, return_ty(cx, id)); if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT); then { if self.impling_types.is_none() { diff --git a/clippy_lints/src/types.rs b/clippy_lints/src/types.rs index 5ca30d598eb15..bc5fe44b30f8f 100644 --- a/clippy_lints/src/types.rs +++ b/clippy_lints/src/types.rs @@ -17,7 +17,7 @@ use rustc_hir::{ use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, InferTy, Ty, TyCtxt, TypeckTables}; +use rustc_middle::ty::{self, InferTy, Ty, TyCtxt, TyS, TypeckTables}; use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::Span; @@ -31,7 +31,7 @@ use crate::utils::paths; use crate::utils::{ clip, comparisons, differing_macro_contexts, higher, in_constant, indent_of, int_bits, is_type_diagnostic_item, last_path_segment, match_def_path, match_path, method_chain_args, multispan_sugg, numeric_literal::NumericLiteral, - qpath_res, same_tys, sext, snippet, snippet_block_with_applicability, snippet_opt, snippet_with_applicability, + qpath_res, sext, snippet, snippet_block_with_applicability, snippet_opt, snippet_with_applicability, snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, unsext, }; @@ -2556,7 +2556,7 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 't if let ExprKind::Path(QPath::TypeRelative(ref ty, ref method)) = fun.kind; if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind; then { - if !same_tys(self.cx, self.target.ty(), self.body.expr_ty(e)) { + if !TyS::same_type(self.target.ty(), self.body.expr_ty(e)) { return; } diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 7fa97b2469912..141035a980ade 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -1,12 +1,12 @@ use crate::utils::{ - is_type_diagnostic_item, match_def_path, match_trait_method, paths, same_tys, snippet, snippet_with_macro_callsite, + is_type_diagnostic_item, match_def_path, match_trait_method, paths, snippet, snippet_with_macro_callsite, span_lint_and_help, span_lint_and_sugg, }; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, HirId, MatchSource}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; +use rustc_middle::ty::{self, TyS}; use rustc_session::{declare_tool_lint, impl_lint_pass}; declare_clippy_lint! { @@ -65,7 +65,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { if match_trait_method(cx, e, &paths::INTO) && &*name.ident.as_str() == "into" { let a = cx.tables.expr_ty(e); let b = cx.tables.expr_ty(&args[0]); - if same_tys(cx, a, b) { + if TyS::same_type(a, b) { let sugg = snippet_with_macro_callsite(cx, args[0].span, "").to_string(); span_lint_and_sugg( cx, @@ -81,7 +81,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { if match_trait_method(cx, e, &paths::INTO_ITERATOR) && &*name.ident.as_str() == "into_iter" { let a = cx.tables.expr_ty(e); let b = cx.tables.expr_ty(&args[0]); - if same_tys(cx, a, b) { + if TyS::same_type(a, b) { let sugg = snippet(cx, args[0].span, "").into_owned(); span_lint_and_sugg( cx, @@ -101,7 +101,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { if is_type_diagnostic_item(cx, a, sym!(result_type)); if let ty::Adt(_, substs) = a.kind; if let Some(a_type) = substs.types().next(); - if same_tys(cx, a_type, b); + if TyS::same_type(a_type, b); then { span_lint_and_help( @@ -131,7 +131,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { if is_type_diagnostic_item(cx, a, sym!(result_type)); if let ty::Adt(_, substs) = a.kind; if let Some(a_type) = substs.types().next(); - if same_tys(cx, a_type, b); + if TyS::same_type(a_type, b); then { let hint = format!("consider removing `{}()`", snippet(cx, path.span, "TryFrom::try_from")); @@ -148,7 +148,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UselessConversion { if_chain! { if match_def_path(cx, def_id, &paths::FROM_FROM); - if same_tys(cx, a, b); + if TyS::same_type(a, b); then { let sugg = snippet(cx, args[0].span.source_callsite(), "").into_owned(); diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index 2cdb6486fcb42..9a6750c51abe3 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -40,7 +40,7 @@ use rustc_hir::{ use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_middle::hir::map::Map; -use rustc_middle::ty::{self, layout::IntegerExt, subst::GenericArg, Binder, Ty, TyCtxt, TypeFoldable}; +use rustc_middle::ty::{self, layout::IntegerExt, subst::GenericArg, Ty, TyCtxt, TypeFoldable}; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::original_sp; use rustc_span::symbol::{self, kw, Symbol}; @@ -879,20 +879,6 @@ pub fn return_ty<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, fn_item: hir::HirId) -> T cx.tcx.erase_late_bound_regions(&ret_ty) } -/// Checks if two types are the same. -/// -/// This discards any lifetime annotations, too. -// -// FIXME: this works correctly for lifetimes bounds (`for <'a> Foo<'a>` == -// `for <'b> Foo<'b>`, but not for type parameters). -pub fn same_tys<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, a: Ty<'tcx>, b: Ty<'tcx>) -> bool { - let a = cx.tcx.erase_late_bound_regions(&Binder::bind(a)); - let b = cx.tcx.erase_late_bound_regions(&Binder::bind(b)); - cx.tcx - .infer_ctxt() - .enter(|infcx| infcx.can_eq(cx.param_env, a, b).is_ok()) -} - /// Returns `true` if the given type is an `unsafe` function. pub fn type_is_unsafe_function<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: Ty<'tcx>) -> bool { match ty.kind { From 5bdbc45ae579e7b8f4187bc791abd67924cb626b Mon Sep 17 00:00:00 2001 From: flip1995 Date: Sun, 7 Jun 2020 03:07:48 +0200 Subject: [PATCH 61/72] Rustup to rust-lang/rust#71796 --- tests/ui/or_fun_call.fixed | 4 ++-- tests/ui/or_fun_call.stderr | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index 7bb08797ef396..2045ffdb5f09d 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -29,7 +29,7 @@ fn or_fun_call() { with_enum.unwrap_or(Enum::A(5)); let with_const_fn = Some(Duration::from_secs(1)); - with_const_fn.unwrap_or(Duration::from_secs(5)); + with_const_fn.unwrap_or_else(|| Duration::from_secs(5)); let with_constructor = Some(vec![1]); with_constructor.unwrap_or_else(make); @@ -94,7 +94,7 @@ fn test_or_with_ctors() { let b = "b".to_string(); let _ = Some(Bar("a".to_string(), Duration::from_secs(1))) - .or(Some(Bar(b, Duration::from_secs(2)))); + .or_else(|| Some(Bar(b, Duration::from_secs(2)))); let vec = vec!["foo"]; let _ = opt.ok_or(vec.len()); diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index 96d55771e6cef..bc5978b538f16 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -1,10 +1,16 @@ +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:32:19 + | +LL | with_const_fn.unwrap_or(Duration::from_secs(5)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| Duration::from_secs(5))` + | + = note: `-D clippy::or-fun-call` implied by `-D warnings` + error: use of `unwrap_or` followed by a function call --> $DIR/or_fun_call.rs:35:22 | LL | with_constructor.unwrap_or(make()); | ^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(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:38:5 @@ -78,5 +84,11 @@ error: use of `or` followed by a function call LL | let _ = Some("a".to_string()).or(Some("b".to_string())); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_else(|| Some("b".to_string()))` -error: aborting due to 13 previous errors +error: use of `or` followed by a function call + --> $DIR/or_fun_call.rs:97:10 + | +LL | .or(Some(Bar(b, Duration::from_secs(2)))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_else(|| Some(Bar(b, Duration::from_secs(2))))` + +error: aborting due to 15 previous errors From d9aa26a14dca32e0c4f2718ad1d3322f0de1674d Mon Sep 17 00:00:00 2001 From: flip1995 Date: Sun, 7 Jun 2020 14:54:21 +0200 Subject: [PATCH 62/72] Temporarily disable RLS integration test --- .github/workflows/clippy_bors.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 3958ba012467b..0c80394f03e3c 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -232,7 +232,8 @@ jobs: matrix: integration: - 'rust-lang/cargo' - - 'rust-lang/rls' + # FIXME: re-enable once fmt_macros is renamed in RLS + # - 'rust-lang/rls' - 'rust-lang/chalk' - 'rust-lang/rustfmt' - 'Marwes/combine' From dc13016ac2df1ce2663660389409b15eb2cf7e40 Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Wed, 3 Jun 2020 01:13:57 +0200 Subject: [PATCH 63/72] Make let_and_return a late lint pass --- clippy_lints/src/let_and_return.rs | 82 ++++++++++++++++++++++++++++++ clippy_lints/src/lib.rs | 8 +-- clippy_lints/src/returns.rs | 82 ++---------------------------- src/lintlist/mod.rs | 2 +- 4 files changed, 91 insertions(+), 83 deletions(-) create mode 100644 clippy_lints/src/let_and_return.rs diff --git a/clippy_lints/src/let_and_return.rs b/clippy_lints/src/let_and_return.rs new file mode 100644 index 0000000000000..8b877f696afea --- /dev/null +++ b/clippy_lints/src/let_and_return.rs @@ -0,0 +1,82 @@ +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Block, ExprKind, PatKind, StmtKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{in_macro, match_qpath, snippet_opt, span_lint_and_then}; + +declare_clippy_lint! { + /// **What it does:** Checks for `let`-bindings, which are subsequently + /// returned. + /// + /// **Why is this bad?** It is just extraneous code. Remove it to make your code + /// more rusty. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn foo() -> String { + /// let x = String::new(); + /// x + /// } + /// ``` + /// instead, use + /// ``` + /// fn foo() -> String { + /// String::new() + /// } + /// ``` + pub LET_AND_RETURN, + style, + "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block" +} + +declare_lint_pass!(LetReturn => [LET_AND_RETURN]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LetReturn { + fn check_block(&mut self, cx: &LateContext<'a, 'tcx>, block: &'tcx Block<'_>) { + // we need both a let-binding stmt and an expr + if_chain! { + if let Some(retexpr) = block.expr; + if let Some(stmt) = block.stmts.iter().last(); + if let StmtKind::Local(local) = &stmt.kind; + if local.ty.is_none(); + if local.attrs.is_empty(); + if let Some(initexpr) = &local.init; + if let PatKind::Binding(.., ident, _) = local.pat.kind; + if let ExprKind::Path(qpath) = &retexpr.kind; + if match_qpath(qpath, &[&*ident.name.as_str()]); + if !in_external_macro(cx.sess(), initexpr.span); + if !in_external_macro(cx.sess(), retexpr.span); + if !in_external_macro(cx.sess(), local.span); + if !in_macro(local.span); + then { + span_lint_and_then( + cx, + LET_AND_RETURN, + retexpr.span, + "returning the result of a `let` binding from a block", + |err| { + err.span_label(local.span, "unnecessary `let` binding"); + + if let Some(snippet) = snippet_opt(cx, initexpr.span) { + err.multipart_suggestion( + "return the expression directly", + vec![ + (local.span, String::new()), + (retexpr.span, snippet), + ], + Applicability::MachineApplicable, + ); + } else { + err.span_help(initexpr.span, "this expression can be directly returned"); + } + }, + ); + } + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 6f8923b266092..6bc9e23bac5dd 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -239,6 +239,7 @@ mod large_const_arrays; mod large_enum_variant; mod large_stack_arrays; mod len_zero; +mod let_and_return; mod let_if_seq; mod let_underscore; mod lifetimes; @@ -596,6 +597,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &large_stack_arrays::LARGE_STACK_ARRAYS, &len_zero::LEN_WITHOUT_IS_EMPTY, &len_zero::LEN_ZERO, + &let_and_return::LET_AND_RETURN, &let_if_seq::USELESS_LET_IF_SEQ, &let_underscore::LET_UNDERSCORE_LOCK, &let_underscore::LET_UNDERSCORE_MUST_USE, @@ -772,7 +774,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: ®ex::INVALID_REGEX, ®ex::REGEX_MACRO, ®ex::TRIVIAL_REGEX, - &returns::LET_AND_RETURN, &returns::NEEDLESS_RETURN, &returns::UNUSED_UNIT, &serde_api::SERDE_API_MISUSE, @@ -1022,6 +1023,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| box formatting::Formatting); store.register_early_pass(|| box misc_early::MiscEarlyLints); store.register_early_pass(|| box returns::Return); + store.register_late_pass(|| box let_and_return::LetReturn); store.register_early_pass(|| box collapsible_if::CollapsibleIf); store.register_early_pass(|| box items_after_statements::ItemsAfterStatements); store.register_early_pass(|| box precedence::Precedence); @@ -1265,6 +1267,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT), LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), LintId::of(&len_zero::LEN_ZERO), + LintId::of(&let_and_return::LET_AND_RETURN), LintId::of(&let_underscore::LET_UNDERSCORE_LOCK), LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES), LintId::of(&lifetimes::NEEDLESS_LIFETIMES), @@ -1390,7 +1393,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(®ex::INVALID_REGEX), LintId::of(®ex::REGEX_MACRO), LintId::of(®ex::TRIVIAL_REGEX), - LintId::of(&returns::LET_AND_RETURN), LintId::of(&returns::NEEDLESS_RETURN), LintId::of(&returns::UNUSED_UNIT), LintId::of(&serde_api::SERDE_API_MISUSE), @@ -1474,6 +1476,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&inherent_to_string::INHERENT_TO_STRING), LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), LintId::of(&len_zero::LEN_ZERO), + LintId::of(&let_and_return::LET_AND_RETURN), LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING), LintId::of(&loops::EMPTY_LOOP), LintId::of(&loops::FOR_KV_MAP), @@ -1526,7 +1529,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), LintId::of(®ex::REGEX_MACRO), LintId::of(®ex::TRIVIAL_REGEX), - LintId::of(&returns::LET_AND_RETURN), LintId::of(&returns::NEEDLESS_RETURN), LintId::of(&returns::UNUSED_UNIT), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 35464f629c364..3c93974417356 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -8,7 +8,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::BytePos; -use crate::utils::{in_macro, match_path_ast, snippet_opt, span_lint_and_sugg, span_lint_and_then}; +use crate::utils::{snippet_opt, span_lint_and_sugg, span_lint_and_then}; declare_clippy_lint! { /// **What it does:** Checks for return statements at the end of a block. @@ -36,33 +36,6 @@ declare_clippy_lint! { "using a return statement like `return expr;` where an expression would suffice" } -declare_clippy_lint! { - /// **What it does:** Checks for `let`-bindings, which are subsequently - /// returned. - /// - /// **Why is this bad?** It is just extraneous code. Remove it to make your code - /// more rusty. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// fn foo() -> String { - /// let x = String::new(); - /// x - /// } - /// ``` - /// instead, use - /// ``` - /// fn foo() -> String { - /// String::new() - /// } - /// ``` - pub LET_AND_RETURN, - style, - "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block" -} - declare_clippy_lint! { /// **What it does:** Checks for unit (`()`) expressions that can be removed. /// @@ -90,7 +63,7 @@ enum RetReplacement { Block, } -declare_lint_pass!(Return => [NEEDLESS_RETURN, LET_AND_RETURN, UNUSED_UNIT]); +declare_lint_pass!(Return => [NEEDLESS_RETURN, UNUSED_UNIT]); impl Return { // Check the final stmt or expr in a block for unnecessary return. @@ -105,7 +78,7 @@ impl Return { } } - // Check a the final expression in a block if it's a return. + // Check the final expression in a block if it's a return. fn check_final_expr( &mut self, cx: &EarlyContext<'_>, @@ -186,54 +159,6 @@ impl Return { }, } } - - // Check for "let x = EXPR; x" - fn check_let_return(cx: &EarlyContext<'_>, block: &ast::Block) { - let mut it = block.stmts.iter(); - - // we need both a let-binding stmt and an expr - if_chain! { - if let Some(retexpr) = it.next_back(); - if let ast::StmtKind::Expr(ref retexpr) = retexpr.kind; - if let Some(stmt) = it.next_back(); - if let ast::StmtKind::Local(ref local) = stmt.kind; - // don't lint in the presence of type inference - if local.ty.is_none(); - if local.attrs.is_empty(); - if let Some(ref initexpr) = local.init; - if let ast::PatKind::Ident(_, ident, _) = local.pat.kind; - if let ast::ExprKind::Path(_, ref path) = retexpr.kind; - if match_path_ast(path, &[&*ident.name.as_str()]); - if !in_external_macro(cx.sess(), initexpr.span); - if !in_external_macro(cx.sess(), retexpr.span); - if !in_external_macro(cx.sess(), local.span); - if !in_macro(local.span); - then { - span_lint_and_then( - cx, - LET_AND_RETURN, - retexpr.span, - "returning the result of a `let` binding from a block", - |err| { - err.span_label(local.span, "unnecessary `let` binding"); - - if let Some(snippet) = snippet_opt(cx, initexpr.span) { - err.multipart_suggestion( - "return the expression directly", - vec![ - (local.span, String::new()), - (retexpr.span, snippet), - ], - Applicability::MachineApplicable, - ); - } else { - err.span_help(initexpr.span, "this expression can be directly returned"); - } - }, - ); - } - } - } } impl EarlyLintPass for Return { @@ -254,7 +179,6 @@ impl EarlyLintPass for Return { } fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) { - Self::check_let_return(cx, block); if_chain! { if let Some(ref stmt) = block.stmts.last(); if let ast::StmtKind::Expr(ref expr) = stmt.kind; diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index d5d07ccb2eb0d..bb191f9be92ca 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1023,7 +1023,7 @@ pub static ref ALL_LINTS: Vec = vec![ group: "style", desc: "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block", deprecation: None, - module: "returns", + module: "let_and_return", }, Lint { name: "let_underscore_lock", From 9c205d7b1baa982ae7063d57b18088ecf28df83b Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Sat, 6 Jun 2020 21:51:41 +0200 Subject: [PATCH 64/72] Rename let_and_return test for consistency with the lint name --- tests/ui/{let_return.rs => let_and_return.rs} | 0 tests/ui/{let_return.stderr => let_and_return.stderr} | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename tests/ui/{let_return.rs => let_and_return.rs} (100%) rename tests/ui/{let_return.stderr => let_and_return.stderr} (89%) diff --git a/tests/ui/let_return.rs b/tests/ui/let_and_return.rs similarity index 100% rename from tests/ui/let_return.rs rename to tests/ui/let_and_return.rs diff --git a/tests/ui/let_return.stderr b/tests/ui/let_and_return.stderr similarity index 89% rename from tests/ui/let_return.stderr rename to tests/ui/let_and_return.stderr index 128a22c86e360..eacf948b3927a 100644 --- a/tests/ui/let_return.stderr +++ b/tests/ui/let_and_return.stderr @@ -1,5 +1,5 @@ error: returning the result of a `let` binding from a block - --> $DIR/let_return.rs:7:5 + --> $DIR/let_and_return.rs:7:5 | LL | let x = 5; | ---------- unnecessary `let` binding @@ -14,7 +14,7 @@ LL | 5 | error: returning the result of a `let` binding from a block - --> $DIR/let_return.rs:13:9 + --> $DIR/let_and_return.rs:13:9 | LL | let x = 5; | ---------- unnecessary `let` binding From dac8a3c1ca19d2b5934ecbe2ed79ae6c156fd885 Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Sun, 7 Jun 2020 00:30:39 +0200 Subject: [PATCH 65/72] let_and_return: do not lint if last statement borrows --- clippy_lints/src/let_and_return.rs | 61 ++++++++++++++++++++++++++- clippy_lints/src/utils/mod.rs | 2 +- tests/ui/let_and_return.rs | 68 ++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/let_and_return.rs b/clippy_lints/src/let_and_return.rs index 8b877f696afea..6d3fb317bcfc5 100644 --- a/clippy_lints/src/let_and_return.rs +++ b/clippy_lints/src/let_and_return.rs @@ -1,8 +1,12 @@ use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{Block, ExprKind, PatKind, StmtKind}; +use rustc_hir::def_id::DefId; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{Block, Expr, ExprKind, PatKind, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::subst::GenericArgKind; use rustc_session::{declare_lint_pass, declare_tool_lint}; use crate::utils::{in_macro, match_qpath, snippet_opt, span_lint_and_then}; @@ -49,6 +53,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LetReturn { if let PatKind::Binding(.., ident, _) = local.pat.kind; if let ExprKind::Path(qpath) = &retexpr.kind; if match_qpath(qpath, &[&*ident.name.as_str()]); + if !last_statement_borrows(cx, initexpr); if !in_external_macro(cx.sess(), initexpr.span); if !in_external_macro(cx.sess(), retexpr.span); if !in_external_macro(cx.sess(), local.span); @@ -80,3 +85,57 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LetReturn { } } } + +fn last_statement_borrows<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + let mut visitor = BorrowVisitor { cx, borrows: false }; + walk_expr(&mut visitor, expr); + visitor.borrows +} + +struct BorrowVisitor<'a, 'tcx> { + cx: &'a LateContext<'a, 'tcx>, + borrows: bool, +} + +impl BorrowVisitor<'_, '_> { + fn fn_def_id(&self, expr: &Expr<'_>) -> Option { + match &expr.kind { + ExprKind::MethodCall(..) => self.cx.tables.type_dependent_def_id(expr.hir_id), + ExprKind::Call( + Expr { + kind: ExprKind::Path(qpath), + .. + }, + .., + ) => self.cx.tables.qpath_res(qpath, expr.hir_id).opt_def_id(), + _ => None, + } + } +} + +impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.borrows { + return; + } + + if let Some(def_id) = self.fn_def_id(expr) { + self.borrows = self + .cx + .tcx + .fn_sig(def_id) + .output() + .skip_binder() + .walk() + .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_))); + } + + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index 06638e7187bae..39410acea4e70 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -400,7 +400,7 @@ pub fn method_calls<'tcx>( /// Matches an `Expr` against a chain of methods, and return the matched `Expr`s. /// /// For example, if `expr` represents the `.baz()` in `foo.bar().baz()`, -/// `matched_method_chain(expr, &["bar", "baz"])` will return a `Vec` +/// `method_chain_args(expr, &["bar", "baz"])` will return a `Vec` /// containing the `Expr`s for /// `.bar()` and `.baz()` pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option]>> { diff --git a/tests/ui/let_and_return.rs b/tests/ui/let_and_return.rs index 23645d48fe799..09614b8c1ad78 100644 --- a/tests/ui/let_and_return.rs +++ b/tests/ui/let_and_return.rs @@ -67,4 +67,72 @@ macro_rules! tuple_encode { tuple_encode!(T0, T1, T2, T3, T4, T5, T6, T7); +mod no_lint_if_stmt_borrows { + mod issue_3792 { + use std::io::{self, BufRead, Stdin}; + + fn read_line() -> String { + let stdin = io::stdin(); + let line = stdin.lock().lines().next().unwrap().unwrap(); + line + } + } + + mod issue_3324 { + use std::cell::RefCell; + use std::rc::{Rc, Weak}; + + fn test(value: Weak>) -> u32 { + let value = value.upgrade().unwrap(); + let ret = value.borrow().baz(); + ret + } + + struct Bar {} + + impl Bar { + fn new() -> Self { + Bar {} + } + fn baz(&self) -> u32 { + 0 + } + } + + fn main() { + let a = Rc::new(RefCell::new(Bar::new())); + let b = Rc::downgrade(&a); + test(b); + } + } + + mod free_function { + struct Inner; + + struct Foo<'a> { + inner: &'a Inner, + } + + impl Drop for Foo<'_> { + fn drop(&mut self) {} + } + + impl Foo<'_> { + fn value(&self) -> i32 { + 42 + } + } + + fn some_foo(inner: &Inner) -> Foo<'_> { + Foo { inner } + } + + fn test() -> i32 { + let x = Inner {}; + let value = some_foo(&x).value(); + value + } + } +} + fn main() {} From ebfc1da07d2cd1cba87a3df79c5ffbfc0d25618c Mon Sep 17 00:00:00 2001 From: Eduardo Broto Date: Sun, 7 Jun 2020 20:38:28 +0200 Subject: [PATCH 66/72] reversed_empty_ranges: don't lint N..N except in for loop arg --- clippy_lints/src/ranges.rs | 62 +++++++++---------- tests/ui/reversed_empty_ranges_fixable.fixed | 8 +-- tests/ui/reversed_empty_ranges_fixable.rs | 8 +-- tests/ui/reversed_empty_ranges_fixable.stderr | 16 ++--- tests/ui/reversed_empty_ranges_unfixable.rs | 5 +- .../ui/reversed_empty_ranges_unfixable.stderr | 18 ++---- 6 files changed, 51 insertions(+), 66 deletions(-) diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 1eb26d97ed4d2..45de4d29375c7 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -241,14 +241,26 @@ fn check_inclusive_range_minus_one(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { } fn check_reversed_empty_range(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { - fn inside_indexing_expr<'a>(cx: &'a LateContext<'_, '_>, expr: &Expr<'_>) -> Option<&'a Expr<'a>> { - match get_parent_expr(cx, expr) { - parent_expr @ Some(Expr { + fn inside_indexing_expr<'a>(cx: &'a LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + matches!( + get_parent_expr(cx, expr), + Some(Expr { kind: ExprKind::Index(..), .. - }) => parent_expr, - _ => None, + }) + ) + } + + fn is_for_loop_arg(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> bool { + let mut cur_expr = expr; + while let Some(parent_expr) = get_parent_expr(cx, cur_expr) { + match higher::for_loop(parent_expr) { + Some((_, args, _)) if args.hir_id == expr.hir_id => return true, + _ => cur_expr = parent_expr, + } } + + false } fn is_empty_range(limits: RangeLimits, ordering: Ordering) -> bool { @@ -267,34 +279,18 @@ fn check_reversed_empty_range(cx: &LateContext<'_, '_>, expr: &Expr<'_>) { if let Some(ordering) = Constant::partial_cmp(cx.tcx, ty, &start_idx, &end_idx); if is_empty_range(limits, ordering); then { - if let Some(parent_expr) = inside_indexing_expr(cx, expr) { - let (reason, outcome) = if ordering == Ordering::Equal { - ("empty", "always yield an empty slice") - } else { - ("reversed", "panic at run-time") - }; - - span_lint_and_then( - cx, - REVERSED_EMPTY_RANGES, - expr.span, - &format!("this range is {} and using it to index a slice will {}", reason, outcome), - |diag| { - if_chain! { - if ordering == Ordering::Equal; - if let ty::Slice(slice_ty) = cx.tables.expr_ty(parent_expr).kind; - then { - diag.span_suggestion( - parent_expr.span, - "if you want an empty slice, use", - format!("[] as &[{}]", slice_ty), - Applicability::MaybeIncorrect - ); - } - } - } - ); - } else { + if inside_indexing_expr(cx, expr) { + // Avoid linting `N..N` as it has proven to be useful, see #5689 and #5628 ... + if ordering != Ordering::Equal { + span_lint( + cx, + REVERSED_EMPTY_RANGES, + expr.span, + "this range is reversed and using it to index a slice will panic at run-time", + ); + } + // ... except in for loop arguments for backwards compatibility with `reverse_range_loop` + } else if ordering != Ordering::Equal || is_for_loop_arg(cx, expr) { span_lint_and_then( cx, REVERSED_EMPTY_RANGES, diff --git a/tests/ui/reversed_empty_ranges_fixable.fixed b/tests/ui/reversed_empty_ranges_fixable.fixed index 332c0427ef65a..79e482eec3037 100644 --- a/tests/ui/reversed_empty_ranges_fixable.fixed +++ b/tests/ui/reversed_empty_ranges_fixable.fixed @@ -4,8 +4,6 @@ const ANSWER: i32 = 42; fn main() { - let arr = [1, 2, 3, 4, 5]; - // These should be linted: (21..=42).rev().for_each(|x| println!("{}", x)); @@ -14,16 +12,18 @@ fn main() { for _ in (-42..=-21).rev() {} for _ in (21u32..42u32).rev() {} - let _ = &[] as &[i32]; - // These should be ignored as they are not empty ranges: (21..=42).for_each(|x| println!("{}", x)); (21..42).for_each(|x| println!("{}", x)); + let arr = [1, 2, 3, 4, 5]; let _ = &arr[1..=3]; let _ = &arr[1..3]; for _ in 21..=42 {} for _ in 21..42 {} + + // This range is empty but should be ignored, see issue #5689 + let _ = &arr[0..0]; } diff --git a/tests/ui/reversed_empty_ranges_fixable.rs b/tests/ui/reversed_empty_ranges_fixable.rs index 901ec8bcc09f4..b2e8bf33771ac 100644 --- a/tests/ui/reversed_empty_ranges_fixable.rs +++ b/tests/ui/reversed_empty_ranges_fixable.rs @@ -4,8 +4,6 @@ const ANSWER: i32 = 42; fn main() { - let arr = [1, 2, 3, 4, 5]; - // These should be linted: (42..=21).for_each(|x| println!("{}", x)); @@ -14,16 +12,18 @@ fn main() { for _ in -21..=-42 {} for _ in 42u32..21u32 {} - let _ = &arr[3..3]; - // These should be ignored as they are not empty ranges: (21..=42).for_each(|x| println!("{}", x)); (21..42).for_each(|x| println!("{}", x)); + let arr = [1, 2, 3, 4, 5]; let _ = &arr[1..=3]; let _ = &arr[1..3]; for _ in 21..=42 {} for _ in 21..42 {} + + // This range is empty but should be ignored, see issue #5689 + let _ = &arr[0..0]; } diff --git a/tests/ui/reversed_empty_ranges_fixable.stderr b/tests/ui/reversed_empty_ranges_fixable.stderr index 9a646fd99398d..de83c4f3d633c 100644 --- a/tests/ui/reversed_empty_ranges_fixable.stderr +++ b/tests/ui/reversed_empty_ranges_fixable.stderr @@ -1,5 +1,5 @@ error: this range is empty so it will yield no values - --> $DIR/reversed_empty_ranges_fixable.rs:11:5 + --> $DIR/reversed_empty_ranges_fixable.rs:9:5 | LL | (42..=21).for_each(|x| println!("{}", x)); | ^^^^^^^^^ @@ -11,7 +11,7 @@ LL | (21..=42).rev().for_each(|x| println!("{}", x)); | ^^^^^^^^^^^^^^^ error: this range is empty so it will yield no values - --> $DIR/reversed_empty_ranges_fixable.rs:12:13 + --> $DIR/reversed_empty_ranges_fixable.rs:10:13 | LL | let _ = (ANSWER..21).filter(|x| x % 2 == 0).take(10).collect::>(); | ^^^^^^^^^^^^ @@ -22,7 +22,7 @@ LL | let _ = (21..ANSWER).rev().filter(|x| x % 2 == 0).take(10).collect:: $DIR/reversed_empty_ranges_fixable.rs:14:14 + --> $DIR/reversed_empty_ranges_fixable.rs:12:14 | LL | for _ in -21..=-42 {} | ^^^^^^^^^ @@ -33,7 +33,7 @@ LL | for _ in (-42..=-21).rev() {} | ^^^^^^^^^^^^^^^^^ error: this range is empty so it will yield no values - --> $DIR/reversed_empty_ranges_fixable.rs:15:14 + --> $DIR/reversed_empty_ranges_fixable.rs:13:14 | LL | for _ in 42u32..21u32 {} | ^^^^^^^^^^^^ @@ -43,11 +43,5 @@ help: consider using the following if you are attempting to iterate over this ra LL | for _ in (21u32..42u32).rev() {} | ^^^^^^^^^^^^^^^^^^^^ -error: this range is empty and using it to index a slice will always yield an empty slice - --> $DIR/reversed_empty_ranges_fixable.rs:17:18 - | -LL | let _ = &arr[3..3]; - | ----^^^^- help: if you want an empty slice, use: `[] as &[i32]` - -error: aborting due to 5 previous errors +error: aborting due to 4 previous errors diff --git a/tests/ui/reversed_empty_ranges_unfixable.rs b/tests/ui/reversed_empty_ranges_unfixable.rs index 561a35625f02e..264d3d1e95af4 100644 --- a/tests/ui/reversed_empty_ranges_unfixable.rs +++ b/tests/ui/reversed_empty_ranges_unfixable.rs @@ -4,11 +4,12 @@ const ANSWER: i32 = 42; const SOME_NUM: usize = 3; fn main() { - let _ = (42 + 10..42 + 10).map(|x| x / 2).find(|&x| x == 21); - let arr = [1, 2, 3, 4, 5]; let _ = &arr[3usize..=1usize]; let _ = &arr[SOME_NUM..1]; for _ in ANSWER..ANSWER {} + + // Should not be linted, see issue #5689 + let _ = (42 + 10..42 + 10).map(|x| x / 2).find(|&x| x == 21); } diff --git a/tests/ui/reversed_empty_ranges_unfixable.stderr b/tests/ui/reversed_empty_ranges_unfixable.stderr index 240188cbb46cb..f23d4eb0f9ca4 100644 --- a/tests/ui/reversed_empty_ranges_unfixable.stderr +++ b/tests/ui/reversed_empty_ranges_unfixable.stderr @@ -1,28 +1,22 @@ -error: this range is empty so it will yield no values - --> $DIR/reversed_empty_ranges_unfixable.rs:7:13 - | -LL | let _ = (42 + 10..42 + 10).map(|x| x / 2).find(|&x| x == 21); - | ^^^^^^^^^^^^^^^^^^ - | - = note: `-D clippy::reversed-empty-ranges` implied by `-D warnings` - error: this range is reversed and using it to index a slice will panic at run-time - --> $DIR/reversed_empty_ranges_unfixable.rs:10:18 + --> $DIR/reversed_empty_ranges_unfixable.rs:8:18 | LL | let _ = &arr[3usize..=1usize]; | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::reversed-empty-ranges` implied by `-D warnings` error: this range is reversed and using it to index a slice will panic at run-time - --> $DIR/reversed_empty_ranges_unfixable.rs:11:18 + --> $DIR/reversed_empty_ranges_unfixable.rs:9:18 | LL | let _ = &arr[SOME_NUM..1]; | ^^^^^^^^^^^ error: this range is empty so it will yield no values - --> $DIR/reversed_empty_ranges_unfixable.rs:13:14 + --> $DIR/reversed_empty_ranges_unfixable.rs:11:14 | LL | for _ in ANSWER..ANSWER {} | ^^^^^^^^^^^^^^ -error: aborting due to 4 previous errors +error: aborting due to 3 previous errors From 7b6dc7b33dc437a59330ef3f5426102ca60fbf51 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 19 Mar 2020 14:14:52 +0100 Subject: [PATCH 67/72] add `unnested_or_patterns` lint --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 7 + clippy_lints/src/unnested_or_patterns.rs | 407 +++++++++++++++++ clippy_lints/src/utils/ast_utils.rs | 525 ++++++++++++++++++++++ clippy_lints/src/utils/hir_utils.rs | 10 +- clippy_lints/src/utils/mod.rs | 3 +- src/lintlist/mod.rs | 7 + tests/ui/neg_cmp_op_on_partial_ord.rs | 1 + tests/ui/neg_cmp_op_on_partial_ord.stderr | 8 +- tests/ui/unnested_or_patterns.fixed | 41 ++ tests/ui/unnested_or_patterns.rs | 41 ++ tests/ui/unnested_or_patterns.stderr | 267 +++++++++++ tests/ui/wildcard_enum_match_arm.fixed | 3 +- tests/ui/wildcard_enum_match_arm.rs | 3 +- tests/ui/wildcard_enum_match_arm.stderr | 10 +- 15 files changed, 1314 insertions(+), 20 deletions(-) create mode 100644 clippy_lints/src/unnested_or_patterns.rs create mode 100755 clippy_lints/src/utils/ast_utils.rs create mode 100644 tests/ui/unnested_or_patterns.fixed create mode 100644 tests/ui/unnested_or_patterns.rs create mode 100644 tests/ui/unnested_or_patterns.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index a4a184480fba9..adc945a69441d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1683,6 +1683,7 @@ Released 2018-09-13 [`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap [`unneeded_field_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_field_pattern [`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern +[`unnested_or_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns [`unreachable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreachable [`unreadable_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal [`unsafe_derive_deserialize`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_derive_deserialize diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 6f8923b266092..9809f953d6705 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1,5 +1,6 @@ // error-pattern:cargo-clippy +#![feature(bindings_after_at)] #![feature(box_syntax)] #![feature(box_patterns)] #![feature(or_patterns)] @@ -12,6 +13,7 @@ #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![feature(crate_visibility_modifier)] #![feature(concat_idents)] +#![feature(drain_filter)] // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) @@ -319,6 +321,7 @@ mod types; mod unicode; mod unnamed_address; mod unnecessary_sort_by; +mod unnested_or_patterns; mod unsafe_removed_from_name; mod unused_io_amount; mod unused_self; @@ -836,6 +839,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &unnamed_address::FN_ADDRESS_COMPARISONS, &unnamed_address::VTABLE_ADDRESS_COMPARISONS, &unnecessary_sort_by::UNNECESSARY_SORT_BY, + &unnested_or_patterns::UNNESTED_OR_PATTERNS, &unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME, &unused_io_amount::UNUSED_IO_AMOUNT, &unused_self::UNUSED_SELF, @@ -1073,6 +1077,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(move || box non_expressive_names::NonExpressiveNames { single_char_binding_names_threshold, }); + store.register_early_pass(|| box unnested_or_patterns::UnnestedOrPatterns); store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(&arithmetic::FLOAT_ARITHMETIC), @@ -1433,6 +1438,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS), LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS), LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), + LintId::of(&unnested_or_patterns::UNNESTED_OR_PATTERNS), LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), LintId::of(&unwrap::PANICKING_UNWRAP), @@ -1616,6 +1622,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::UNNECESSARY_CAST), LintId::of(&types::VEC_BOX), LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), + LintId::of(&unnested_or_patterns::UNNESTED_OR_PATTERNS), LintId::of(&unwrap::UNNECESSARY_UNWRAP), LintId::of(&useless_conversion::USELESS_CONVERSION), LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO), diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs new file mode 100644 index 0000000000000..2723af03c0b98 --- /dev/null +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -0,0 +1,407 @@ +#![allow(clippy::wildcard_imports, clippy::enum_glob_use)] + +use crate::utils::ast_utils::{eq_field_pat, eq_id, eq_pat, eq_path}; +use crate::utils::{over, span_lint_and_then}; +use rustc_ast::ast::{self, Pat, PatKind, PatKind::*, DUMMY_NODE_ID}; +use rustc_ast::mut_visit::*; +use rustc_ast::ptr::P; +use rustc_ast_pretty::pprust; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::DUMMY_SP; + +use std::cell::Cell; +use std::mem; + +declare_clippy_lint! { + /// **What it does:** + /// + /// Checks for unnested or-patterns, e.g., `Some(0) | Some(2)` and + /// suggests replacing the pattern with a nested one, `Some(0 | 2)`. + /// + /// Another way to think of this is that it rewrites patterns in + /// *disjunctive normal form (DNF)* into *conjunctive normal form (CNF)*. + /// + /// **Why is this bad?** + /// + /// In the example above, `Some` is repeated, which unncessarily complicates the pattern. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// fn main() { + /// if let Some(0) | Some(2) = Some(0) {} + /// } + /// ``` + /// Use instead: + /// ```rust + /// #![feature(or_patterns)] + /// + /// fn main() { + /// if let Some(0 | 2) = Some(0) {} + /// } + /// ``` + pub UNNESTED_OR_PATTERNS, + complexity, + "unnested or-patterns, e.g., `Foo(Bar) | Foo(Baz) instead of `Foo(Bar | Baz)`" +} + +declare_lint_pass!(UnnestedOrPatterns => [UNNESTED_OR_PATTERNS]); + +impl EarlyLintPass for UnnestedOrPatterns { + fn check_arm(&mut self, cx: &EarlyContext<'_>, a: &ast::Arm) { + lint_unnested_or_patterns(cx, &a.pat); + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + if let ast::ExprKind::Let(pat, _) = &e.kind { + lint_unnested_or_patterns(cx, pat); + } + } + + fn check_param(&mut self, cx: &EarlyContext<'_>, p: &ast::Param) { + lint_unnested_or_patterns(cx, &p.pat); + } + + fn check_local(&mut self, cx: &EarlyContext<'_>, l: &ast::Local) { + lint_unnested_or_patterns(cx, &l.pat); + } +} + +fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) { + if !cx.sess.opts.unstable_features.is_nightly_build() { + // User cannot do `#![feature(or_patterns)]`, so bail. + return; + } + + if let Ident(.., None) | Lit(_) | Wild | Path(..) | Range(..) | Rest | MacCall(_) = pat.kind { + // This is a leaf pattern, so cloning is unprofitable. + return; + } + + let mut pat = P(pat.clone()); + + // Nix all the paren patterns everywhere so that they aren't in our way. + remove_all_parens(&mut pat); + + // Transform all unnested or-patterns into nested ones, and if there were none, quit. + if !unnest_or_patterns(&mut pat) { + return; + } + + span_lint_and_then(cx, UNNESTED_OR_PATTERNS, pat.span, "unnested or-patterns", |db| { + insert_necessary_parens(&mut pat); + db.span_suggestion_verbose( + pat.span, + "nest the patterns", + pprust::pat_to_string(&pat), + Applicability::MachineApplicable, + ); + }); +} + +/// Remove all `(p)` patterns in `pat`. +fn remove_all_parens(pat: &mut P) { + struct Visitor; + impl MutVisitor for Visitor { + fn visit_pat(&mut self, pat: &mut P) { + noop_visit_pat(pat, self); + let inner = match &mut pat.kind { + Paren(i) => mem::replace(&mut i.kind, Wild), + _ => return, + }; + pat.kind = inner; + } + } + Visitor.visit_pat(pat); +} + +/// Insert parens where necessary according to Rust's precedence rules for patterns. +fn insert_necessary_parens(pat: &mut P) { + struct Visitor; + impl MutVisitor for Visitor { + fn visit_pat(&mut self, pat: &mut P) { + use ast::{BindingMode::*, Mutability::*}; + noop_visit_pat(pat, self); + let target = match &mut pat.kind { + // `i @ a | b`, `box a | b`, and `& mut? a | b`. + Ident(.., Some(p)) | Box(p) | Ref(p, _) if matches!(&p.kind, Or(ps) if ps.len() > 1) => p, + Ref(p, Not) if matches!(p.kind, Ident(ByValue(Mut), ..)) => p, // `&(mut x)` + _ => return, + }; + target.kind = Paren(P(take_pat(target))); + } + } + Visitor.visit_pat(pat); +} + +/// Unnest or-patterns `p0 | ... | p1` in the pattern `pat`. +/// For example, this would transform `Some(0) | FOO | Some(2)` into `Some(0 | 2) | FOO`. +fn unnest_or_patterns(pat: &mut P) -> bool { + struct Visitor { + changed: bool, + } + impl MutVisitor for Visitor { + fn visit_pat(&mut self, p: &mut P) { + // This is a bottom up transformation, so recurse first. + noop_visit_pat(p, self); + + // Don't have an or-pattern? Just quit early on. + let alternatives = match &mut p.kind { + Or(ps) => ps, + _ => return, + }; + + // Collapse or-patterns directly nested in or-patterns. + let mut idx = 0; + let mut this_level_changed = false; + while idx < alternatives.len() { + let inner = if let Or(ps) = &mut alternatives[idx].kind { + mem::take(ps) + } else { + idx += 1; + continue; + }; + this_level_changed = true; + alternatives.splice(idx..=idx, inner); + } + + // Focus on `p_n` and then try to transform all `p_i` where `i > n`. + let mut focus_idx = 0; + while focus_idx < alternatives.len() { + this_level_changed |= transform_with_focus_on_idx(alternatives, focus_idx); + focus_idx += 1; + } + self.changed |= this_level_changed; + + // Deal with `Some(Some(0)) | Some(Some(1))`. + if this_level_changed { + noop_visit_pat(p, self); + } + } + } + + let mut visitor = Visitor { changed: false }; + visitor.visit_pat(pat); + visitor.changed +} + +/// Match `$scrutinee` against `$pat` and extract `$then` from it. +/// Panics if there is no match. +macro_rules! always_pat { + ($scrutinee:expr, $pat:pat => $then:expr) => { + match $scrutinee { + $pat => $then, + _ => unreachable!(), + } + }; +} + +/// Focus on `focus_idx` in `alternatives`, +/// attempting to extend it with elements of the same constructor `C` +/// in `alternatives[focus_idx + 1..]`. +fn transform_with_focus_on_idx(alternatives: &mut Vec>, focus_idx: usize) -> bool { + // Extract the kind; we'll need to make some changes in it. + let mut focus_kind = mem::replace(&mut alternatives[focus_idx].kind, PatKind::Wild); + // We'll focus on `alternatives[focus_idx]`, + // so we're draining from `alternatives[focus_idx + 1..]`. + let start = focus_idx + 1; + + // We're trying to find whatever kind (~"constructor") we found in `alternatives[start..]`. + let changed = match &mut focus_kind { + // These pattern forms are "leafs" and do not have sub-patterns. + // Therefore they are not some form of constructor `C`, + // with which a pattern `C(P0)` may be formed, + // which we would want to join with other `C(Pj)`s. + Ident(.., None) | Lit(_) | Wild | Path(..) | Range(..) | Rest | MacCall(_) + // Dealt with elsewhere. + | Or(_) | Paren(_) => false, + // Transform `box x | ... | box y` into `box (x | y)`. + // + // The cases below until `Slice(...)` deal *singleton* products. + // These patterns have the shape `C(p)`, and not e.g., `C(p0, ..., pn)`. + Box(target) => extend_with_matching( + target, start, alternatives, + |k| matches!(k, Box(_)), + |k| always_pat!(k, Box(p) => p), + ), + // Transform `&m x | ... | &m y` into `&m (x, y)`. + Ref(target, m1) => extend_with_matching( + target, start, alternatives, + |k| matches!(k, Ref(_, m2) if m1 == m2), // Mutabilities must match. + |k| always_pat!(k, Ref(p, _) => p), + ), + // Transform `b @ p0 | ... b @ p1` into `b @ (p0 | p1)`. + Ident(b1, i1, Some(target)) => extend_with_matching( + target, start, alternatives, + // Binding names must match. + |k| matches!(k, Ident(b2, i2, Some(_)) if b1 == b2 && eq_id(*i1, *i2)), + |k| always_pat!(k, Ident(_, _, Some(p)) => p), + ), + // Transform `[pre, x, post] | ... | [pre, y, post]` into `[pre, x | y, post]`. + Slice(ps1) => extend_with_matching_product( + ps1, start, alternatives, + |k, ps1, idx| matches!(k, Slice(ps2) if eq_pre_post(ps1, ps2, idx)), + |k| always_pat!(k, Slice(ps) => ps), + ), + // Transform `(pre, x, post) | ... | (pre, y, post)` into `(pre, x | y, post]`. + Tuple(ps1) => extend_with_matching_product( + ps1, start, alternatives, + |k, ps1, idx| matches!(k, Tuple(ps2) if eq_pre_post(ps1, ps2, idx)), + |k| always_pat!(k, Tuple(ps) => ps), + ), + // Transform `S(pre, x, post) | ... | S(pre, y, post)` into `S(pre, x | y, post]`. + TupleStruct(path1, ps1) => extend_with_matching_product( + ps1, start, alternatives, + |k, ps1, idx| matches!( + k, + TupleStruct(path2, ps2) if eq_path(path1, path2) && eq_pre_post(ps1, ps2, idx) + ), + |k| always_pat!(k, TupleStruct(_, ps) => ps), + ), + // Transform a record pattern `S { fp_0, ..., fp_n }`. + Struct(path1, fps1, rest1) => extend_with_struct_pat(path1, fps1, *rest1, start, alternatives), + }; + + alternatives[focus_idx].kind = focus_kind; + changed +} + +/// Here we focusing on a record pattern `S { fp_0, ..., fp_n }`. +/// In particular, for a record pattern, the order in which the field patterns is irrelevant. +/// So when we fixate on some `ident_k: pat_k`, we try to find `ident_k` in the other pattern +/// and check that all `fp_i` where `i ∈ ((0...n) \ k)` between two patterns are equal. +fn extend_with_struct_pat( + path1: &ast::Path, + fps1: &mut Vec, + rest1: bool, + start: usize, + alternatives: &mut Vec>, +) -> bool { + (0..fps1.len()).any(|idx| { + let pos_in_2 = Cell::new(None); // The element `k`. + let tail_or = drain_matching( + start, + alternatives, + |k| { + matches!(k, Struct(path2, fps2, rest2) + if rest1 == *rest2 // If one struct pattern has `..` so must the other. + && eq_path(path1, path2) + && fps1.len() == fps2.len() + && fps1.iter().enumerate().all(|(idx_1, fp1)| { + if idx_1 == idx { + // In the case of `k`, we merely require identical field names + // so that we will transform into `ident_k: p1_k | p2_k`. + let pos = fps2.iter().position(|fp2| eq_id(fp1.ident, fp2.ident)); + pos_in_2.set(pos); + pos.is_some() + } else { + fps2.iter().any(|fp2| eq_field_pat(fp1, fp2)) + } + })) + }, + // Extract `p2_k`. + |k| always_pat!(k, Struct(_, mut fps, _) => fps.swap_remove(pos_in_2.take().unwrap()).pat), + ); + extend_with_tail_or(&mut fps1[idx].pat, tail_or) + }) +} + +/// Like `extend_with_matching` but for products with > 1 factor, e.g., `C(p_0, ..., p_n)`. +/// Here, the idea is that we fixate on some `p_k` in `C`, +/// allowing it to vary between two `targets` and `ps2` (returned by `extract`), +/// while also requiring `ps1[..n] ~ ps2[..n]` (pre) and `ps1[n + 1..] ~ ps2[n + 1..]` (post), +/// where `~` denotes semantic equality. +fn extend_with_matching_product( + targets: &mut Vec>, + start: usize, + alternatives: &mut Vec>, + predicate: impl Fn(&PatKind, &[P], usize) -> bool, + extract: impl Fn(PatKind) -> Vec>, +) -> bool { + (0..targets.len()).any(|idx| { + let tail_or = drain_matching( + start, + alternatives, + |k| predicate(k, targets, idx), + |k| extract(k).swap_remove(idx), + ); + extend_with_tail_or(&mut targets[idx], tail_or) + }) +} + +/// Extract the pattern from the given one and replace it with `Wild`. +/// This is meant for temporarily swapping out the pattern for manipulation. +fn take_pat(from: &mut Pat) -> Pat { + let dummy = Pat { + id: DUMMY_NODE_ID, + kind: Wild, + span: DUMMY_SP, + }; + mem::replace(from, dummy) +} + +/// Extend `target` as an or-pattern with the alternatives +/// in `tail_or` if there are any and return if there were. +fn extend_with_tail_or(target: &mut Pat, tail_or: Vec>) -> bool { + fn extend(target: &mut Pat, mut tail_or: Vec>) { + match target { + // On an existing or-pattern in the target, append to it. + Pat { kind: Or(ps), .. } => ps.append(&mut tail_or), + // Otherwise convert the target to an or-pattern. + target => { + let mut init_or = vec![P(take_pat(target))]; + init_or.append(&mut tail_or); + target.kind = Or(init_or); + }, + } + } + + let changed = !tail_or.is_empty(); + if changed { + // Extend the target. + extend(target, tail_or); + } + changed +} + +// Extract all inner patterns in `alternatives` matching our `predicate`. +// Only elements beginning with `start` are considered for extraction. +fn drain_matching( + start: usize, + alternatives: &mut Vec>, + predicate: impl Fn(&PatKind) -> bool, + extract: impl Fn(PatKind) -> P, +) -> Vec> { + let mut tail_or = vec![]; + let mut idx = 0; + for pat in alternatives.drain_filter(|p| { + // Check if we should extract, but only if `idx >= start`. + idx += 1; + idx > start && predicate(&p.kind) + }) { + tail_or.push(extract(pat.into_inner().kind)); + } + tail_or +} + +fn extend_with_matching( + target: &mut Pat, + start: usize, + alternatives: &mut Vec>, + predicate: impl Fn(&PatKind) -> bool, + extract: impl Fn(PatKind) -> P, +) -> bool { + extend_with_tail_or(target, drain_matching(start, alternatives, predicate, extract)) +} + +/// Are the patterns in `ps1` and `ps2` equal save for `ps1[idx]` compared to `ps2[idx]`? +fn eq_pre_post(ps1: &[P], ps2: &[P], idx: usize) -> bool { + ps1[idx].is_rest() == ps2[idx].is_rest() // Avoid `[x, ..] | [x, 0]` => `[x, .. | 0]`. + && ps1.len() == ps2.len() + && over(&ps1[..idx], &ps2[..idx], |l, r| eq_pat(l, r)) + && over(&ps1[idx + 1..], &ps2[idx + 1..], |l, r| eq_pat(l, r)) +} diff --git a/clippy_lints/src/utils/ast_utils.rs b/clippy_lints/src/utils/ast_utils.rs new file mode 100755 index 0000000000000..69a7b6c051e3b --- /dev/null +++ b/clippy_lints/src/utils/ast_utils.rs @@ -0,0 +1,525 @@ +//! Utilities for manipulating and extracting information from `rustc_ast::ast`. +//! +//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s. + +#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)] + +use crate::utils::{both, over}; +use rustc_ast::ast::{self, *}; +use rustc_ast::ptr::P; +use std::mem; + +/// Checks if each element in the first slice is contained within the latter as per `eq_fn`. +pub fn unordered_over(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool { + left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r))) +} + +pub fn eq_id(l: Ident, r: Ident) -> bool { + l.name == r.name +} + +pub fn eq_pat(l: &Pat, r: &Pat) -> bool { + use PatKind::*; + match (&l.kind, &r.kind) { + (Paren(l), _) => eq_pat(l, r), + (_, Paren(r)) => eq_pat(l, r), + (Wild, Wild) | (Rest, Rest) => true, + (Lit(l), Lit(r)) => eq_expr(l, r), + (Ident(b1, i1, s1), Ident(b2, i2, s2)) => b1 == b2 && eq_id(*i1, *i2) && both(s1, s2, |l, r| eq_pat(l, r)), + (Range(lf, lt, le), Range(rf, rt, re)) => { + eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt) && eq_range_end(&le.node, &re.node) + }, + (Box(l), Box(r)) + | (Ref(l, Mutability::Not), Ref(r, Mutability::Not)) + | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r), + (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)), + (Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp), + (TupleStruct(lp, lfs), TupleStruct(rp, rfs)) => eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r)), + (Struct(lp, lfs, lr), Struct(rp, rfs, rr)) => { + lr == rr && eq_path(lp, rp) && unordered_over(lfs, rfs, |lf, rf| eq_field_pat(lf, rf)) + }, + (Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)), + (MacCall(l), MacCall(r)) => eq_mac_call(l, r), + _ => false, + } +} + +pub fn eq_range_end(l: &RangeEnd, r: &RangeEnd) -> bool { + match (l, r) { + (RangeEnd::Excluded, RangeEnd::Excluded) => true, + (RangeEnd::Included(l), RangeEnd::Included(r)) => { + matches!(l, RangeSyntax::DotDotEq) == matches!(r, RangeSyntax::DotDotEq) + }, + _ => false, + } +} + +pub fn eq_field_pat(l: &FieldPat, r: &FieldPat) -> bool { + l.is_placeholder == r.is_placeholder + && eq_id(l.ident, r.ident) + && eq_pat(&l.pat, &r.pat) + && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) +} + +pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool { + l.position == r.position && eq_ty(&l.ty, &r.ty) +} + +pub fn eq_path(l: &Path, r: &Path) -> bool { + over(&l.segments, &r.segments, |l, r| eq_path_seg(l, r)) +} + +pub fn eq_path_seg(l: &PathSegment, r: &PathSegment) -> bool { + eq_id(l.ident, r.ident) && both(&l.args, &r.args, |l, r| eq_generic_args(l, r)) +} + +pub fn eq_generic_args(l: &GenericArgs, r: &GenericArgs) -> bool { + match (l, r) { + (GenericArgs::AngleBracketed(l), GenericArgs::AngleBracketed(r)) => { + over(&l.args, &r.args, |l, r| eq_angle_arg(l, r)) + }, + (GenericArgs::Parenthesized(l), GenericArgs::Parenthesized(r)) => { + over(&l.inputs, &r.inputs, |l, r| eq_ty(l, r)) && eq_fn_ret_ty(&l.output, &r.output) + }, + _ => false, + } +} + +pub fn eq_angle_arg(l: &AngleBracketedArg, r: &AngleBracketedArg) -> bool { + match (l, r) { + (AngleBracketedArg::Arg(l), AngleBracketedArg::Arg(r)) => eq_generic_arg(l, r), + (AngleBracketedArg::Constraint(l), AngleBracketedArg::Constraint(r)) => eq_assoc_constraint(l, r), + _ => false, + } +} + +pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool { + match (l, r) { + (GenericArg::Lifetime(l), GenericArg::Lifetime(r)) => eq_id(l.ident, r.ident), + (GenericArg::Type(l), GenericArg::Type(r)) => eq_ty(l, r), + (GenericArg::Const(l), GenericArg::Const(r)) => eq_expr(&l.value, &r.value), + _ => false, + } +} + +pub fn eq_expr_opt(l: &Option>, r: &Option>) -> bool { + both(l, r, |l, r| eq_expr(l, r)) +} + +pub fn eq_expr(l: &Expr, r: &Expr) -> bool { + use ExprKind::*; + if !over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) { + return false; + } + match (&l.kind, &r.kind) { + (Paren(l), _) => eq_expr(l, r), + (_, Paren(r)) => eq_expr(l, r), + (Err, Err) => true, + (Box(l), Box(r)) | (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r), + (Array(l), Array(r)) | (Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)), + (Repeat(le, ls), Repeat(re, rs)) => eq_expr(le, re) && eq_expr(&ls.value, &rs.value), + (Call(lc, la), Call(rc, ra)) => eq_expr(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)), + (MethodCall(lc, la), MethodCall(rc, ra)) => eq_path_seg(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)), + (Binary(lo, ll, lr), Binary(ro, rl, rr)) => lo.node == ro.node && eq_expr(ll, rl) && eq_expr(lr, rr), + (Unary(lo, l), Unary(ro, r)) => mem::discriminant(lo) == mem::discriminant(ro) && eq_expr(l, r), + (Lit(l), Lit(r)) => l.kind == r.kind, + (Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt), + (Let(lp, le), Let(rp, re)) => eq_pat(lp, rp) && eq_expr(le, re), + (If(lc, lt, le), If(rc, rt, re)) => eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le, re), + (While(lc, lt, ll), While(rc, rt, rl)) => eq_label(ll, rl) && eq_expr(lc, rc) && eq_block(lt, rt), + (ForLoop(lp, li, lt, ll), ForLoop(rp, ri, rt, rl)) => { + eq_label(ll, rl) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt) + }, + (Loop(lt, ll), Loop(rt, rl)) => eq_label(ll, rl) && eq_block(lt, rt), + (Block(lb, ll), Block(rb, rl)) => eq_label(ll, rl) && eq_block(lb, rb), + (TryBlock(l), TryBlock(r)) => eq_block(l, r), + (Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l, r), + (Break(ll, le), Break(rl, re)) => eq_label(ll, rl) && eq_expr_opt(le, re), + (Continue(ll), Continue(rl)) => eq_label(ll, rl), + (Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2), Index(r1, r2)) => eq_expr(l1, r1) && eq_expr(l2, r2), + (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, |l, r| eq_arm(l, r)), + (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) + }, + (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), + (AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re), + (Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp), + (MacCall(l), MacCall(r)) => eq_mac_call(l, r), + (Struct(lp, lfs, lb), Struct(rp, rfs, rb)) => { + eq_path(lp, rp) && eq_expr_opt(lb, rb) && unordered_over(lfs, rfs, |l, r| eq_field(l, r)) + }, + _ => false, + } +} + +pub fn eq_field(l: &Field, r: &Field) -> bool { + l.is_placeholder == r.is_placeholder + && eq_id(l.ident, r.ident) + && eq_expr(&l.expr, &r.expr) + && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) +} + +pub fn eq_arm(l: &Arm, r: &Arm) -> bool { + l.is_placeholder == r.is_placeholder + && eq_pat(&l.pat, &r.pat) + && eq_expr(&l.body, &r.body) + && eq_expr_opt(&l.guard, &r.guard) + && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) +} + +pub fn eq_label(l: &Option