Merge commit 'ff0993c5e9162ddaea78e83d0f0161e68bd4ea73' into clippy

This commit is contained in:
Lzu Tao 2020-06-09 14:36:01 +00:00
parent 161474b7f7
commit 8db24840f7
143 changed files with 3965 additions and 936 deletions

View File

@ -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'

View File

@ -6,11 +6,88 @@ 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 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)
### 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)
* 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`]
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
* 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 ICE 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 +170,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)
@ -1401,6 +1478,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
@ -1601,9 +1679,11 @@ 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
[`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
@ -1630,6 +1710,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

View File

@ -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 <repo-path>` where `<repo-path>` 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`].

View File

@ -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"

View File

@ -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);
}

View File

@ -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")),
_ => {},
}
}

View File

@ -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
)

View File

@ -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(())
}

View File

@ -17,11 +17,11 @@ 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"
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"] }

View File

@ -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,

View File

@ -54,18 +54,13 @@ declare_lint_pass!(AwaitHoldingLock => [AWAIT_HOLDING_LOCK]);
impl LateLintPass<'_, '_> for AwaitHoldingLock {
fn check_body(&mut self, cx: &LateContext<'_, '_>, body: &'_ Body<'_>) {
use AsyncGeneratorKind::{Block, Closure, Fn};
match body.generator_kind {
Some(GeneratorKind::Async(Block))
| Some(GeneratorKind::Async(Closure))
| Some(GeneratorKind::Async(Fn)) => {
if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
let body_id = BodyId {
hir_id: body.value.hir_id,
};
let def_id = cx.tcx.hir().body_owner_def_id(body_id);
let tables = cx.tcx.typeck_tables_of(def_id);
check_interior_types(cx, &tables.generator_interior_types, body.value.span);
},
_ => {},
}
}
}

View File

@ -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<String>) -> 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) {

View File

@ -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<Conversion<'tcx>> {
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<Conversion<'a>> {
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;
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);
then {
Some((from_type, to_type))
} else {
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));
}
},
_ => {},
}
};
None
}
}
} else {
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"];

View File

@ -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<Symbol, Ty<'tcx>>,
rhs: &FxHashMap<Symbol, Ty<'tcx>>,
) -> bool {
fn same_bindings<'tcx>(lhs: &FxHashMap<Symbol, Ty<'tcx>>, rhs: &FxHashMap<Symbol, Ty<'tcx>>) -> 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();

View File

@ -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,

View File

@ -27,6 +27,10 @@ declare_clippy_lint! {
/// ```rust
/// fn foo<T: Drop>() {}
/// ```
/// Could be written as:
/// ```rust
/// fn foo<T>() {}
/// ```
pub DROP_BOUNDS,
correctness,
"Bounds of the form `T: Drop` are useless"

View File

@ -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,

View File

@ -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"

View File

@ -39,7 +39,11 @@ declare_clippy_lint! {
///
/// **Example:**
/// ```ignore
/// // Bad
/// &x == y
///
/// // Good
/// x == *y
/// ```
pub OP_REF,
style,

View File

@ -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,

View File

@ -26,7 +26,11 @@ declare_clippy_lint! {
///
/// **Example:**
/// ```rust,ignore
/// // Bad
/// xs.map(|x| foo(x))
///
/// // Good
/// xs.map(foo)
/// ```
/// where `foo(_)` is a plain function that takes the exact argument type of
/// `x`.

View File

@ -21,11 +21,20 @@ declare_clippy_lint! {
/// **Example:**
/// ```rust
/// let mut x = 0;
///
/// // Bad
/// let a = {
/// x = 1;
/// 1
/// } + x;
/// // Unclear whether a is 1 or 2.
///
/// // Good
/// let tmp = {
/// x = 1;
/// 1
/// };
/// let a = tmp + x;
/// ```
pub EVAL_ORDER_DEPENDENCE,
complexity,

View File

@ -20,12 +20,31 @@ declare_clippy_lint! {
/// **Example:**
/// ```rust
/// struct Foo(i32);
///
/// // Bad
/// impl From<String> for Foo {
/// fn from(s: String) -> Self {
/// Foo(s.parse().unwrap())
/// }
/// }
/// ```
///
/// ```rust
/// // Good
/// struct Foo(i32);
///
/// use std::convert::TryFrom;
/// impl TryFrom<String> for Foo {
/// type Error = ();
/// fn try_from(s: String) -> Result<Self, Self::Error> {
/// if let Ok(parsed) = s.parse() {
/// Ok(Foo(parsed))
/// } else {
/// Err(())
/// }
/// }
/// }
/// ```
pub FALLIBLE_IMPL_FROM,
nursery,
"Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`"

View File

@ -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();

View File

@ -25,9 +25,13 @@ declare_clippy_lint! {
///
/// **Examples:**
/// ```rust
///
/// // Bad
/// # let foo = "foo";
/// format!("foo");
/// format!("{}", foo);
///
/// // Good
/// format!("foo");
/// ```
pub USELESS_FORMAT,
complexity,

View File

@ -112,12 +112,8 @@ declare_lint_pass!(Formatting => [
impl EarlyLintPass for Formatting {
fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
for w in block.stmts.windows(2) {
match (&w[0].kind, &w[1].kind) {
(&StmtKind::Expr(ref first), &StmtKind::Expr(ref second))
| (&StmtKind::Expr(ref first), &StmtKind::Semi(ref second)) => {
if let (StmtKind::Expr(first), StmtKind::Expr(second) | StmtKind::Semi(second)) = (&w[0].kind, &w[1].kind) {
check_missing_else(cx, first, second);
},
_ => (),
}
}
}

View File

@ -49,7 +49,7 @@ declare_clippy_lint! {
/// **Known problems:** None.
///
/// **Example:**
/// ``` rust
/// ```rust
/// fn im_too_long() {
/// println!("");
/// // ... 100 more LoC
@ -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,

View File

@ -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);

View File

@ -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.

View File

@ -15,10 +15,13 @@ declare_clippy_lint! {
///
/// **Example:**
/// ```rust
/// fn main() {
/// // Bad
/// let x = 3 / 2;
/// println!("{}", x);
/// }
///
/// // Good
/// let x = 3f32 / 2f32;
/// println!("{}", x);
/// ```
pub INTEGER_DIVISION,
restriction,

View File

@ -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"

View File

@ -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, ..) => {

View File

@ -0,0 +1,141 @@
use if_chain::if_chain;
use rustc_errors::Applicability;
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};
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 !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);
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");
}
},
);
}
}
}
}
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<DefId> {
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<Self::Map> {
NestedVisitorMap::None
}
}

View File

@ -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`.)
@ -239,6 +241,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;
@ -318,6 +321,8 @@ mod try_err;
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;
@ -325,6 +330,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;
@ -594,6 +600,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,
@ -664,6 +671,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,
@ -769,7 +777,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&regex::INVALID_REGEX,
&regex::REGEX_MACRO,
&regex::TRIVIAL_REGEX,
&returns::LET_AND_RETURN,
&returns::NEEDLESS_RETURN,
&returns::UNUSED_UNIT,
&serde_api::SERDE_API_MISUSE,
@ -832,6 +839,8 @@ 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,
&unnested_or_patterns::UNNESTED_OR_PATTERNS,
&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
&unused_io_amount::UNUSED_IO_AMOUNT,
&unused_self::UNUSED_SELF,
@ -847,6 +856,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,
@ -994,6 +1004,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 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);
@ -1016,6 +1027,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);
@ -1062,10 +1074,12 @@ 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,
});
store.register_early_pass(|| box unnested_or_patterns::UnnestedOrPatterns);
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@ -1164,6 +1178,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),
@ -1257,6 +1272,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),
@ -1303,6 +1319,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),
@ -1381,7 +1398,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&regex::INVALID_REGEX),
LintId::of(&regex::REGEX_MACRO),
LintId::of(&regex::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),
@ -1410,7 +1426,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),
@ -1424,12 +1439,15 @@ 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(&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),
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),
@ -1464,6 +1482,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),
@ -1483,6 +1502,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),
@ -1515,7 +1535,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
LintId::of(&regex::REGEX_MACRO),
LintId::of(&regex::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),
@ -1604,6 +1623,8 @@ 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(&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),
@ -1669,7 +1690,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),
@ -1677,6 +1697,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![

View File

@ -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,

View File

@ -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,10 +24,10 @@ 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::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;
@ -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)
}
}
@ -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!()
}

View File

@ -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,14 @@ 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 +299,17 @@ declare_clippy_lint! {
///
/// **Example:**
/// ```rust
/// // Bad
/// match "foo" {
/// "a" => {},
/// "bar" | _ => {},
/// }
///
/// // Good
/// match "foo" {
/// "a" => {},
/// _ => {},
/// }
/// ```
pub WILDCARD_IN_OR_PATTERNS,
complexity,

View File

@ -57,7 +57,7 @@ pub fn lint(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, args: &[&[hir::Expr<
);
} else {
match (mm, arith) {
(MinMax::Max, "add") | (MinMax::Max, "mul") | (MinMax::Min, "sub") => (),
(MinMax::Max, "add" | "mul") | (MinMax::Min, "sub") => (),
_ => return,
}

View File

@ -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};
@ -26,12 +26,12 @@ 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,
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! {
@ -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,16 @@ 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| if *x == 0 {
/// Some(*x * 2)
/// } else {
/// None
/// });
/// ```
pub FILTER_MAP,
pedantic,
@ -634,7 +657,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 +769,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 +997,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 +1008,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 +1081,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 +1110,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,
@ -1242,6 +1281,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 +1338,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 +1386,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]),
@ -1336,9 +1403,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
lint_search_is_some(cx, expr, "rposition", arg_lists[1], arg_lists[0], method_spans[1])
},
["extend", ..] => lint_extend(cx, expr, arg_lists[0]),
["as_ptr", "unwrap"] | ["as_ptr", "expect"] => {
lint_cstring_as_ptr(cx, expr, &arg_lists[1][0], &arg_lists[0][0])
},
["as_ptr", "unwrap" | "expect"] => lint_cstring_as_ptr(cx, expr, &arg_lists[1][0], &arg_lists[0][0]),
["nth", "iter"] => lint_iter_nth(cx, expr, &arg_lists, false),
["nth", "iter_mut"] => lint_iter_nth(cx, expr, &arg_lists, true),
["nth", ..] => lint_iter_nth_zero(cx, expr, arg_lists[0]),
@ -1351,12 +1416,10 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
["filter_map", ..] => unnecessary_filter_map::lint(cx, expr, arg_lists[0]),
["count", "map"] => lint_suspicious_map(cx, expr),
["assume_init"] => lint_maybe_uninit(cx, &arg_lists[0][0], expr),
["unwrap_or", arith @ "checked_add"]
| ["unwrap_or", arith @ "checked_sub"]
| ["unwrap_or", arith @ "checked_mul"] => {
["unwrap_or", arith @ ("checked_add" | "checked_sub" | "checked_mul")] => {
manual_saturating_arithmetic::lint(cx, expr, &arg_lists, &arith["checked_".len()..])
},
["add"] | ["offset"] | ["sub"] | ["wrapping_offset"] | ["wrapping_add"] | ["wrapping_sub"] => {
["add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub"] => {
check_pointer_offset(cx, expr, arg_lists[0])
},
["is_file", ..] => lint_filetype_is_file(cx, expr, arg_lists[0]),
@ -1481,7 +1544,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,
})
@ -1508,7 +1571,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,
@ -1762,8 +1825,7 @@ fn lint_expect_fun_call(
hir::ExprKind::Call(fun, _) => {
if let hir::ExprKind::Path(ref p) = fun.kind {
match cx.tables.qpath_res(p, fun.hir_id) {
hir::def::Res::Def(hir::def::DefKind::Fn, def_id)
| hir::def::Res::Def(hir::def::DefKind::AssocFn, def_id) => matches!(
hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!(
cx.tcx.fn_sig(def_id).output().skip_binder().kind,
ty::Ref(ty::ReStatic, ..)
),
@ -2199,6 +2261,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<'_>,

View File

@ -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::<u32>();
/// ```
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,
@ -248,17 +275,14 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MiscLints {
return;
}
for arg in iter_input_pats(decl, body) {
match arg.pat.kind {
PatKind::Binding(BindingAnnotation::Ref, ..) | PatKind::Binding(BindingAnnotation::RefMut, ..) => {
if let PatKind::Binding(BindingAnnotation::Ref | BindingAnnotation::RefMut, ..) = arg.pat.kind {
span_lint(
cx,
TOPLEVEL_REF_ARG,
arg.pat.span,
"`ref` directly on a function argument is ignored. Consider using a reference type \
instead.",
"`ref` directly on a function argument is ignored. \
Consider using a reference type instead.",
);
},
_ => {},
}
}
}

View File

@ -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 @ _ => (), // easier written as `y`,
/// y @ _ => (),
/// }
///
/// // Good
/// match v {
/// Some(x) => (),
/// 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, ..) => (),
/// _ => (),

View File

@ -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));

View File

@ -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,

View File

@ -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,

View File

@ -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.
///

View File

@ -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"

View File

@ -424,7 +424,7 @@ fn erode_from_back(s: &str) -> String {
}
fn span_of_first_expr_in_block(block: &ast::Block) -> Option<Span> {
block.stmts.iter().next().map(|stmt| stmt.span)
block.stmts.get(0).map(|stmt| stmt.span)
}
#[cfg(test)]

View File

@ -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,

View File

@ -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() {

View File

@ -147,7 +147,7 @@ fn reduce_expression<'a>(cx: &LateContext<'_, '_>, expr: &'a Expr<'a>) -> Option
if let ExprKind::Path(ref qpath) = callee.kind {
let res = qpath_res(cx, qpath, callee.hir_id);
match res {
Res::Def(DefKind::Struct, ..) | Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(..), _)
Res::Def(DefKind::Struct | DefKind::Variant | DefKind::Ctor(..), ..)
if !has_drop(cx, cx.tables.expr_ty(expr)) =>
{
Some(args.iter().collect())

View File

@ -47,7 +47,11 @@ declare_clippy_lint! {
///
/// **Example:**
/// ```ignore
/// // Bad
/// fn foo(&Vec<u32>) { .. }
///
/// // 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

View File

@ -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(cx: &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(
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,
&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
"this range is reversed and using it to index a slice will panic at run-time",
);
}
}
}
);
} else {
// ... 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,

View File

@ -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,

View File

@ -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,
span_lint(
cx,
REGEX_MACRO,
span,
"`regex!(_)` found. \
Please use `Regex::new(_)`, which is faster for now.");
Please use `Regex::new(_)`, which is faster for now."
);
self.spans.insert(span);
}
self.last = Some(block.hir_id);

View File

@ -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;

View File

@ -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,

View File

@ -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();
/// }

View File

@ -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,

View File

@ -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,

View File

@ -71,8 +71,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for SuspiciousImpl {
if let hir::Node::Expr(e) = cx.tcx.hir().get(parent_expr) {
match e.kind {
hir::ExprKind::Binary(..)
| hir::ExprKind::Unary(hir::UnOp::UnNot, _)
| hir::ExprKind::Unary(hir::UnOp::UnNeg, _)
| hir::ExprKind::Unary(hir::UnOp::UnNot | hir::UnOp::UnNeg, _)
| hir::ExprKind::AssignOp(..) => return,
_ => {},
}
@ -191,8 +190,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BinaryExprVisitor {
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
match expr.kind {
hir::ExprKind::Binary(..)
| hir::ExprKind::Unary(hir::UnOp::UnNot, _)
| hir::ExprKind::Unary(hir::UnOp::UnNeg, _)
| hir::ExprKind::Unary(hir::UnOp::UnNot | hir::UnOp::UnNeg, _)
| hir::ExprKind::AssignOp(..) => self.in_binary_expr = true,
_ => {},
}

View File

@ -312,7 +312,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
e.span,
&format!("transmute from a type (`{}`) to itself", from_ty),
),
(&ty::Ref(_, rty, rty_mutbl), &ty::RawPtr(ptr_ty)) => span_lint_and_then(
(ty::Ref(_, rty, rty_mutbl), ty::RawPtr(ptr_ty)) => span_lint_and_then(
cx,
USELESS_TRANSMUTE,
e.span,
@ -321,10 +321,10 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
let rty_and_mut = ty::TypeAndMut {
ty: rty,
mutbl: rty_mutbl,
mutbl: *rty_mutbl,
};
let sugg = if ptr_ty == rty_and_mut {
let sugg = if *ptr_ty == rty_and_mut {
arg.as_ty(to_ty)
} else {
arg.as_ty(cx.tcx.mk_ptr(rty_and_mut)).as_ty(to_ty)
@ -334,7 +334,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
}
},
),
(&ty::Int(_), &ty::RawPtr(_)) | (&ty::Uint(_), &ty::RawPtr(_)) => span_lint_and_then(
(ty::Int(_) | ty::Uint(_), ty::RawPtr(_)) => span_lint_and_then(
cx,
USELESS_TRANSMUTE,
e.span,
@ -350,16 +350,13 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
}
},
),
(&ty::Float(_), &ty::Ref(..))
| (&ty::Float(_), &ty::RawPtr(_))
| (&ty::Char, &ty::Ref(..))
| (&ty::Char, &ty::RawPtr(_)) => span_lint(
(ty::Float(_) | ty::Char, ty::Ref(..) | ty::RawPtr(_)) => span_lint(
cx,
WRONG_TRANSMUTE,
e.span,
&format!("transmute from a `{}` to a pointer", from_ty),
),
(&ty::RawPtr(from_ptr), _) if from_ptr.ty == to_ty => span_lint(
(ty::RawPtr(from_ptr), _) if from_ptr.ty == to_ty => span_lint(
cx,
CROSSPOINTER_TRANSMUTE,
e.span,
@ -368,7 +365,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
from_ty, to_ty
),
),
(_, &ty::RawPtr(to_ptr)) if to_ptr.ty == from_ty => span_lint(
(_, ty::RawPtr(to_ptr)) if to_ptr.ty == from_ty => span_lint(
cx,
CROSSPOINTER_TRANSMUTE,
e.span,
@ -377,7 +374,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
from_ty, to_ty
),
),
(&ty::RawPtr(from_pty), &ty::Ref(_, to_ref_ty, mutbl)) => span_lint_and_then(
(ty::RawPtr(from_pty), ty::Ref(_, to_ref_ty, mutbl)) => span_lint_and_then(
cx,
TRANSMUTE_PTR_TO_REF,
e.span,
@ -388,13 +385,13 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
),
|diag| {
let arg = sugg::Sugg::hir(cx, &args[0], "..");
let (deref, cast) = if mutbl == Mutability::Mut {
let (deref, cast) = if *mutbl == Mutability::Mut {
("&mut *", "*mut")
} else {
("&*", "*const")
};
let arg = if from_pty.ty == to_ref_ty {
let arg = if from_pty.ty == *to_ref_ty {
arg
} else {
arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, to_ref_ty)))
@ -408,7 +405,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
);
},
),
(&ty::Int(ast::IntTy::I32), &ty::Char) | (&ty::Uint(ast::UintTy::U32), &ty::Char) => {
(ty::Int(ast::IntTy::I32) | ty::Uint(ast::UintTy::U32), &ty::Char) => {
span_lint_and_then(
cx,
TRANSMUTE_INT_TO_CHAR,
@ -430,13 +427,13 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
},
)
},
(&ty::Ref(_, ty_from, from_mutbl), &ty::Ref(_, ty_to, to_mutbl)) => {
(ty::Ref(_, ty_from, from_mutbl), ty::Ref(_, ty_to, to_mutbl)) => {
if_chain! {
if let (&ty::Slice(slice_ty), &ty::Str) = (&ty_from.kind, &ty_to.kind);
if let ty::Uint(ast::UintTy::U8) = slice_ty.kind;
if from_mutbl == to_mutbl;
then {
let postfix = if from_mutbl == Mutability::Mut {
let postfix = if *from_mutbl == Mutability::Mut {
"_mut"
} else {
""
@ -465,13 +462,13 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
|diag| if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
let ty_from_and_mut = ty::TypeAndMut {
ty: ty_from,
mutbl: from_mutbl
mutbl: *from_mutbl
};
let ty_to_and_mut = ty::TypeAndMut { ty: ty_to, mutbl: to_mutbl };
let ty_to_and_mut = ty::TypeAndMut { ty: ty_to, mutbl: *to_mutbl };
let sugg_paren = arg
.as_ty(cx.tcx.mk_ptr(ty_from_and_mut))
.as_ty(cx.tcx.mk_ptr(ty_to_and_mut));
let sugg = if to_mutbl == Mutability::Mut {
let sugg = if *to_mutbl == Mutability::Mut {
sugg_paren.mut_addr_deref()
} else {
sugg_paren.addr_deref()
@ -488,19 +485,19 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
}
}
},
(&ty::RawPtr(_), &ty::RawPtr(to_ty)) => span_lint_and_then(
(ty::RawPtr(_), ty::RawPtr(to_ty)) => span_lint_and_then(
cx,
TRANSMUTE_PTR_TO_PTR,
e.span,
"transmute from a pointer to a pointer",
|diag| {
if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
let sugg = arg.as_ty(cx.tcx.mk_ptr(to_ty));
let sugg = arg.as_ty(cx.tcx.mk_ptr(*to_ty));
diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified);
}
},
),
(&ty::Int(ast::IntTy::I8), &ty::Bool) | (&ty::Uint(ast::UintTy::U8), &ty::Bool) => {
(ty::Int(ast::IntTy::I8) | ty::Uint(ast::UintTy::U8), ty::Bool) => {
span_lint_and_then(
cx,
TRANSMUTE_INT_TO_BOOL,
@ -518,7 +515,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
},
)
},
(&ty::Int(_), &ty::Float(_)) | (&ty::Uint(_), &ty::Float(_)) => span_lint_and_then(
(ty::Int(_) | ty::Uint(_), ty::Float(_)) => span_lint_and_then(
cx,
TRANSMUTE_INT_TO_FLOAT,
e.span,
@ -541,7 +538,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
);
},
),
(&ty::Float(float_ty), &ty::Int(_)) | (&ty::Float(float_ty), &ty::Uint(_)) => span_lint_and_then(
(ty::Float(float_ty), ty::Int(_) | ty::Uint(_)) => span_lint_and_then(
cx,
TRANSMUTE_FLOAT_TO_INT,
e.span,
@ -585,7 +582,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
);
},
),
(&ty::Adt(ref from_adt, ref from_substs), &ty::Adt(ref to_adt, ref to_substs)) => {
(ty::Adt(from_adt, from_substs), ty::Adt(to_adt, to_substs)) => {
if from_adt.did != to_adt.did ||
!COLLECTIONS.iter().any(|path| match_def_path(cx, to_adt.did, path)) {
return;

View File

@ -10,14 +10,14 @@ 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,
};
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;
@ -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, 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! {
@ -779,24 +779,22 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitArg {
match expr.kind {
ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args) => {
for arg in args {
let args_to_recover = args
.iter()
.filter(|arg| {
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;
if let ExprKind::Match(.., MatchSource::TryDesugar) = &arg.kind {
false
} else {
true
}
} else {
false
}
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,
);
}
})
.collect::<Vec<_>>();
if !args_to_recover.is_empty() {
lint_unit_args(cx, expr, &args_to_recover);
}
},
_ => (),
@ -804,6 +802,101 @@ 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()
.filter(|arg| !is_empty_block(arg))
.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::<Vec<String>>();
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!("{}use {}unit literal{} instead", and, singular, plural),
args_to_recover
.iter()
.map(|arg| (arg.span, "()".to_string()))
.collect::<Vec<_>>(),
applicability,
);
},
);
}
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 {
@ -974,7 +1067,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 +1076,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"
}
@ -2462,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;
}

View File

@ -0,0 +1,267 @@
use crate::utils;
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, PathSegment, QPath};
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:**
/// Detects when people use `Vec::sort_by` and pass in a function
/// 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` if
/// possible) than to use `Vec::sort_by` and and a more complicated
/// closure.
///
/// **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
/// # struct A;
/// # impl A { fn foo(&self) {} }
/// # let mut vec: Vec<A> = 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<A> = Vec::new();
/// vec.sort_by_key(|a| a.foo());
/// ```
pub UNNECESSARY_SORT_BY,
complexity,
"Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer"
}
declare_lint_pass!(UnnecessarySortBy => [UNNECESSARY_SORT_BY]);
enum LintTrigger {
Sort(SortDetection),
SortByKey(SortByKeyDetection),
}
struct SortDetection {
vec_name: String,
unstable: bool,
}
struct SortByKeyDetection {
vec_name: String,
closure_arg: String,
closure_body: String,
reverse: bool,
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, _), 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)
},
// 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,
}
}
fn detect_lint(cx: &LateContext<'_, '_>, expr: &Expr<'_>) -> Option<LintTrigger> {
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_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(_, _, left_ident, _), .. }, ..},
Param { pat: Pat { kind: PatKind::Binding(_, _, right_ident, _), .. }, .. }
] = &closure_body.params;
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, 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(), false)
} else if mirrored_exprs(&cx, &left_expr, &right_ident, &right_expr, &left_ident) {
(Sugg::hir(cx, &left_expr, "..").to_string(), right_ident.name.to_string(), true)
} else {
return None;
};
let vec_name = Sugg::hir(cx, &args[0], "..").to_string();
let unstable = name == "sort_unstable_by";
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,
reverse
}))
}
}
} else {
None
}
}
}
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,
UNNECESSARY_SORT_BY,
expr.span,
"use Vec::sort_by_key here instead",
"try",
format!(
"{}.sort{}_by_key(|&{}| {})",
trigger.vec_name,
if trigger.unstable { "_unstable" } else { "" },
trigger.closure_arg,
if trigger.reverse {
format!("Reverse({})", trigger.closure_body)
} else {
trigger.closure_body.to_string()
},
),
if trigger.reverse {
Applicability::MaybeIncorrect
} else {
Applicability::MachineApplicable
},
),
Some(LintTrigger::Sort(trigger)) => utils::span_lint_and_sugg(
cx,
UNNECESSARY_SORT_BY,
expr.span,
"use Vec::sort here instead",
"try",
format!(
"{}.sort{}()",
trigger.vec_name,
if trigger.unstable { "_unstable" } else { "" },
),
Applicability::MachineApplicable,
),
None => {},
}
}
}

View File

@ -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<Pat>) {
struct Visitor;
impl MutVisitor for Visitor {
fn visit_pat(&mut self, pat: &mut P<Pat>) {
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<Pat>) {
struct Visitor;
impl MutVisitor for Visitor {
fn visit_pat(&mut self, pat: &mut P<Pat>) {
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<Pat>) -> bool {
struct Visitor {
changed: bool,
}
impl MutVisitor for Visitor {
fn visit_pat(&mut self, p: &mut P<Pat>) {
// 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<P<Pat>>, 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(p_0)` may be formed,
// which we would want to join with other `C(p_j)`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 with *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<ast::FieldPat>,
rest1: bool,
start: usize,
alternatives: &mut Vec<P<Pat>>,
) -> 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<P<Pat>>,
start: usize,
alternatives: &mut Vec<P<Pat>>,
predicate: impl Fn(&PatKind, &[P<Pat>], usize) -> bool,
extract: impl Fn(PatKind) -> Vec<P<Pat>>,
) -> 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<P<Pat>>) -> bool {
fn extend(target: &mut Pat, mut tail_or: Vec<P<Pat>>) {
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<P<Pat>>,
predicate: impl Fn(&PatKind) -> bool,
extract: impl Fn(PatKind) -> P<Pat>,
) -> Vec<P<Pat>> {
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<P<Pat>>,
predicate: impl Fn(&PatKind) -> bool,
extract: impl Fn(PatKind) -> P<Pat>,
) -> 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<Pat>], ps2: &[P<Pat>], 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))
}

View File

@ -101,7 +101,7 @@ fn collect_unwrap_info<'a, 'tcx>(
if let ExprKind::Binary(op, left, right) = &expr.kind {
match (invert, op.node) {
(false, BinOpKind::And) | (false, BinOpKind::BitAnd) | (true, BinOpKind::Or) | (true, BinOpKind::BitOr) => {
(false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr) => {
let mut unwrap_info = collect_unwrap_info(cx, left, branch, invert);
unwrap_info.append(&mut collect_unwrap_info(cx, right, branch, invert));
return unwrap_info;

View File

@ -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, "<expr>").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, "<expr>").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(), "<expr>").into_owned();

View File

@ -0,0 +1,526 @@
//! 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 rustc_span::symbol::Ident;
use std::mem;
/// Checks if each element in the first slice is contained within the latter as per `eq_fn`.
pub fn unordered_over<X>(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<P<Expr>>, r: &Option<P<Expr>>) -> 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<Label>, r: &Option<Label>) -> bool {
both(l, r, |l, r| eq_id(l.ident, r.ident))
}
pub fn eq_block(l: &Block, r: &Block) -> bool {
l.rules == r.rules && over(&l.stmts, &r.stmts, |l, r| eq_stmt(l, r))
}
pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool {
use StmtKind::*;
match (&l.kind, &r.kind) {
(Local(l), Local(r)) => {
eq_pat(&l.pat, &r.pat)
&& both(&l.ty, &r.ty, |l, r| eq_ty(l, r))
&& eq_expr_opt(&l.init, &r.init)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
},
(Item(l), Item(r)) => eq_item(l, r, eq_item_kind),
(Expr(l), Expr(r)) | (Semi(l), Semi(r)) => eq_expr(l, r),
(Empty, Empty) => true,
(MacCall(l), MacCall(r)) => l.1 == r.1 && eq_mac_call(&l.0, &r.0) && over(&l.2, &r.2, |l, r| eq_attr(l, r)),
_ => false,
}
}
pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> bool) -> bool {
eq_id(l.ident, r.ident)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
&& eq_vis(&l.vis, &r.vis)
&& eq_kind(&l.kind, &r.kind)
}
pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
use ItemKind::*;
match (l, r) {
(ExternCrate(l), ExternCrate(r)) => l == r,
(Use(l), Use(r)) => eq_use_tree(l, r),
(Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Fn(ld, lf, lg, lb), Fn(rd, rf, rg, rb)) => {
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(Mod(l), Mod(r)) => l.inline == r.inline && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_item_kind)),
(ForeignMod(l), ForeignMod(r)) => {
both(&l.abi, &r.abi, |l, r| eq_str_lit(l, r))
&& over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind))
},
(TyAlias(ld, lg, lb, lt), TyAlias(rd, rg, rb, rt)) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
&& both(lt, rt, |l, r| eq_ty(l, r))
},
(Enum(le, lg), Enum(re, rg)) => {
over(&le.variants, &re.variants, |l, r| eq_variant(l, r)) && eq_generics(lg, rg)
},
(Struct(lv, lg), Struct(rv, rg)) | (Union(lv, lg), Union(rv, rg)) => {
eq_variant_data(lv, rv) && eq_generics(lg, rg)
},
(Trait(la, lu, lg, lb, li), Trait(ra, ru, rg, rb, ri)) => {
la == ra
&& matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
&& eq_generics(lg, rg)
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
&& over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
},
(TraitAlias(lg, lb), TraitAlias(rg, rb)) => eq_generics(lg, rg) && over(lb, rb, |l, r| eq_generic_bound(l, r)),
(
Impl {
unsafety: lu,
polarity: lp,
defaultness: ld,
constness: lc,
generics: lg,
of_trait: lot,
self_ty: lst,
items: li,
},
Impl {
unsafety: ru,
polarity: rp,
defaultness: rd,
constness: rc,
generics: rg,
of_trait: rot,
self_ty: rst,
items: ri,
},
) => {
matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
&& matches!(lp, ImplPolarity::Positive) == matches!(rp, ImplPolarity::Positive)
&& eq_defaultness(*ld, *rd)
&& matches!(lc, ast::Const::No) == matches!(rc, ast::Const::No)
&& eq_generics(lg, rg)
&& both(lot, rot, |l, r| eq_path(&l.path, &r.path))
&& eq_ty(lst, rst)
&& over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
},
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
(MacroDef(l), MacroDef(r)) => l.macro_rules == r.macro_rules && eq_mac_args(&l.body, &r.body),
_ => false,
}
}
pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
use ForeignItemKind::*;
match (l, r) {
(Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Fn(ld, lf, lg, lb), Fn(rd, rf, rg, rb)) => {
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(TyAlias(ld, lg, lb, lt), TyAlias(rd, rg, rb, rt)) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
&& both(lt, rt, |l, r| eq_ty(l, r))
},
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
_ => false,
}
}
pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
use AssocItemKind::*;
match (l, r) {
(Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Fn(ld, lf, lg, lb), Fn(rd, rf, rg, rb)) => {
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(TyAlias(ld, lg, lb, lt), TyAlias(rd, rg, rb, rt)) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
&& both(lt, rt, |l, r| eq_ty(l, r))
},
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
_ => false,
}
}
pub fn eq_variant(l: &Variant, r: &Variant) -> bool {
l.is_placeholder == r.is_placeholder
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
&& eq_vis(&l.vis, &r.vis)
&& eq_id(l.ident, r.ident)
&& eq_variant_data(&l.data, &r.data)
&& both(&l.disr_expr, &r.disr_expr, |l, r| eq_expr(&l.value, &r.value))
}
pub fn eq_variant_data(l: &VariantData, r: &VariantData) -> bool {
use VariantData::*;
match (l, r) {
(Unit(_), Unit(_)) => true,
(Struct(l, _), Struct(r, _)) | (Tuple(l, _), Tuple(r, _)) => over(l, r, |l, r| eq_struct_field(l, r)),
_ => false,
}
}
pub fn eq_struct_field(l: &StructField, r: &StructField) -> bool {
l.is_placeholder == r.is_placeholder
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
&& eq_vis(&l.vis, &r.vis)
&& both(&l.ident, &r.ident, |l, r| eq_id(*l, *r))
&& eq_ty(&l.ty, &r.ty)
}
pub fn eq_fn_sig(l: &FnSig, r: &FnSig) -> bool {
eq_fn_decl(&l.decl, &r.decl) && eq_fn_header(&l.header, &r.header)
}
pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
matches!(l.unsafety, Unsafe::No) == matches!(r.unsafety, Unsafe::No)
&& l.asyncness.is_async() == r.asyncness.is_async()
&& matches!(l.constness, Const::No) == matches!(r.constness, Const::No)
&& eq_ext(&l.ext, &r.ext)
}
pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
over(&l.params, &r.params, |l, r| eq_generic_param(l, r))
&& over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {
eq_where_predicate(l, r)
})
}
pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool {
use WherePredicate::*;
match (l, r) {
(BoundPredicate(l), BoundPredicate(r)) => {
over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
eq_generic_param(l, r)
}) && eq_ty(&l.bounded_ty, &r.bounded_ty)
&& over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
},
(RegionPredicate(l), RegionPredicate(r)) => {
eq_id(l.lifetime.ident, r.lifetime.ident) && over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
},
(EqPredicate(l), EqPredicate(r)) => eq_ty(&l.lhs_ty, &r.lhs_ty) && eq_ty(&l.rhs_ty, &r.rhs_ty),
_ => false,
}
}
pub fn eq_use_tree(l: &UseTree, r: &UseTree) -> bool {
eq_path(&l.prefix, &r.prefix) && eq_use_tree_kind(&l.kind, &r.kind)
}
pub fn eq_use_tree_kind(l: &UseTreeKind, r: &UseTreeKind) -> bool {
use UseTreeKind::*;
match (l, r) {
(Glob, Glob) => true,
(Simple(l, _, _), Simple(r, _, _)) => both(l, r, |l, r| eq_id(*l, *r)),
(Nested(l), Nested(r)) => over(l, r, |(l, _), (r, _)| eq_use_tree(l, r)),
_ => false,
}
}
pub fn eq_defaultness(l: Defaultness, r: Defaultness) -> bool {
match (l, r) {
(Defaultness::Final, Defaultness::Final) | (Defaultness::Default(_), Defaultness::Default(_)) => true,
_ => false,
}
}
pub fn eq_vis(l: &Visibility, r: &Visibility) -> bool {
use VisibilityKind::*;
match (&l.node, &r.node) {
(Public, Public) | (Inherited, Inherited) | (Crate(_), Crate(_)) => true,
(Restricted { path: l, .. }, Restricted { path: r, .. }) => eq_path(l, r),
_ => false,
}
}
pub fn eq_fn_decl(l: &FnDecl, r: &FnDecl) -> bool {
eq_fn_ret_ty(&l.output, &r.output)
&& over(&l.inputs, &r.inputs, |l, r| {
l.is_placeholder == r.is_placeholder
&& eq_pat(&l.pat, &r.pat)
&& eq_ty(&l.ty, &r.ty)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
})
}
pub fn eq_fn_ret_ty(l: &FnRetTy, r: &FnRetTy) -> bool {
match (l, r) {
(FnRetTy::Default(_), FnRetTy::Default(_)) => true,
(FnRetTy::Ty(l), FnRetTy::Ty(r)) => eq_ty(l, r),
_ => false,
}
}
pub fn eq_ty(l: &Ty, r: &Ty) -> bool {
use TyKind::*;
match (&l.kind, &r.kind) {
(Paren(l), _) => eq_ty(l, r),
(_, Paren(r)) => eq_ty(l, r),
(Never, Never) | (Infer, Infer) | (ImplicitSelf, ImplicitSelf) | (Err, Err) | (CVarArgs, CVarArgs) => true,
(Slice(l), Slice(r)) => eq_ty(l, r),
(Array(le, ls), Array(re, rs)) => eq_ty(le, re) && eq_expr(&ls.value, &rs.value),
(Ptr(l), Ptr(r)) => l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty),
(Rptr(ll, l), Rptr(rl, r)) => {
both(ll, rl, |l, r| eq_id(l.ident, r.ident)) && l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty)
},
(BareFn(l), BareFn(r)) => {
l.unsafety == r.unsafety
&& eq_ext(&l.ext, &r.ext)
&& over(&l.generic_params, &r.generic_params, |l, r| eq_generic_param(l, r))
&& eq_fn_decl(&l.decl, &r.decl)
},
(Tup(l), Tup(r)) => over(l, r, |l, r| eq_ty(l, r)),
(Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
(TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, |l, r| eq_generic_bound(l, r)),
(ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, |l, r| eq_generic_bound(l, r)),
(Typeof(l), Typeof(r)) => eq_expr(&l.value, &r.value),
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
_ => false,
}
}
pub fn eq_ext(l: &Extern, r: &Extern) -> bool {
use Extern::*;
match (l, r) {
(None, None) | (Implicit, Implicit) => true,
(Explicit(l), Explicit(r)) => eq_str_lit(l, r),
_ => false,
}
}
pub fn eq_str_lit(l: &StrLit, r: &StrLit) -> bool {
l.style == r.style && l.symbol == r.symbol && l.suffix == r.suffix
}
pub fn eq_poly_ref_trait(l: &PolyTraitRef, r: &PolyTraitRef) -> bool {
eq_path(&l.trait_ref.path, &r.trait_ref.path)
&& over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
eq_generic_param(l, r)
})
}
pub fn eq_generic_param(l: &GenericParam, r: &GenericParam) -> bool {
use GenericParamKind::*;
l.is_placeholder == r.is_placeholder
&& eq_id(l.ident, r.ident)
&& over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
&& match (&l.kind, &r.kind) {
(Lifetime, Lifetime) => true,
(Type { default: l }, Type { default: r }) => both(l, r, |l, r| eq_ty(l, r)),
(Const { ty: l }, Const { ty: r }) => eq_ty(l, r),
_ => false,
}
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
}
pub fn eq_generic_bound(l: &GenericBound, r: &GenericBound) -> bool {
use GenericBound::*;
match (l, r) {
(Trait(ptr1, tbm1), Trait(ptr2, tbm2)) => tbm1 == tbm2 && eq_poly_ref_trait(ptr1, ptr2),
(Outlives(l), Outlives(r)) => eq_id(l.ident, r.ident),
_ => false,
}
}
pub fn eq_assoc_constraint(l: &AssocTyConstraint, r: &AssocTyConstraint) -> bool {
use AssocTyConstraintKind::*;
eq_id(l.ident, r.ident)
&& match (&l.kind, &r.kind) {
(Equality { ty: l }, Equality { ty: r }) => eq_ty(l, r),
(Bound { bounds: l }, Bound { bounds: r }) => over(l, r, |l, r| eq_generic_bound(l, r)),
_ => false,
}
}
pub fn eq_mac_call(l: &MacCall, r: &MacCall) -> bool {
eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args)
}
pub fn eq_attr(l: &Attribute, r: &Attribute) -> bool {
use AttrKind::*;
l.style == r.style
&& match (&l.kind, &r.kind) {
(DocComment(l), DocComment(r)) => l == r,
(Normal(l), Normal(r)) => eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args),
_ => false,
}
}
pub fn eq_mac_args(l: &MacArgs, r: &MacArgs) -> bool {
use MacArgs::*;
match (l, r) {
(Empty, Empty) => true,
(Delimited(_, ld, lts), Delimited(_, rd, rts)) => ld == rd && lts.eq_unspanned(rts),
(Eq(_, lts), Eq(_, rts)) => lts.eq_unspanned(rts),
_ => false,
}
}

View File

@ -332,19 +332,13 @@ fn swap_binop<'a>(
/// Checks if the two `Option`s are both `None` or some equal values as per
/// `eq_fn`.
fn both<X, F>(l: &Option<X>, r: &Option<X>, mut eq_fn: F) -> bool
where
F: FnMut(&X, &X) -> bool,
{
pub fn both<X>(l: &Option<X>, r: &Option<X>, mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
l.as_ref()
.map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y)))
}
/// Checks if two slices are equal as per `eq_fn`.
fn over<X, F>(left: &[X], right: &[X], mut eq_fn: F) -> bool
where
F: FnMut(&X, &X) -> bool,
{
pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
}

View File

@ -1,6 +1,8 @@
#[macro_use]
pub mod sym;
#[allow(clippy::module_name_repetitions)]
pub mod ast_utils;
pub mod attrs;
pub mod author;
pub mod camel_case;
@ -19,7 +21,7 @@ pub mod sugg;
pub mod usage;
pub use self::attrs::*;
pub use self::diagnostics::*;
pub use self::hir_utils::{SpanlessEq, SpanlessHash};
pub use self::hir_utils::{both, over, SpanlessEq, SpanlessHash};
use std::borrow::Cow;
use std::mem;
@ -40,7 +42,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};
@ -72,7 +74,7 @@ pub fn in_constant(cx: &LateContext<'_, '_>, id: HirId) -> bool {
let parent_id = cx.tcx.hir().get_parent_item(id);
match cx.tcx.hir().get(parent_id) {
Node::Item(&Item {
kind: ItemKind::Const(..),
kind: ItemKind::Const(..) | ItemKind::Static(..),
..
})
| Node::TraitItem(&TraitItem {
@ -83,11 +85,7 @@ pub fn in_constant(cx: &LateContext<'_, '_>, id: HirId) -> bool {
kind: ImplItemKind::Const(..),
..
})
| Node::AnonConst(_)
| Node::Item(&Item {
kind: ItemKind::Static(..),
..
}) => true,
| Node::AnonConst(_) => true,
Node::Item(&Item {
kind: ItemKind::Fn(ref sig, ..),
..
@ -165,8 +163,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 +179,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 +198,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,
},
@ -398,7 +398,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<Vec<&'a [Expr<'a>]>> {
@ -882,20 +882,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 {
@ -1408,6 +1394,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};

View File

@ -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"];

View File

@ -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,

View File

@ -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,
);
},
);
}
}
}
}

View File

@ -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:**

View File

@ -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

View File

@ -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();

View File

@ -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,

View File

@ -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
@ -32,8 +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:
![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, 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
@ -74,5 +76,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

View File

@ -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:

View File

@ -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,6 +19,60 @@ point releases, step 1. and 2. should be enough._
[Rust Forge]: https://forge.rust-lang.org/
## Remerge the `beta` branch
This step is only necessary, if since the last release something was backported
to the beta Rust release. The remerge is then necessary, to make sure that the
Clippy commit, that was used by the now stable Rust release, persists in the
tree of the Clippy repository.
To find out if this step is necessary run
```bash
# Assumes that the local master branch is up-to-date
$ git fetch upstream
$ git branch master --contains upstream/beta
```
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 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
```
After this, open a PR to the master branch. In this PR, the commit hash of the
`HEAD` of the `beta` branch must exists. In addition to that, no files should
be changed by this PR.
## Update the `beta` branch
This step must be done **after** the PR of the previous step was merged.
First, the Clippy commit of the `beta` branch of the Rust repository has to be
determined.
```bash
# Assuming the current directory corresponds to the Rust repository
$ git checkout beta
$ 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
be updated.
```bash
# Assuming the current directory corresponds to the Clippy repository
$ 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
@ -28,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")
```
@ -54,58 +107,3 @@ After this, the release should be available on the Clippy [release page].
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
to the beta Rust release. The remerge is then necessary, to make sure that the
Clippy commit, that was used by the now stable Rust release, persists in the
tree of the Clippy repository.
To find out if this step is necessary run
```bash
# Assumes that the local master branch is up-to-date
$ git fetch upstream
$ git branch master --contains upstream/beta
```
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 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
```
After this, open a PR to the master branch. In this PR, the commit hash of the
`HEAD` of the `beta` branch must exists. In addition to that, no files should
be changed by this PR.
## Update the `beta` branch
This step must be done **after** the PR of the previous step was merged.
First, the Clippy commit of the `beta` branch of the Rust repository has to be
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}')
```
After finding the Clippy commit, the `beta` branch in the Clippy repository can
be updated.
```bash
# Assuming the current directory corresponds to the Clippy repository
$ git checkout beta
$ git rebase $BETA_SHA
$ git push upstream beta
```

View File

@ -166,7 +166,7 @@ pub static ref ALL_LINTS: Vec<Lint> = 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",
@ -934,6 +934,13 @@ pub static ref ALL_LINTS: Vec<Lint> = 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",
@ -1016,7 +1023,7 @@ pub static ref ALL_LINTS: Vec<Lint> = 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",
@ -1735,7 +1742,7 @@ pub static ref ALL_LINTS: Vec<Lint> = 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",
},
@ -2292,6 +2299,13 @@ pub static ref ALL_LINTS: Vec<Lint> = 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",
@ -2313,6 +2327,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
deprecation: None,
module: "misc_early",
},
Lint {
name: "unnested_or_patterns",
group: "complexity",
desc: "unnested or-patterns, e.g., `Foo(Bar) | Foo(Baz) instead of `Foo(Bar | Baz)`",
deprecation: None,
module: "unnested_or_patterns",
},
Lint {
name: "unreachable",
group: "restriction",
@ -2460,6 +2481,13 @@ pub static ref ALL_LINTS: Vec<Lint> = 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",

View File

@ -184,8 +184,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() {

View File

@ -2,3 +2,5 @@
name = "cargo_common_metadata"
version = "0.1.0"
publish = false
[workspace]

View File

@ -9,3 +9,5 @@ readme = "README.md"
license = "MIT OR Apache-2.0"
keywords = ["metadata", "lint", "clippy"]
categories = ["development-tools::testing"]
[workspace]

View File

@ -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"

View File

@ -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"

View File

@ -3,6 +3,8 @@ name = "cargo_common_metadata"
version = "0.1.0"
publish = false
[workspace]
[dependencies]
regex = "1.3.7"
serde = "1.0.110"

View File

@ -3,5 +3,7 @@ name = "wildcard_dependencies"
version = "0.1.0"
publish = false
[workspace]
[dependencies]
regex = "*"

View File

@ -3,5 +3,7 @@ name = "wildcard_dependencies"
version = "0.1.0"
publish = false
[workspace]
[dependencies]
regex = "1"

View File

@ -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<u32> {
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<u16> {
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<u8> {
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<i32> {
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<i16> {
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<i32> {
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<i32> {
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<u8> {
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<u8> {
if value >= 0 {
Some(value as u8)
} else {
None
}
pub fn i8_to_u8(value: i8) {
let _ = value >= 0;
}
fn main() {}

View File

@ -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<u32> {
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<u16> {
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<u8> {
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<i32> {
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<i16> {
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<i32> {
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<i32> {
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<u8> {
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<u8> {
if value >= 0 {
Some(value as u8)
} else {
None
}
pub fn i8_to_u8(value: i8) {
let _ = value >= 0;
}
fn main() {}

View File

@ -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 {
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 {
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:22:13
|
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:29:8
--> $DIR/checked_conversions.rs:23:13
|
LL | if value <= (u8::max_value() as isize) && value >= 0 {
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:27:13
|
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:39:8
--> $DIR/checked_conversions.rs:28:13
|
LL | if value <= (i32::max_value() as i64) && value >= (i32::min_value() as i64) {
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:34:13
|
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:47:8
--> $DIR/checked_conversions.rs:35:13
|
LL | if value <= i64::from(i16::max_value()) && value >= i64::from(i16::min_value()) {
LL | let _ = value <= (i32::MAX as i64) && value >= (i32::MIN as i64);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::try_from(value).is_ok()`
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:57:8
--> $DIR/checked_conversions.rs:40:13
|
LL | if value <= i32::max_value() as u32 {
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:65:8
--> $DIR/checked_conversions.rs:47:13
|
LL | if value <= isize::max_value() as usize && value as i32 == 5 {
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:73:8
--> $DIR/checked_conversions.rs:52:13
|
LL | if value <= u16::max_value() as u32 && value as i32 == 5 {
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: aborting due to 8 previous errors
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

View File

@ -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 `<i32 as
// std::iter::Iterator>::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: ?Sized> {
x: X,
}
struct TwoStrs(str, str)
where
str: Sized;
fn unsized_local()
where
for<'a> Dst<A + 'a>: Sized,
{
let x: Dst<A> = *(Box::new(Dst { x: 1 }) as Box<Dst<A>>);
}
fn return_str() -> str
where
str: Sized,
{
*"Sized".to_string().into_boxed_str()
}
fn use_op(s: String) -> String
where
String: ::std::ops::Neg<Output = String>,
{
-s
}
fn use_for()
where
i32: Iterator,
{
for _ in 2i32 {}
}
fn main() {}

View File

@ -0,0 +1,22 @@
error: trait objects without an explicit `dyn` are deprecated
--> $DIR/ice-3969.rs:25:17
|
LL | for<'a> Dst<A + 'a>: 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<A> = *(Box::new(Dst { x: 1 }) as Box<Dst<A>>);
| ^ help: use `dyn`: `dyn A`
error: trait objects without an explicit `dyn` are deprecated
--> $DIR/ice-3969.rs:27:57
|
LL | let x: Dst<A> = *(Box::new(Dst { x: 1 }) as Box<Dst<A>>);
| ^ help: use `dyn`: `dyn A`
error: aborting due to 3 previous errors

View File

@ -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
/// <foo
struct A {}
fn main() {}

View File

@ -40,4 +40,6 @@ fn main() {
let _ = (&HashSet::<i32>::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()
}

View File

@ -40,4 +40,6 @@ fn main() {
let _ = (&HashSet::<i32>::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()
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

Some files were not shown because too many files have changed in this diff Show More