Auto merge of #95772 - flip1995:clippyup, r=Manishearth,flip1995

Update Clippy

r? `@Manishearth`
This commit is contained in:
bors 2022-04-08 13:22:07 +00:00
commit 2d5a21f63c
178 changed files with 3159 additions and 1535 deletions

View file

@ -613,9 +613,8 @@ dependencies = [
[[package]]
name = "clippy"
version = "0.1.61"
version = "0.1.62"
dependencies = [
"cargo_metadata",
"clippy_lints",
"clippy_utils",
"compiletest_rs",
@ -642,20 +641,18 @@ dependencies = [
name = "clippy_dev"
version = "0.0.1"
dependencies = [
"bytecount",
"cargo_metadata",
"clap 2.34.0",
"indoc",
"itertools",
"opener",
"regex",
"shell-escape",
"tempfile",
"walkdir",
]
[[package]]
name = "clippy_lints"
version = "0.1.61"
version = "0.1.62"
dependencies = [
"cargo_metadata",
"clippy_utils",
@ -676,7 +673,7 @@ dependencies = [
[[package]]
name = "clippy_utils"
version = "0.1.61"
version = "0.1.62"
dependencies = [
"arrayvec",
"if_chain",

View file

@ -6,11 +6,143 @@ document.
## Unreleased / In Rust Nightly
[0eff589...master](https://github.com/rust-lang/rust-clippy/compare/0eff589...master)
[57b3c4b...master](https://github.com/rust-lang/rust-clippy/compare/57b3c4b...master)
## Rust 1.59 (beta)
## Rust 1.60
Current beta, release 2022-02-24
Current stable, released 2022-04-07
[0eff589...57b3c4b](https://github.com/rust-lang/rust-clippy/compare/0eff589...57b3c4b)
### New Lints
* [`single_char_lifetime_names`]
[#8236](https://github.com/rust-lang/rust-clippy/pull/8236)
* [`iter_overeager_cloned`]
[#8203](https://github.com/rust-lang/rust-clippy/pull/8203)
* [`transmute_undefined_repr`]
[#8398](https://github.com/rust-lang/rust-clippy/pull/8398)
* [`default_union_representation`]
[#8289](https://github.com/rust-lang/rust-clippy/pull/8289)
* [`manual_bits`]
[#8213](https://github.com/rust-lang/rust-clippy/pull/8213)
* [`borrow_as_ptr`]
[#8210](https://github.com/rust-lang/rust-clippy/pull/8210)
### Moves and Deprecations
* Moved [`disallowed_methods`] and [`disallowed_types`] to `style` (now warn-by-default)
[#8261](https://github.com/rust-lang/rust-clippy/pull/8261)
* Rename `ref_in_deref` to [`needless_borrow`]
[#8217](https://github.com/rust-lang/rust-clippy/pull/8217)
* Moved [`mutex_atomic`] to `nursery` (now allow-by-default)
[#8260](https://github.com/rust-lang/rust-clippy/pull/8260)
### Enhancements
* [`ptr_arg`]: Now takes the argument usage into account and lints for mutable references
[#8271](https://github.com/rust-lang/rust-clippy/pull/8271)
* [`unused_io_amount`]: Now supports async read and write traits
[#8179](https://github.com/rust-lang/rust-clippy/pull/8179)
* [`while_let_on_iterator`]: Improved detection to catch more cases
[#8221](https://github.com/rust-lang/rust-clippy/pull/8221)
* [`trait_duplication_in_bounds`]: Now covers trait functions with `Self` bounds
[#8252](https://github.com/rust-lang/rust-clippy/pull/8252)
* [`unwrap_used`]: Now works for `.get(i).unwrap()` and `.get_mut(i).unwrap()`
[#8372](https://github.com/rust-lang/rust-clippy/pull/8372)
* [`map_clone`]: The suggestion takes `msrv` into account
[#8280](https://github.com/rust-lang/rust-clippy/pull/8280)
* [`manual_bits`] and [`borrow_as_ptr`]: Now track the `clippy::msrv` attribute
[#8280](https://github.com/rust-lang/rust-clippy/pull/8280)
* [`disallowed_methods`]: Now works for methods on primitive types
[#8112](https://github.com/rust-lang/rust-clippy/pull/8112)
* [`not_unsafe_ptr_arg_deref`]: Now works for type aliases
[#8273](https://github.com/rust-lang/rust-clippy/pull/8273)
* [`needless_question_mark`]: Now works for async functions
[#8311](https://github.com/rust-lang/rust-clippy/pull/8311)
* [`iter_not_returning_iterator`]: Now handles type projections
[#8228](https://github.com/rust-lang/rust-clippy/pull/8228)
* [`wrong_self_convention`]: Now detects wrong `self` references in more cases
[#8208](https://github.com/rust-lang/rust-clippy/pull/8208)
* [`single_match`]: Now works for `match` statements with tuples
[#8322](https://github.com/rust-lang/rust-clippy/pull/8322)
### False Positive Fixes
* [`erasing_op`]: No longer triggers if the output type changes
[#8204](https://github.com/rust-lang/rust-clippy/pull/8204)
* [`if_same_then_else`]: No longer triggers for `if let` statements
[#8297](https://github.com/rust-lang/rust-clippy/pull/8297)
* [`manual_memcpy`]: No longer lints on `VecDeque`
[#8226](https://github.com/rust-lang/rust-clippy/pull/8226)
* [`trait_duplication_in_bounds`]: Now takes path segments into account
[#8315](https://github.com/rust-lang/rust-clippy/pull/8315)
* [`deref_addrof`]: No longer lints when the dereference or borrow occurs in different a context
[#8268](https://github.com/rust-lang/rust-clippy/pull/8268)
* [`type_repetition_in_bounds`]: Now checks for full equality to prevent false positives
[#8224](https://github.com/rust-lang/rust-clippy/pull/8224)
* [`ptr_arg`]: No longer lint for mutable references in traits
[#8369](https://github.com/rust-lang/rust-clippy/pull/8369)
* [`implicit_clone`]: No longer lints for double references
[#8231](https://github.com/rust-lang/rust-clippy/pull/8231)
* [`needless_lifetimes`]: No longer lints lifetimes for explicit `self` types
[#8278](https://github.com/rust-lang/rust-clippy/pull/8278)
* [`op_ref`]: No longer lints in `BinOp` impl if that can cause recursion
[#8298](https://github.com/rust-lang/rust-clippy/pull/8298)
* [`enum_variant_names`]: No longer triggers for empty variant names
[#8329](https://github.com/rust-lang/rust-clippy/pull/8329)
* [`redundant_closure`]: No longer lints for `Arc<T>` or `Rc<T>`
[#8193](https://github.com/rust-lang/rust-clippy/pull/8193)
* [`iter_not_returning_iterator`]: No longer lints on trait implementations but therefore on trait definitions
[#8228](https://github.com/rust-lang/rust-clippy/pull/8228)
* [`single_match`]: No longer lints on exhaustive enum patterns without a wildcard
[#8322](https://github.com/rust-lang/rust-clippy/pull/8322)
* [`manual_swap`]: No longer lints on cases that involve automatic dereferences
[#8220](https://github.com/rust-lang/rust-clippy/pull/8220)
* [`useless_format`]: Now works for implicit named arguments
[#8295](https://github.com/rust-lang/rust-clippy/pull/8295)
### Suggestion Fixes/Improvements
* [`needless_borrow`]: Prevent mutable borrows being moved and suggest removing the borrow on method calls
[#8217](https://github.com/rust-lang/rust-clippy/pull/8217)
* [`chars_next_cmp`]: Correctly excapes the suggestion
[#8376](https://github.com/rust-lang/rust-clippy/pull/8376)
* [`explicit_write`]: Add suggestions for `write!`s with format arguments
[#8365](https://github.com/rust-lang/rust-clippy/pull/8365)
* [`manual_memcpy`]: Suggests `copy_from_slice` when applicable
[#8226](https://github.com/rust-lang/rust-clippy/pull/8226)
* [`or_fun_call`]: Improved suggestion display for long arguments
[#8292](https://github.com/rust-lang/rust-clippy/pull/8292)
* [`unnecessary_cast`]: Now correctly includes the sign
[#8350](https://github.com/rust-lang/rust-clippy/pull/8350)
* [`cmp_owned`]: No longer flips the comparison order
[#8299](https://github.com/rust-lang/rust-clippy/pull/8299)
* [`explicit_counter_loop`]: Now correctly suggests `iter()` on references
[#8382](https://github.com/rust-lang/rust-clippy/pull/8382)
### ICE Fixes
* [`manual_split_once`]
[#8250](https://github.com/rust-lang/rust-clippy/pull/8250)
### Documentation Improvements
* [`map_flatten`]: Add documentation for the `Option` type
[#8354](https://github.com/rust-lang/rust-clippy/pull/8354)
* Document that Clippy's driver might use a different code generation than rustc
[#8037](https://github.com/rust-lang/rust-clippy/pull/8037)
* Clippy's lint list will now automatically focus the search box
[#8343](https://github.com/rust-lang/rust-clippy/pull/8343)
### Others
* Clippy now warns if we find multiple Clippy config files exist
[#8326](https://github.com/rust-lang/rust-clippy/pull/8326)
## Rust 1.59
Released 2022-02-24
[e181011...0eff589](https://github.com/rust-lang/rust-clippy/compare/e181011...0eff589)
@ -174,7 +306,7 @@ Current beta, release 2022-02-24
## Rust 1.58
Current stable, released 2022-01-13
Released 2022-01-13
[00e31fa...e181011](https://github.com/rust-lang/rust-clippy/compare/00e31fa...e181011)
@ -3069,6 +3201,7 @@ Released 2018-09-13
[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
[`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons
[`cast_abs_to_unsigned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_abs_to_unsigned
[`cast_enum_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_constructor
[`cast_enum_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_truncation
[`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless
@ -3097,6 +3230,7 @@ Released 2018-09-13
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
[`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def
[`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir
[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute
[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
@ -3123,6 +3257,7 @@ Released 2018-09-13
[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg
[`double_parens`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_parens
[`drop_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy
[`drop_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_non_drop
[`drop_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_ref
[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument
[`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec
@ -3130,12 +3265,14 @@ Released 2018-09-13
[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum
[`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr
[`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop
[`empty_structs_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_structs_with_brackets
[`enum_clike_unportable_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_clike_unportable_variant
[`enum_glob_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_glob_use
[`enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names
[`eq_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#eq_op
[`equatable_if_let`]: https://rust-lang.github.io/rust-clippy/master/index.html#equatable_if_let
[`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op
[`err_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#err_expect
[`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence
[`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision
[`exhaustive_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums
@ -3174,6 +3311,7 @@ Released 2018-09-13
[`for_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_kv_map
[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles
[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
[`forget_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_non_drop
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
[`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args
[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect

View file

@ -1,6 +1,6 @@
[package]
name = "clippy"
version = "0.1.61"
version = "0.1.62"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"
@ -21,13 +21,12 @@ name = "clippy-driver"
path = "src/driver.rs"
[dependencies]
clippy_lints = { version = "0.1", path = "clippy_lints" }
clippy_lints = { path = "clippy_lints" }
semver = "1.0"
rustc_tools_util = { version = "0.2", path = "rustc_tools_util" }
rustc_tools_util = { path = "rustc_tools_util" }
tempfile = { version = "3.2", optional = true }
[dev-dependencies]
cargo_metadata = "0.14"
compiletest_rs = { version = "0.7.1", features = ["tmp"] }
tester = "0.9"
regex = "1.5"
@ -45,7 +44,7 @@ derive-new = "0.5"
if_chain = "1.0"
itertools = "0.10.1"
quote = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde = { version = "1.0.125", features = ["derive"] }
syn = { version = "1.0", features = ["full"] }
futures = "0.3"
parking_lot = "0.11.2"

View file

@ -4,15 +4,17 @@ version = "0.0.1"
edition = "2021"
[dependencies]
bytecount = "0.6"
clap = "2.33"
indoc = "1.0"
itertools = "0.10.1"
opener = "0.5"
regex = "1.5"
shell-escape = "0.1"
tempfile = "3.2"
walkdir = "2.3"
cargo_metadata = "0.14"
[features]
deny-warnings = []
[package.metadata.rust-analyzer]
# This package uses #[feature(rustc_private)]
rustc_private = true

View file

@ -1,22 +1,15 @@
//! `bless` updates the reference files in the repo with changed output files
//! from the last test run.
use crate::cargo_clippy_path;
use std::ffi::OsStr;
use std::fs;
use std::lazy::SyncLazy;
use std::path::{Path, PathBuf};
use walkdir::{DirEntry, WalkDir};
#[cfg(not(windows))]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
#[cfg(windows)]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
static CLIPPY_BUILD_TIME: SyncLazy<Option<std::time::SystemTime>> = SyncLazy::new(|| {
let mut path = std::env::current_exe().unwrap();
path.set_file_name(CARGO_CLIPPY_EXE);
fs::metadata(path).ok()?.modified().ok()
});
static CLIPPY_BUILD_TIME: SyncLazy<Option<std::time::SystemTime>> =
SyncLazy::new(|| cargo_clippy_path().metadata().ok()?.modified().ok());
/// # Panics
///

View file

@ -1,8 +1,12 @@
#![feature(let_else)]
#![feature(once_cell)]
#![feature(rustc_private)]
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
// warn on lints, that are included in `rust-lang/rust`s bootstrap
#![warn(rust_2018_idioms, unused_lifetimes)]
extern crate rustc_lexer;
use std::path::PathBuf;
pub mod bless;
@ -13,6 +17,19 @@ pub mod serve;
pub mod setup;
pub mod update_lints;
#[cfg(not(windows))]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
#[cfg(windows)]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
/// Returns the path to the `cargo-clippy` binary
#[must_use]
pub fn cargo_clippy_path() -> PathBuf {
let mut path = std::env::current_exe().expect("failed to get current executable name");
path.set_file_name(CARGO_CLIPPY_EXE);
path
}
/// Returns the path to the Clippy project directory
///
/// # Panics

View file

@ -1,19 +1,52 @@
use std::process::{self, Command};
use crate::cargo_clippy_path;
use std::process::{self, Command, ExitStatus};
use std::{fs, io};
pub fn run(filename: &str) {
let code = Command::new("cargo")
.args(["run", "--bin", "clippy-driver", "--"])
.args(["-L", "./target/debug"])
.args(["-Z", "no-codegen"])
.args(["--edition", "2021"])
.arg(filename)
.status()
.expect("failed to run cargo")
.code();
if code.is_none() {
eprintln!("Killed by signal");
fn exit_if_err(status: io::Result<ExitStatus>) {
match status.expect("failed to run command").code() {
Some(0) => {},
Some(n) => process::exit(n),
None => {
eprintln!("Killed by signal");
process::exit(1);
},
}
}
pub fn run(path: &str) {
let is_file = match fs::metadata(path) {
Ok(metadata) => metadata.is_file(),
Err(e) => {
eprintln!("Failed to read {path}: {e:?}");
process::exit(1);
},
};
if is_file {
exit_if_err(
Command::new("cargo")
.args(["run", "--bin", "clippy-driver", "--"])
.args(["-L", "./target/debug"])
.args(["-Z", "no-codegen"])
.args(["--edition", "2021"])
.arg(path)
.status(),
);
} else {
exit_if_err(Command::new("cargo").arg("build").status());
// Run in a tempdir as changes to clippy do not retrigger linting
let target = tempfile::Builder::new()
.prefix("clippy")
.tempdir()
.expect("failed to create tempdir");
let status = Command::new(cargo_clippy_path())
.current_dir(path)
.env("CARGO_TARGET_DIR", target.as_ref())
.status();
target.close().expect("failed to remove tempdir");
exit_if_err(status);
}
process::exit(code.unwrap_or(1));
}

View file

@ -4,6 +4,7 @@
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints};
use indoc::indoc;
fn main() {
let matches = get_clap_config();
@ -56,8 +57,8 @@ fn main() {
serve::run(port, lint);
},
("lint", Some(matches)) => {
let filename = matches.value_of("filename").unwrap();
lint::run(filename);
let path = matches.value_of("path").unwrap();
lint::run(path);
},
_ => {},
}
@ -225,11 +226,20 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
)
.subcommand(
SubCommand::with_name("lint")
.about("Manually run clippy on a file")
.about("Manually run clippy on a file or package")
.after_help(indoc! {"
EXAMPLES
Lint a single file:
cargo dev lint tests/ui/attrs.rs
Lint a package directory:
cargo dev lint tests/ui-cargo/wildcard_dependencies/fail
cargo dev lint ~/my-project
"})
.arg(
Arg::with_name("filename")
Arg::with_name("path")
.required(true)
.help("The path to a file to lint"),
.help("The path to a file or package directory to lint"),
),
)
.get_matches()

View file

@ -133,15 +133,23 @@ fn to_camel_case(name: &str) -> String {
}
fn get_stabilisation_version() -> String {
let mut command = cargo_metadata::MetadataCommand::new();
command.no_deps();
if let Ok(metadata) = command.exec() {
if let Some(pkg) = metadata.packages.iter().find(|pkg| pkg.name == "clippy") {
return format!("{}.{}.0", pkg.version.minor, pkg.version.patch);
}
fn parse_manifest(contents: &str) -> Option<String> {
let version = contents
.lines()
.filter_map(|l| l.split_once('='))
.find_map(|(k, v)| (k.trim() == "version").then(|| v.trim()))?;
let Some(("0", version)) = version.get(1..version.len() - 1)?.split_once('.') else {
return None;
};
let (minor, patch) = version.split_once('.')?;
Some(format!(
"{}.{}.0",
minor.parse::<u32>().ok()?,
patch.parse::<u32>().ok()?
))
}
String::from("<TODO set version(see doc/adding_lints.md)>")
let contents = fs::read_to_string("Cargo.toml").expect("Unable to read `Cargo.toml`");
parse_manifest(&contents).expect("Unable to find package version in `Cargo.toml`")
}
fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String {

View file

@ -1,9 +1,9 @@
use core::fmt::Write;
use itertools::Itertools;
use regex::Regex;
use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs;
use std::lazy::SyncLazy;
use std::path::Path;
use walkdir::WalkDir;
@ -13,35 +13,7 @@ const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev u
// Use that command to update this file and do not edit by hand.\n\
// Manual edits will be overwritten.\n\n";
static DEC_CLIPPY_LINT_RE: SyncLazy<Regex> = SyncLazy::new(|| {
Regex::new(
r#"(?x)
declare_clippy_lint!\s*[\{(]
(?:\s+///.*)*
(?:\s*\#\[clippy::version\s*=\s*"[^"]*"\])?
\s+pub\s+(?P<name>[A-Z_][A-Z_0-9]*)\s*,\s*
(?P<cat>[a-z_]+)\s*,\s*
"(?P<desc>(?:[^"\\]+|\\(?s).(?-s))*)"\s*[})]
"#,
)
.unwrap()
});
static DEC_DEPRECATED_LINT_RE: SyncLazy<Regex> = SyncLazy::new(|| {
Regex::new(
r#"(?x)
declare_deprecated_lint!\s*[{(]\s*
(?:\s+///.*)*
(?:\s*\#\[clippy::version\s*=\s*"[^"]*"\])?
\s+pub\s+(?P<name>[A-Z_][A-Z_0-9]*)\s*,\s*
"(?P<desc>(?:[^"\\]+|\\(?s).(?-s))*)"\s*[})]
"#,
)
.unwrap()
});
static NL_ESCAPE_RE: SyncLazy<Regex> = SyncLazy::new(|| Regex::new(r#"\\\n\s*"#).unwrap());
static DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html";
const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html";
#[derive(Clone, Copy, PartialEq)]
pub enum UpdateMode {
@ -60,60 +32,52 @@ pub enum UpdateMode {
/// Panics if a file path could not read from or then written to
#[allow(clippy::too_many_lines)]
pub fn run(update_mode: UpdateMode) {
let lint_list: Vec<Lint> = gather_all().collect();
let (lints, deprecated_lints) = gather_all();
let internal_lints = Lint::internal_lints(&lint_list);
let deprecated_lints = Lint::deprecated_lints(&lint_list);
let usable_lints = Lint::usable_lints(&lint_list);
let internal_lints = Lint::internal_lints(&lints);
let usable_lints = Lint::usable_lints(&lints);
let mut sorted_usable_lints = usable_lints.clone();
sorted_usable_lints.sort_by_key(|lint| lint.name.clone());
let usable_lint_count = round_to_fifty(usable_lints.len());
let mut file_change = false;
file_change |= replace_region_in_file(
replace_region_in_file(
update_mode,
Path::new("README.md"),
&format!(
r#"\[There are over \d+ lints included in this crate!\]\({}\)"#,
DOCS_LINK
),
"",
true,
update_mode == UpdateMode::Change,
|| {
vec![format!(
"[There are over {} lints included in this crate!]({})",
usable_lint_count, DOCS_LINK
)]
"[There are over ",
" lints included in this crate!]",
|res| {
write!(res, "{}", round_to_fifty(usable_lints.len())).unwrap();
},
)
.changed;
);
file_change |= replace_region_in_file(
replace_region_in_file(
update_mode,
Path::new("CHANGELOG.md"),
"<!-- begin autogenerated links to lint list -->",
"<!-- begin autogenerated links to lint list -->\n",
"<!-- end autogenerated links to lint list -->",
false,
update_mode == UpdateMode::Change,
|| gen_changelog_lint_list(usable_lints.iter().chain(deprecated_lints.iter())),
)
.changed;
|res| {
for lint in usable_lints
.iter()
.map(|l| &l.name)
.chain(deprecated_lints.iter().map(|l| &l.name))
.sorted()
{
writeln!(res, "[`{}`]: {}#{}", lint, DOCS_LINK, lint).unwrap();
}
},
);
// This has to be in lib.rs, otherwise rustfmt doesn't work
file_change |= replace_region_in_file(
replace_region_in_file(
update_mode,
Path::new("clippy_lints/src/lib.rs"),
"begin lints modules",
"end lints modules",
false,
update_mode == UpdateMode::Change,
|| gen_modules_list(usable_lints.iter()),
)
.changed;
if file_change && update_mode == UpdateMode::Check {
exit_with_failure();
}
"// begin lints modules, do not remove this comment, its used in `update_lints`\n",
"// end lints modules, do not remove this comment, its used in `update_lints`",
|res| {
for lint_mod in usable_lints.iter().map(|l| &l.module).unique().sorted() {
writeln!(res, "mod {};", lint_mod).unwrap();
}
},
);
process_file(
"clippy_lints/src/lib.register_lints.rs",
@ -123,7 +87,7 @@ pub fn run(update_mode: UpdateMode) {
process_file(
"clippy_lints/src/lib.deprecated.rs",
update_mode,
&gen_deprecated(deprecated_lints.iter()),
&gen_deprecated(&deprecated_lints),
);
let all_group_lints = usable_lints.iter().filter(|l| {
@ -146,15 +110,12 @@ pub fn run(update_mode: UpdateMode) {
}
pub fn print_lints() {
let lint_list: Vec<Lint> = gather_all().collect();
let (lint_list, _) = gather_all();
let usable_lints = Lint::usable_lints(&lint_list);
let usable_lint_count = usable_lints.len();
let grouped_by_lint_group = Lint::by_lint_group(usable_lints.into_iter());
for (lint_group, mut lints) in grouped_by_lint_group {
if lint_group == "Deprecated" {
continue;
}
println!("\n## {}", lint_group);
lints.sort_by_key(|l| l.name.clone());
@ -198,19 +159,17 @@ struct Lint {
name: String,
group: String,
desc: String,
deprecation: Option<String>,
module: String,
}
impl Lint {
#[must_use]
fn new(name: &str, group: &str, desc: &str, deprecation: Option<&str>, module: &str) -> Self {
fn new(name: &str, group: &str, desc: &str, module: &str) -> Self {
Self {
name: name.to_lowercase(),
group: group.to_string(),
desc: NL_ESCAPE_RE.replace(&desc.replace("\\\"", "\""), "").to_string(),
deprecation: deprecation.map(ToString::to_string),
module: module.to_string(),
group: group.into(),
desc: remove_line_splices(desc),
module: module.into(),
}
}
@ -219,7 +178,7 @@ impl Lint {
fn usable_lints(lints: &[Self]) -> Vec<Self> {
lints
.iter()
.filter(|l| l.deprecation.is_none() && !l.group.starts_with("internal"))
.filter(|l| !l.group.starts_with("internal"))
.cloned()
.collect()
}
@ -230,12 +189,6 @@ impl Lint {
lints.iter().filter(|l| l.group == "internal").cloned().collect()
}
/// Returns all deprecated lints
#[must_use]
fn deprecated_lints(lints: &[Self]) -> Vec<Self> {
lints.iter().filter(|l| l.deprecation.is_some()).cloned().collect()
}
/// Returns the lints in a `HashMap`, grouped by the different lint groups
#[must_use]
fn by_lint_group(lints: impl Iterator<Item = Self>) -> HashMap<String, Vec<Self>> {
@ -243,6 +196,20 @@ impl Lint {
}
}
#[derive(Clone, PartialEq, Debug)]
struct DeprecatedLint {
name: String,
reason: String,
}
impl DeprecatedLint {
fn new(name: &str, reason: &str) -> Self {
Self {
name: name.to_lowercase(),
reason: remove_line_splices(reason),
}
}
}
/// Generates the code for registering a group
fn gen_lint_group_list<'a>(group_name: &str, lints: impl Iterator<Item = &'a Lint>) -> String {
let mut details: Vec<_> = lints.map(|l| (&l.module, l.name.to_uppercase())).collect();
@ -262,32 +229,12 @@ fn gen_lint_group_list<'a>(group_name: &str, lints: impl Iterator<Item = &'a Lin
output
}
/// Generates the module declarations for `lints`
#[must_use]
fn gen_modules_list<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String> {
lints
.map(|l| &l.module)
.unique()
.map(|module| format!("mod {};", module))
.sorted()
.collect::<Vec<String>>()
}
/// Generates the list of lint links at the bottom of the CHANGELOG
#[must_use]
fn gen_changelog_lint_list<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String> {
lints
.sorted_by_key(|l| &l.name)
.map(|l| format!("[`{}`]: {}#{}", l.name, DOCS_LINK, l.name))
.collect()
}
/// Generates the `register_removed` code
#[must_use]
fn gen_deprecated<'a>(lints: impl Iterator<Item = &'a Lint>) -> String {
fn gen_deprecated(lints: &[DeprecatedLint]) -> String {
let mut output = GENERATED_FILE_COMMENT.to_string();
output.push_str("{\n");
for Lint { name, deprecation, .. } in lints {
for lint in lints {
output.push_str(&format!(
concat!(
" store.register_removed(\n",
@ -295,8 +242,7 @@ fn gen_deprecated<'a>(lints: impl Iterator<Item = &'a Lint>) -> String {
" \"{}\",\n",
" );\n"
),
name,
deprecation.as_ref().expect("`lints` are deprecated")
lint.name, lint.reason,
));
}
output.push_str("}\n");
@ -330,61 +276,136 @@ fn gen_register_lint_list<'a>(
output
}
/// Gathers all files in `src/clippy_lints` and gathers all lints inside
fn gather_all() -> impl Iterator<Item = Lint> {
lint_files().flat_map(|f| gather_from_file(&f))
}
/// Gathers all lints defined in `clippy_lints/src`
fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>) {
let mut lints = Vec::with_capacity(1000);
let mut deprecated_lints = Vec::with_capacity(50);
let root_path = clippy_project_root().join("clippy_lints/src");
fn gather_from_file(dir_entry: &walkdir::DirEntry) -> impl Iterator<Item = Lint> {
let content = fs::read_to_string(dir_entry.path()).unwrap();
let path = dir_entry.path();
let filename = path.file_stem().unwrap();
let path_buf = path.with_file_name(filename);
let mut rel_path = path_buf
.strip_prefix(clippy_project_root().join("clippy_lints/src"))
.expect("only files in `clippy_lints/src` should be looked at");
// If the lints are stored in mod.rs, we get the module name from
// the containing directory:
if filename == "mod" {
rel_path = rel_path.parent().unwrap();
}
let module = rel_path
.components()
.map(|c| c.as_os_str().to_str().unwrap())
.collect::<Vec<_>>()
.join("::");
parse_contents(&content, &module)
}
fn parse_contents(content: &str, module: &str) -> impl Iterator<Item = Lint> {
let lints = DEC_CLIPPY_LINT_RE
.captures_iter(content)
.map(|m| Lint::new(&m["name"], &m["cat"], &m["desc"], None, module));
let deprecated = DEC_DEPRECATED_LINT_RE
.captures_iter(content)
.map(|m| Lint::new(&m["name"], "Deprecated", &m["desc"], Some(&m["desc"]), module));
// Removing the `.collect::<Vec<Lint>>().into_iter()` causes some lifetime issues due to the map
lints.chain(deprecated).collect::<Vec<Lint>>().into_iter()
}
/// Collects all .rs files in the `clippy_lints/src` directory
fn lint_files() -> impl Iterator<Item = walkdir::DirEntry> {
// We use `WalkDir` instead of `fs::read_dir` here in order to recurse into subdirectories.
// Otherwise we would not collect all the lints, for example in `clippy_lints/src/methods/`.
let path = clippy_project_root().join("clippy_lints/src");
WalkDir::new(path)
for (rel_path, file) in WalkDir::new(&root_path)
.into_iter()
.filter_map(Result::ok)
.map(Result::unwrap)
.filter(|f| f.path().extension() == Some(OsStr::new("rs")))
.map(|f| (f.path().strip_prefix(&root_path).unwrap().to_path_buf(), f))
{
let path = file.path();
let contents =
fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e));
let module = rel_path
.components()
.map(|c| c.as_os_str().to_str().unwrap())
.collect::<Vec<_>>()
.join("::");
// If the lints are stored in mod.rs, we get the module name from
// the containing directory:
let module = if let Some(module) = module.strip_suffix("::mod.rs") {
module
} else {
module.strip_suffix(".rs").unwrap_or(&module)
};
if module == "deprecated_lints" {
parse_deprecated_contents(&contents, &mut deprecated_lints);
} else {
parse_contents(&contents, module, &mut lints);
}
}
(lints, deprecated_lints)
}
/// Whether a file has had its text changed or not
#[derive(PartialEq, Debug)]
struct FileChange {
changed: bool,
new_lines: String,
macro_rules! match_tokens {
($iter:ident, $($token:ident $({$($fields:tt)*})? $(($capture:ident))?)*) => {
{
$($(let $capture =)? if let Some((TokenKind::$token $({$($fields)*})?, _x)) = $iter.next() {
_x
} else {
continue;
};)*
#[allow(clippy::unused_unit)]
{ ($($($capture,)?)*) }
}
}
}
/// Parse a source file looking for `declare_clippy_lint` macro invocations.
fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
let mut offset = 0usize;
let mut iter = tokenize(contents).map(|t| {
let range = offset..offset + t.len;
offset = range.end;
(t.kind, &contents[range])
});
while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_clippy_lint") {
let mut iter = iter
.by_ref()
.filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
// matches `!{`
match_tokens!(iter, Bang OpenBrace);
match iter.next() {
// #[clippy::version = "version"] pub
Some((TokenKind::Pound, _)) => {
match_tokens!(iter, OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Ident);
},
// pub
Some((TokenKind::Ident, _)) => (),
_ => continue,
}
let (name, group, desc) = match_tokens!(
iter,
// LINT_NAME
Ident(name) Comma
// group,
Ident(group) Comma
// "description" }
Literal{..}(desc) CloseBrace
);
lints.push(Lint::new(name, group, desc, module));
}
}
/// Parse a source file looking for `declare_deprecated_lint` macro invocations.
fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) {
let mut offset = 0usize;
let mut iter = tokenize(contents).map(|t| {
let range = offset..offset + t.len;
offset = range.end;
(t.kind, &contents[range])
});
while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_deprecated_lint") {
let mut iter = iter
.by_ref()
.filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
let (name, reason) = match_tokens!(
iter,
// !{
Bang OpenBrace
// #[clippy::version = "version"]
Pound OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket
// pub LINT_NAME,
Ident Ident(name) Comma
// "description"
Literal{kind: LiteralKind::Str{..},..}(reason)
// }
CloseBrace
);
lints.push(DeprecatedLint::new(name, reason));
}
}
/// Removes the line splices and surrounding quotes from a string literal
fn remove_line_splices(s: &str) -> String {
let s = s
.strip_prefix('r')
.unwrap_or(s)
.trim_matches('#')
.strip_prefix('"')
.and_then(|s| s.strip_suffix('"'))
.unwrap_or_else(|| panic!("expected quoted string, found `{}`", s));
let mut res = String::with_capacity(s.len());
unescape::unescape_literal(s, unescape::Mode::Str, &mut |range, _| res.push_str(&s[range]));
res
}
/// Replaces a region in a file delimited by two lines matching regexes.
@ -396,144 +417,49 @@ struct FileChange {
/// # Panics
///
/// Panics if the path could not read or then written
fn replace_region_in_file<F>(
fn replace_region_in_file(
update_mode: UpdateMode,
path: &Path,
start: &str,
end: &str,
replace_start: bool,
write_back: bool,
replacements: F,
) -> FileChange
where
F: FnOnce() -> Vec<String>,
{
let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from {}: {}", path.display(), e));
let file_change = replace_region_in_text(&contents, start, end, replace_start, replacements);
write_replacement: impl FnMut(&mut String),
) {
let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from `{}`: {}", path.display(), e));
let new_contents = match replace_region_in_text(&contents, start, end, write_replacement) {
Ok(x) => x,
Err(delim) => panic!("Couldn't find `{}` in file `{}`", delim, path.display()),
};
if write_back {
if let Err(e) = fs::write(path, file_change.new_lines.as_bytes()) {
panic!("Cannot write to {}: {}", path.display(), e);
}
}
file_change
}
/// Replaces a region in a text delimited by two lines matching regexes.
///
/// * `text` is the input text on which you want to perform the replacement
/// * `start` is a `&str` that describes the delimiter line before the region you want to replace.
/// As the `&str` will be converted to a `Regex`, this can contain regex syntax, too.
/// * `end` is a `&str` that describes the delimiter line until where the replacement should happen.
/// As the `&str` will be converted to a `Regex`, this can contain regex syntax, too.
/// * If `replace_start` is true, the `start` delimiter line is replaced as well. The `end`
/// delimiter line is never replaced.
/// * `replacements` is a closure that has to return a `Vec<String>` which contains the new text.
///
/// If you want to perform the replacement on files instead of already parsed text,
/// use `replace_region_in_file`.
///
/// # Example
///
/// ```ignore
/// let the_text = "replace_start\nsome text\nthat will be replaced\nreplace_end";
/// let result =
/// replace_region_in_text(the_text, "replace_start", "replace_end", false, || {
/// vec!["a different".to_string(), "text".to_string()]
/// })
/// .new_lines;
/// assert_eq!("replace_start\na different\ntext\nreplace_end", result);
/// ```
///
/// # Panics
///
/// Panics if start or end is not valid regex
fn replace_region_in_text<F>(text: &str, start: &str, end: &str, replace_start: bool, replacements: F) -> FileChange
where
F: FnOnce() -> Vec<String>,
{
let replace_it = replacements();
let mut in_old_region = false;
let mut found = false;
let mut new_lines = vec![];
let start = Regex::new(start).unwrap();
let end = Regex::new(end).unwrap();
for line in text.lines() {
if in_old_region {
if end.is_match(line) {
in_old_region = false;
new_lines.extend(replace_it.clone());
new_lines.push(line.to_string());
match update_mode {
UpdateMode::Check if contents != new_contents => exit_with_failure(),
UpdateMode::Check => (),
UpdateMode::Change => {
if let Err(e) = fs::write(path, new_contents.as_bytes()) {
panic!("Cannot write to `{}`: {}", path.display(), e);
}
} else if start.is_match(line) {
if !replace_start {
new_lines.push(line.to_string());
}
in_old_region = true;
found = true;
} else {
new_lines.push(line.to_string());
}
},
}
if !found {
// This happens if the provided regex in `clippy_dev/src/main.rs` does not match in the
// given text or file. Most likely this is an error on the programmer's side and the Regex
// is incorrect.
eprintln!("error: regex \n{:?}\ndoesn't match. You may have to update it.", start);
std::process::exit(1);
}
let mut new_lines = new_lines.join("\n");
if text.ends_with('\n') {
new_lines.push('\n');
}
let changed = new_lines != text;
FileChange { changed, new_lines }
}
#[test]
fn test_parse_contents() {
let result: Vec<Lint> = parse_contents(
r#"
declare_clippy_lint! {
#[clippy::version = "Hello Clippy!"]
pub PTR_ARG,
style,
"really long \
text"
}
/// Replaces a region in a text delimited by two strings. Returns the new text if both delimiters
/// were found, or the missing delimiter if not.
fn replace_region_in_text<'a>(
text: &str,
start: &'a str,
end: &'a str,
mut write_replacement: impl FnMut(&mut String),
) -> Result<String, &'a str> {
let (text_start, rest) = text.split_once(start).ok_or(start)?;
let (_, text_end) = rest.split_once(end).ok_or(end)?;
declare_clippy_lint!{
#[clippy::version = "Test version"]
pub DOC_MARKDOWN,
pedantic,
"single line"
}
let mut res = String::with_capacity(text.len() + 4096);
res.push_str(text_start);
res.push_str(start);
write_replacement(&mut res);
res.push_str(end);
res.push_str(text_end);
/// some doc comment
declare_deprecated_lint! {
#[clippy::version = "I'm a version"]
pub SHOULD_ASSERT_EQ,
"`assert!()` will be more flexible with RFC 2011"
}
"#,
"module_name",
)
.collect();
let expected = vec![
Lint::new("ptr_arg", "style", "really long text", None, "module_name"),
Lint::new("doc_markdown", "pedantic", "single line", None, "module_name"),
Lint::new(
"should_assert_eq",
"Deprecated",
"`assert!()` will be more flexible with RFC 2011",
Some("`assert!()` will be more flexible with RFC 2011"),
"module_name",
),
];
assert_eq!(expected, result);
Ok(res)
}
#[cfg(test)]
@ -541,55 +467,65 @@ mod tests {
use super::*;
#[test]
fn test_replace_region() {
let text = "\nabc\n123\n789\ndef\nghi";
let expected = FileChange {
changed: true,
new_lines: "\nabc\nhello world\ndef\nghi".to_string(),
};
let result = replace_region_in_text(text, r#"^\s*abc$"#, r#"^\s*def"#, false, || {
vec!["hello world".to_string()]
});
fn test_parse_contents() {
static CONTENTS: &str = r#"
declare_clippy_lint! {
#[clippy::version = "Hello Clippy!"]
pub PTR_ARG,
style,
"really long \
text"
}
declare_clippy_lint!{
#[clippy::version = "Test version"]
pub DOC_MARKDOWN,
pedantic,
"single line"
}
"#;
let mut result = Vec::new();
parse_contents(CONTENTS, "module_name", &mut result);
let expected = vec![
Lint::new("ptr_arg", "style", "\"really long text\"", "module_name"),
Lint::new("doc_markdown", "pedantic", "\"single line\"", "module_name"),
];
assert_eq!(expected, result);
}
#[test]
fn test_replace_region_with_start() {
let text = "\nabc\n123\n789\ndef\nghi";
let expected = FileChange {
changed: true,
new_lines: "\nhello world\ndef\nghi".to_string(),
};
let result = replace_region_in_text(text, r#"^\s*abc$"#, r#"^\s*def"#, true, || {
vec!["hello world".to_string()]
});
assert_eq!(expected, result);
}
fn test_parse_deprecated_contents() {
static DEPRECATED_CONTENTS: &str = r#"
/// some doc comment
declare_deprecated_lint! {
#[clippy::version = "I'm a version"]
pub SHOULD_ASSERT_EQ,
"`assert!()` will be more flexible with RFC 2011"
}
"#;
#[test]
fn test_replace_region_no_changes() {
let text = "123\n456\n789";
let expected = FileChange {
changed: false,
new_lines: "123\n456\n789".to_string(),
};
let result = replace_region_in_text(text, r#"^\s*123$"#, r#"^\s*456"#, false, Vec::new);
let mut result = Vec::new();
parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result);
let expected = vec![DeprecatedLint::new(
"should_assert_eq",
"\"`assert!()` will be more flexible with RFC 2011\"",
)];
assert_eq!(expected, result);
}
#[test]
fn test_usable_lints() {
let lints = vec![
Lint::new("should_assert_eq", "Deprecated", "abc", Some("Reason"), "module_name"),
Lint::new("should_assert_eq2", "Not Deprecated", "abc", None, "module_name"),
Lint::new("should_assert_eq2", "internal", "abc", None, "module_name"),
Lint::new("should_assert_eq2", "internal_style", "abc", None, "module_name"),
Lint::new("should_assert_eq2", "Not Deprecated", "\"abc\"", "module_name"),
Lint::new("should_assert_eq2", "internal", "\"abc\"", "module_name"),
Lint::new("should_assert_eq2", "internal_style", "\"abc\"", "module_name"),
];
let expected = vec![Lint::new(
"should_assert_eq2",
"Not Deprecated",
"abc",
None,
"\"abc\"",
"module_name",
)];
assert_eq!(expected, Lint::usable_lints(&lints));
@ -598,55 +534,30 @@ mod tests {
#[test]
fn test_by_lint_group() {
let lints = vec![
Lint::new("should_assert_eq", "group1", "abc", None, "module_name"),
Lint::new("should_assert_eq2", "group2", "abc", None, "module_name"),
Lint::new("incorrect_match", "group1", "abc", None, "module_name"),
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name"),
Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"),
];
let mut expected: HashMap<String, Vec<Lint>> = HashMap::new();
expected.insert(
"group1".to_string(),
vec![
Lint::new("should_assert_eq", "group1", "abc", None, "module_name"),
Lint::new("incorrect_match", "group1", "abc", None, "module_name"),
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"),
],
);
expected.insert(
"group2".to_string(),
vec![Lint::new("should_assert_eq2", "group2", "abc", None, "module_name")],
vec![Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name")],
);
assert_eq!(expected, Lint::by_lint_group(lints.into_iter()));
}
#[test]
fn test_gen_changelog_lint_list() {
let lints = vec![
Lint::new("should_assert_eq", "group1", "abc", None, "module_name"),
Lint::new("should_assert_eq2", "group2", "abc", None, "module_name"),
];
let expected = vec![
format!("[`should_assert_eq`]: {}#should_assert_eq", DOCS_LINK),
format!("[`should_assert_eq2`]: {}#should_assert_eq2", DOCS_LINK),
];
assert_eq!(expected, gen_changelog_lint_list(lints.iter()));
}
#[test]
fn test_gen_deprecated() {
let lints = vec![
Lint::new(
"should_assert_eq",
"group1",
"abc",
Some("has been superseded by should_assert_eq2"),
"module_name",
),
Lint::new(
"another_deprecated",
"group2",
"abc",
Some("will be removed"),
"module_name",
),
DeprecatedLint::new("should_assert_eq", "\"has been superseded by should_assert_eq2\""),
DeprecatedLint::new("another_deprecated", "\"will be removed\""),
];
let expected = GENERATED_FILE_COMMENT.to_string()
@ -665,32 +576,15 @@ mod tests {
.join("\n")
+ "\n";
assert_eq!(expected, gen_deprecated(lints.iter()));
}
#[test]
#[should_panic]
fn test_gen_deprecated_fail() {
let lints = vec![Lint::new("should_assert_eq2", "group2", "abc", None, "module_name")];
let _deprecated_lints = gen_deprecated(lints.iter());
}
#[test]
fn test_gen_modules_list() {
let lints = vec![
Lint::new("should_assert_eq", "group1", "abc", None, "module_name"),
Lint::new("incorrect_stuff", "group3", "abc", None, "another_module"),
];
let expected = vec!["mod another_module;".to_string(), "mod module_name;".to_string()];
assert_eq!(expected, gen_modules_list(lints.iter()));
assert_eq!(expected, gen_deprecated(&lints));
}
#[test]
fn test_gen_lint_group_list() {
let lints = vec![
Lint::new("abc", "group1", "abc", None, "module_name"),
Lint::new("should_assert_eq", "group1", "abc", None, "module_name"),
Lint::new("internal", "internal_style", "abc", None, "module_name"),
Lint::new("abc", "group1", "\"abc\"", "module_name"),
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
Lint::new("internal", "internal_style", "\"abc\"", "module_name"),
];
let expected = GENERATED_FILE_COMMENT.to_string()
+ &[

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_lints"
version = "0.1.61"
version = "0.1.62"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View file

@ -0,0 +1,42 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use clippy_utils::{meets_msrv, msrvs};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_semver::RustcVersion;
use super::CAST_ABS_TO_UNSIGNED;
pub(super) fn check(
cx: &LateContext<'_>,
expr: &Expr<'_>,
cast_expr: &Expr<'_>,
cast_from: Ty<'_>,
cast_to: Ty<'_>,
msrv: &Option<RustcVersion>,
) {
if_chain! {
if meets_msrv(msrv.as_ref(), &msrvs::UNSIGNED_ABS);
if cast_from.is_integral();
if cast_to.is_integral();
if cast_from.is_signed();
if !cast_to.is_signed();
if let ExprKind::MethodCall(method_path, args, _) = cast_expr.kind;
if let method_name = method_path.ident.name.as_str();
if method_name == "abs";
then {
span_lint_and_sugg(
cx,
CAST_ABS_TO_UNSIGNED,
expr.span,
&format!("casting the result of `{}::{}()` to {}", cast_from, method_name, cast_to),
"replace with",
format!("{}.unsigned_abs()", Sugg::hir(cx, &args[0], "..")),
Applicability::MachineApplicable,
);
}
}
}

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_hir_ty_cfg_dependant;
use clippy_utils::ty::is_c_void;
use if_chain::if_chain;
use clippy_utils::{get_parent_expr, is_hir_ty_cfg_dependant, match_any_def_paths, paths};
use rustc_hir::{Expr, ExprKind, GenericArg};
use rustc_lint::LateContext;
use rustc_middle::ty::layout::LayoutOf;
@ -20,45 +19,78 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
);
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
} else if let ExprKind::MethodCall(method_path, [self_arg, ..], _) = &expr.kind {
if_chain! {
if method_path.ident.name == sym!(cast);
if let Some(generic_args) = method_path.args;
if let [GenericArg::Type(cast_to)] = generic_args.args;
if method_path.ident.name == sym!(cast)
&& let Some(generic_args) = method_path.args
&& let [GenericArg::Type(cast_to)] = generic_args.args
// There probably is no obvious reason to do this, just to be consistent with `as` cases.
if !is_hir_ty_cfg_dependant(cx, cast_to);
then {
let (cast_from, cast_to) =
(cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr));
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
}
&& !is_hir_ty_cfg_dependant(cx, cast_to)
{
let (cast_from, cast_to) =
(cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr));
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
}
}
}
fn lint_cast_ptr_alignment<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) {
if_chain! {
if let ty::RawPtr(from_ptr_ty) = &cast_from.kind();
if let ty::RawPtr(to_ptr_ty) = &cast_to.kind();
if let Ok(from_layout) = cx.layout_of(from_ptr_ty.ty);
if let Ok(to_layout) = cx.layout_of(to_ptr_ty.ty);
if from_layout.align.abi < to_layout.align.abi;
if let ty::RawPtr(from_ptr_ty) = &cast_from.kind()
&& let ty::RawPtr(to_ptr_ty) = &cast_to.kind()
&& let Ok(from_layout) = cx.layout_of(from_ptr_ty.ty)
&& let Ok(to_layout) = cx.layout_of(to_ptr_ty.ty)
&& from_layout.align.abi < to_layout.align.abi
// with c_void, we inherently need to trust the user
if !is_c_void(cx, from_ptr_ty.ty);
&& !is_c_void(cx, from_ptr_ty.ty)
// when casting from a ZST, we don't know enough to properly lint
if !from_layout.is_zst();
then {
span_lint(
cx,
CAST_PTR_ALIGNMENT,
expr.span,
&format!(
"casting from `{}` to a more-strictly-aligned pointer (`{}`) ({} < {} bytes)",
cast_from,
cast_to,
from_layout.align.abi.bytes(),
to_layout.align.abi.bytes(),
),
);
}
&& !from_layout.is_zst()
&& !is_used_as_unaligned(cx, expr)
{
span_lint(
cx,
CAST_PTR_ALIGNMENT,
expr.span,
&format!(
"casting from `{}` to a more-strictly-aligned pointer (`{}`) ({} < {} bytes)",
cast_from,
cast_to,
from_layout.align.abi.bytes(),
to_layout.align.abi.bytes(),
),
);
}
}
fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
let Some(parent) = get_parent_expr(cx, e) else {
return false;
};
match parent.kind {
ExprKind::MethodCall(name, [self_arg, ..], _) if self_arg.hir_id == e.hir_id => {
if matches!(name.ident.as_str(), "read_unaligned" | "write_unaligned")
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
&& let Some(def_id) = cx.tcx.impl_of_method(def_id)
&& cx.tcx.type_of(def_id).is_unsafe_ptr()
{
true
} else {
false
}
},
ExprKind::Call(func, [arg, ..]) if arg.hir_id == e.hir_id => {
static PATHS: &[&[&str]] = &[
paths::PTR_READ_UNALIGNED.as_slice(),
paths::PTR_WRITE_UNALIGNED.as_slice(),
paths::PTR_UNALIGNED_VOLATILE_LOAD.as_slice(),
paths::PTR_UNALIGNED_VOLATILE_STORE.as_slice(),
];
if let ExprKind::Path(path) = &func.kind
&& let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
&& match_any_def_paths(cx, def_id, PATHS).is_some()
{
true
} else {
false
}
},
_ => false,
}
}

View file

@ -1,3 +1,4 @@
mod cast_abs_to_unsigned;
mod cast_enum_constructor;
mod cast_lossless;
mod cast_possible_truncation;
@ -473,6 +474,28 @@ declare_clippy_lint! {
"casts from an enum tuple constructor to an integer"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for uses of the `abs()` method that cast the result to unsigned.
///
/// ### Why is this bad?
/// The `unsigned_abs()` method avoids panic when called on the MIN value.
///
/// ### Example
/// ```rust
/// let x: i32 = -42;
/// let y: u32 = x.abs() as u32;
/// ```
/// Use instead:
/// let x: i32 = -42;
/// let y: u32 = x.unsigned_abs();
/// ```
#[clippy::version = "1.61.0"]
pub CAST_ABS_TO_UNSIGNED,
suspicious,
"casting the result of `abs()` to an unsigned integer can panic"
}
pub struct Casts {
msrv: Option<RustcVersion>,
}
@ -500,7 +523,8 @@ impl_lint_pass!(Casts => [
CHAR_LIT_AS_U8,
PTR_AS_PTR,
CAST_ENUM_TRUNCATION,
CAST_ENUM_CONSTRUCTOR
CAST_ENUM_CONSTRUCTOR,
CAST_ABS_TO_UNSIGNED
]);
impl<'tcx> LateLintPass<'tcx> for Casts {
@ -536,6 +560,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
cast_possible_wrap::check(cx, expr, cast_from, cast_to);
cast_precision_loss::check(cx, expr, cast_from, cast_to);
cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
cast_abs_to_unsigned::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
}
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
cast_enum_constructor::check(cx, expr, cast_expr, cast_from);

View file

@ -4,7 +4,8 @@ use clippy_utils::source::snippet_opt;
use if_chain::if_chain;
use rustc_ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Lit, UnOp};
use rustc_hir::def::Res;
use rustc_hir::{Expr, ExprKind, Lit, QPath, TyKind, UnOp};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, FloatTy, InferTy, Ty};
@ -18,6 +19,17 @@ pub(super) fn check(
cast_from: Ty<'_>,
cast_to: Ty<'_>,
) -> bool {
// skip non-primitive type cast
if_chain! {
if let ExprKind::Cast(_, cast_to) = expr.kind;
if let TyKind::Path(QPath::Resolved(_, path)) = &cast_to.kind;
if let Res::PrimTy(_) = path.res;
then {}
else {
return false
}
}
if let Some(lit) = get_numeric_literal(cast_expr) {
let literal_str = snippet_opt(cx, cast_expr.span).unwrap_or_default();

View file

@ -0,0 +1,125 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_ast::ast::{AttrKind, Attribute, Item, ItemKind};
use rustc_ast::token::{Token, TokenKind};
use rustc_ast::tokenstream::{TokenStream, TokenTree};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{symbol::sym, Span};
declare_clippy_lint! {
/// ### What it does
/// Checks for use of `crate` as opposed to `$crate` in a macro definition.
///
/// ### Why is this bad?
/// `crate` refers to the macro call's crate, whereas `$crate` refers to the macro definition's
/// crate. Rarely is the former intended. See:
/// https://doc.rust-lang.org/reference/macros-by-example.html#hygiene
///
/// ### Example
/// ```rust
/// #[macro_export]
/// macro_rules! print_message {
/// () => {
/// println!("{}", crate::MESSAGE);
/// };
/// }
/// pub const MESSAGE: &str = "Hello!";
/// ```
/// Use instead:
/// ```rust
/// #[macro_export]
/// macro_rules! print_message {
/// () => {
/// println!("{}", $crate::MESSAGE);
/// };
/// }
/// pub const MESSAGE: &str = "Hello!";
/// ```
///
/// Note that if the use of `crate` is intentional, an `allow` attribute can be applied to the
/// macro definition, e.g.:
/// ```rust,ignore
/// #[allow(clippy::crate_in_macro_def)]
/// macro_rules! ok { ... crate::foo ... }
/// ```
#[clippy::version = "1.61.0"]
pub CRATE_IN_MACRO_DEF,
suspicious,
"using `crate` in a macro definition"
}
declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]);
impl EarlyLintPass for CrateInMacroDef {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if_chain! {
if item.attrs.iter().any(is_macro_export);
if let ItemKind::MacroDef(macro_def) = &item.kind;
let tts = macro_def.body.inner_tokens();
if let Some(span) = contains_unhygienic_crate_reference(&tts);
then {
span_lint_and_sugg(
cx,
CRATE_IN_MACRO_DEF,
span,
"`crate` references the macro call's crate",
"to reference the macro definition's crate, use",
String::from("$crate"),
Applicability::MachineApplicable,
);
}
}
}
}
fn is_macro_export(attr: &Attribute) -> bool {
if_chain! {
if let AttrKind::Normal(attr_item, _) = &attr.kind;
if let [segment] = attr_item.path.segments.as_slice();
then {
segment.ident.name == sym::macro_export
} else {
false
}
}
}
fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
let mut prev_is_dollar = false;
let mut cursor = tts.trees();
while let Some(curr) = cursor.next() {
if_chain! {
if !prev_is_dollar;
if let Some(span) = is_crate_keyword(&curr);
if let Some(next) = cursor.look_ahead(0);
if is_token(next, &TokenKind::ModSep);
then {
return Some(span);
}
}
if let TokenTree::Delimited(_, _, tts) = &curr {
let span = contains_unhygienic_crate_reference(tts);
if span.is_some() {
return span;
}
}
prev_is_dollar = is_token(&curr, &TokenKind::Dollar);
}
None
}
fn is_crate_keyword(tt: &TokenTree) -> Option<Span> {
if_chain! {
if let TokenTree::Token(Token { kind: TokenKind::Ident(symbol, _), span }) = tt;
if symbol.as_str() == "crate";
then { Some(*span) } else { None }
}
}
fn is_token(tt: &TokenTree, kind: &TokenKind) -> bool {
if let TokenTree::Token(Token { kind: other, .. }) = tt {
kind == other
} else {
false
}
}

View file

@ -621,8 +621,8 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
let filename = FileName::anon_source_code(&code);
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let fallback_bundle = rustc_errors::fallback_fluent_bundle(false)
.expect("failed to load fallback fluent bundle");
let fallback_bundle =
rustc_errors::fallback_fluent_bundle(false).expect("failed to load fallback fluent bundle");
let emitter = EmitterWriter::new(
Box::new(io::sink()),
None,

View file

@ -1,9 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::ty::is_copy;
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind};
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
use clippy_utils::is_must_use_func_call;
use clippy_utils::ty::{is_copy, is_must_use_ty, is_type_lang_item};
use rustc_hir::{Expr, ExprKind, LangItem};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
@ -103,6 +102,75 @@ declare_clippy_lint! {
"calls to `std::mem::forget` with a value that implements Copy"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `std::mem::drop` with a value that does not implement `Drop`.
///
/// ### Why is this bad?
/// Calling `std::mem::drop` is no different than dropping such a type. A different value may
/// have been intended.
///
/// ### Example
/// ```rust
/// struct Foo;
/// let x = Foo;
/// std::mem::drop(x);
/// ```
#[clippy::version = "1.61.0"]
pub DROP_NON_DROP,
suspicious,
"call to `std::mem::drop` with a value which does not implement `Drop`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `std::mem::forget` with a value that does not implement `Drop`.
///
/// ### Why is this bad?
/// Calling `std::mem::forget` is no different than dropping such a type. A different value may
/// have been intended.
///
/// ### Example
/// ```rust
/// struct Foo;
/// let x = Foo;
/// std::mem::forget(x);
/// ```
#[clippy::version = "1.61.0"]
pub FORGET_NON_DROP,
suspicious,
"call to `std::mem::forget` with a value which does not implement `Drop`"
}
declare_clippy_lint! {
/// ### What it does
/// Prevents the safe `std::mem::drop` function from being called on `std::mem::ManuallyDrop`.
///
/// ### Why is this bad?
/// The safe `drop` function does not drop the inner value of a `ManuallyDrop`.
///
/// ### Known problems
/// Does not catch cases if the user binds `std::mem::drop`
/// to a different name and calls it that way.
///
/// ### Example
/// ```rust
/// struct S;
/// drop(std::mem::ManuallyDrop::new(S));
/// ```
/// Use instead:
/// ```rust
/// struct S;
/// unsafe {
/// std::mem::ManuallyDrop::drop(&mut std::mem::ManuallyDrop::new(S));
/// }
/// ```
#[clippy::version = "1.49.0"]
pub UNDROPPED_MANUALLY_DROPS,
correctness,
"use of safe `std::mem::drop` function to drop a std::mem::ManuallyDrop, which will not drop the inner value"
}
const DROP_REF_SUMMARY: &str = "calls to `std::mem::drop` with a reference instead of an owned value. \
Dropping a reference does nothing";
const FORGET_REF_SUMMARY: &str = "calls to `std::mem::forget` with a reference instead of an owned value. \
@ -111,60 +179,65 @@ const DROP_COPY_SUMMARY: &str = "calls to `std::mem::drop` with a value that imp
Dropping a copy leaves the original intact";
const FORGET_COPY_SUMMARY: &str = "calls to `std::mem::forget` with a value that implements `Copy`. \
Forgetting a copy leaves the original intact";
const DROP_NON_DROP_SUMMARY: &str = "call to `std::mem::drop` with a value that does not implement `Drop`. \
Dropping such a type only extends it's contained lifetimes";
const FORGET_NON_DROP_SUMMARY: &str = "call to `std::mem::forget` with a value that does not implement `Drop`. \
Forgetting such a type is the same as dropping it";
declare_lint_pass!(DropForgetRef => [DROP_REF, FORGET_REF, DROP_COPY, FORGET_COPY]);
declare_lint_pass!(DropForgetRef => [
DROP_REF,
FORGET_REF,
DROP_COPY,
FORGET_COPY,
DROP_NON_DROP,
FORGET_NON_DROP,
UNDROPPED_MANUALLY_DROPS
]);
impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Call(path, args) = expr.kind;
if let ExprKind::Path(ref qpath) = path.kind;
if args.len() == 1;
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
then {
let lint;
let msg;
let arg = &args[0];
let arg_ty = cx.typeck_results().expr_ty(arg);
if let ty::Ref(..) = arg_ty.kind() {
match cx.tcx.get_diagnostic_name(def_id) {
Some(sym::mem_drop) => {
lint = DROP_REF;
msg = DROP_REF_SUMMARY.to_string();
},
Some(sym::mem_forget) => {
lint = FORGET_REF;
msg = FORGET_REF_SUMMARY.to_string();
},
_ => return,
}
span_lint_and_note(cx,
lint,
expr.span,
&msg,
Some(arg.span),
&format!("argument has type `{}`", arg_ty));
} else if is_copy(cx, arg_ty) {
match cx.tcx.get_diagnostic_name(def_id) {
Some(sym::mem_drop) => {
lint = DROP_COPY;
msg = DROP_COPY_SUMMARY.to_string();
},
Some(sym::mem_forget) => {
lint = FORGET_COPY;
msg = FORGET_COPY_SUMMARY.to_string();
},
_ => return,
}
span_lint_and_note(cx,
lint,
expr.span,
&msg,
Some(arg.span),
&format!("argument has type {}", arg_ty));
if let ExprKind::Call(path, [arg]) = expr.kind
&& let ExprKind::Path(ref qpath) = path.kind
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
&& let Some(fn_name) = cx.tcx.get_diagnostic_name(def_id)
{
let arg_ty = cx.typeck_results().expr_ty(arg);
let (lint, msg) = match fn_name {
sym::mem_drop if arg_ty.is_ref() => (DROP_REF, DROP_REF_SUMMARY),
sym::mem_forget if arg_ty.is_ref() => (FORGET_REF, FORGET_REF_SUMMARY),
sym::mem_drop if is_copy(cx, arg_ty) => (DROP_COPY, DROP_COPY_SUMMARY),
sym::mem_forget if is_copy(cx, arg_ty) => (FORGET_COPY, FORGET_COPY_SUMMARY),
sym::mem_drop if is_type_lang_item(cx, arg_ty, LangItem::ManuallyDrop) => {
span_lint_and_help(
cx,
UNDROPPED_MANUALLY_DROPS,
expr.span,
"the inner value of this ManuallyDrop will not be dropped",
None,
"to drop a `ManuallyDrop<T>`, use std::mem::ManuallyDrop::drop",
);
return;
}
}
sym::mem_drop
if !(arg_ty.needs_drop(cx.tcx, cx.param_env)
|| is_must_use_func_call(cx, arg)
|| is_must_use_ty(cx, arg_ty)) =>
{
(DROP_NON_DROP, DROP_NON_DROP_SUMMARY)
},
sym::mem_forget if !arg_ty.needs_drop(cx.tcx, cx.param_env) => {
(FORGET_NON_DROP, FORGET_NON_DROP_SUMMARY)
},
_ => return,
};
span_lint_and_note(
cx,
lint,
expr.span,
msg,
Some(arg.span),
&format!("argument has type `{}`", arg_ty),
);
}
}
}

View file

@ -0,0 +1,99 @@
use clippy_utils::{diagnostics::span_lint_and_then, source::snippet_opt};
use rustc_ast::ast::{Item, ItemKind, VariantData};
use rustc_errors::Applicability;
use rustc_lexer::TokenKind;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Finds structs without fields (a so-called "empty struct") that are declared with brackets.
///
/// ### Why is this bad?
/// Empty brackets after a struct declaration can be omitted.
///
/// ### Example
/// ```rust
/// struct Cookie {}
/// ```
/// Use instead:
/// ```rust
/// struct Cookie;
/// ```
#[clippy::version = "1.62.0"]
pub EMPTY_STRUCTS_WITH_BRACKETS,
restriction,
"finds struct declarations with empty brackets"
}
declare_lint_pass!(EmptyStructsWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS]);
impl EarlyLintPass for EmptyStructsWithBrackets {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
let span_after_ident = item.span.with_lo(item.ident.span.hi());
if let ItemKind::Struct(var_data, _) = &item.kind
&& has_brackets(var_data)
&& has_no_fields(cx, var_data, span_after_ident) {
span_lint_and_then(
cx,
EMPTY_STRUCTS_WITH_BRACKETS,
span_after_ident,
"found empty brackets on struct declaration",
|diagnostic| {
diagnostic.span_suggestion_hidden(
span_after_ident,
"remove the brackets",
";".to_string(),
Applicability::MachineApplicable);
},
);
}
}
}
fn has_no_ident_token(braces_span_str: &str) -> bool {
!rustc_lexer::tokenize(braces_span_str).any(|t| t.kind == TokenKind::Ident)
}
fn has_brackets(var_data: &VariantData) -> bool {
!matches!(var_data, VariantData::Unit(_))
}
fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool {
if !var_data.fields().is_empty() {
return false;
}
// there might still be field declarations hidden from the AST
// (conditionaly compiled code using #[cfg(..)])
let Some(braces_span_str) = snippet_opt(cx, braces_span) else {
return false;
};
has_no_ident_token(braces_span_str.as_ref())
}
#[cfg(test)]
mod unit_test {
use super::*;
#[test]
fn test_has_no_ident_token() {
let input = "{ field: u8 }";
assert!(!has_no_ident_token(input));
let input = "(u8, String);";
assert!(!has_no_ident_token(input));
let input = " {
// test = 5
}
";
assert!(has_no_ident_token(input));
let input = " ();";
assert!(has_no_ident_token(input));
}
}

View file

@ -5,7 +5,7 @@ use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use clippy_utils::consts::{constant_simple, Constant};
use clippy_utils::consts::{constant_full_int, constant_simple, Constant, FullInt};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{clip, unsext};
@ -54,6 +54,7 @@ impl<'tcx> LateLintPass<'tcx> for IdentityOp {
check(cx, left, -1, e.span, right.span);
check(cx, right, -1, e.span, left.span);
},
BinOpKind::Rem => check_remainder(cx, left, right, e.span, left.span),
_ => (),
}
}
@ -70,6 +71,18 @@ fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_
&& constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1)))
}
fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) {
let lhs_const = constant_full_int(cx, cx.typeck_results(), left);
let rhs_const = constant_full_int(cx, cx.typeck_results(), right);
if match (lhs_const, rhs_const) {
(Some(FullInt::S(lv)), Some(FullInt::S(rv))) => lv.abs() < rv.abs(),
(Some(FullInt::U(lv)), Some(FullInt::U(rv))) => lv < rv,
_ => return,
} {
span_ineffective_operation(cx, span, arg);
}
}
fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) {
if let Some(Constant::Int(v)) = constant_simple(cx, cx.typeck_results(), e).map(Constant::peel_refs) {
let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() {
@ -83,15 +96,19 @@ fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) {
1 => v == 1,
_ => unreachable!(),
} {
span_lint(
cx,
IDENTITY_OP,
span,
&format!(
"the operation is ineffective. Consider reducing it to `{}`",
snippet(cx, arg, "..")
),
);
span_ineffective_operation(cx, span, arg);
}
}
}
fn span_ineffective_operation(cx: &LateContext<'_>, span: Span, arg: Span) {
span_lint(
cx,
IDENTITY_OP,
span,
&format!(
"the operation is ineffective. Consider reducing it to `{}`",
snippet(cx, arg, "..")
),
);
}

View file

@ -96,6 +96,10 @@ declare_lint_pass!(IndexingSlicing => [INDEXING_SLICING, OUT_OF_BOUNDS_INDEXING]
impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if cx.tcx.hir().is_inside_const_context(expr.hir_id) {
return;
}
if let ExprKind::Index(array, index) = &expr.kind {
let ty = cx.typeck_results().expr_ty(array).peel_refs();
if let Some(range) = higher::Range::hir(index) {
@ -151,6 +155,10 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
} else {
// Catchall non-range index, i.e., [n] or [n << m]
if let ty::Array(..) = ty.kind() {
// Index is a const block.
if let ExprKind::ConstBlock(..) = index.kind {
return;
}
// Index is a constant uint.
if let Some(..) = constant(cx, cx.typeck_results(), index) {
// Let rustc's `const_err` lint handle constant `usize` indexing on arrays.

View file

@ -23,6 +23,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
LintId::of(booleans::LOGIC_BUG),
LintId::of(booleans::NONMINIMAL_BOOL),
LintId::of(casts::CAST_ABS_TO_UNSIGNED),
LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
LintId::of(casts::CAST_ENUM_TRUNCATION),
LintId::of(casts::CAST_REF_TO_MUT),
@ -37,6 +38,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(comparison_chain::COMPARISON_CHAIN),
LintId::of(copies::IFS_SAME_COND),
LintId::of(copies::IF_SAME_THEN_ELSE),
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
LintId::of(dereference::NEEDLESS_BORROW),
LintId::of(derivable_impls::DERIVABLE_IMPLS),
@ -49,9 +51,12 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(double_comparison::DOUBLE_COMPARISONS),
LintId::of(double_parens::DOUBLE_PARENS),
LintId::of(drop_forget_ref::DROP_COPY),
LintId::of(drop_forget_ref::DROP_NON_DROP),
LintId::of(drop_forget_ref::DROP_REF),
LintId::of(drop_forget_ref::FORGET_COPY),
LintId::of(drop_forget_ref::FORGET_NON_DROP),
LintId::of(drop_forget_ref::FORGET_REF),
LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS),
LintId::of(duration_subsec::DURATION_SUBSEC),
LintId::of(entry::MAP_ENTRY),
LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
@ -152,6 +157,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(methods::CHARS_NEXT_CMP),
LintId::of(methods::CLONE_DOUBLE_REF),
LintId::of(methods::CLONE_ON_COPY),
LintId::of(methods::ERR_EXPECT),
LintId::of(methods::EXPECT_FUN_CALL),
LintId::of(methods::EXTEND_WITH_DRAIN),
LintId::of(methods::FILTER_MAP_IDENTITY),
@ -175,6 +181,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
LintId::of(methods::MAP_FLATTEN),
LintId::of(methods::MAP_IDENTITY),
LintId::of(methods::NEEDLESS_OPTION_AS_DEREF),
LintId::of(methods::NEEDLESS_SPLITN),
LintId::of(methods::NEW_RET_NO_SELF),
LintId::of(methods::OK_EXPECT),
@ -224,7 +231,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(needless_bool::NEEDLESS_BOOL),
LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
LintId::of(needless_late_init::NEEDLESS_LATE_INIT),
LintId::of(needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF),
LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK),
LintId::of(needless_update::NEEDLESS_UPDATE),
LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
@ -296,7 +302,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(types::REDUNDANT_ALLOCATION),
LintId::of(types::TYPE_COMPLEXITY),
LintId::of(types::VEC_BOX),
LintId::of(undropped_manually_drops::UNDROPPED_MANUALLY_DROPS),
LintId::of(unicode::INVISIBLE_CHARACTERS),
LintId::of(uninit_vec::UNINIT_VEC),
LintId::of(unit_hash::UNIT_HASH),

View file

@ -44,6 +44,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(methods::MANUAL_SPLIT_ONCE),
LintId::of(methods::MAP_FLATTEN),
LintId::of(methods::MAP_IDENTITY),
LintId::of(methods::NEEDLESS_OPTION_AS_DEREF),
LintId::of(methods::NEEDLESS_SPLITN),
LintId::of(methods::OPTION_AS_REF_DEREF),
LintId::of(methods::OPTION_FILTER_MAP),
@ -60,7 +61,6 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(needless_bool::BOOL_COMPARISON),
LintId::of(needless_bool::NEEDLESS_BOOL),
LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
LintId::of(needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF),
LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK),
LintId::of(needless_update::NEEDLESS_UPDATE),
LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),

View file

@ -22,6 +22,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
LintId::of(drop_forget_ref::DROP_REF),
LintId::of(drop_forget_ref::FORGET_COPY),
LintId::of(drop_forget_ref::FORGET_REF),
LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS),
LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
LintId::of(eq_op::EQ_OP),
LintId::of(erasing_op::ERASING_OP),
@ -62,7 +63,6 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
LintId::of(transmute::WRONG_TRANSMUTE),
LintId::of(transmuting_null::TRANSMUTING_NULL),
LintId::of(undropped_manually_drops::UNDROPPED_MANUALLY_DROPS),
LintId::of(unicode::INVISIBLE_CHARACTERS),
LintId::of(uninit_vec::UNINIT_VEC),
LintId::of(unit_hash::UNIT_HASH),

View file

@ -70,6 +70,7 @@ store.register_lints(&[
cargo::REDUNDANT_FEATURE_NAMES,
cargo::WILDCARD_DEPENDENCIES,
case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
casts::CAST_ABS_TO_UNSIGNED,
casts::CAST_ENUM_CONSTRUCTOR,
casts::CAST_ENUM_TRUNCATION,
casts::CAST_LOSSLESS,
@ -97,6 +98,7 @@ store.register_lints(&[
copies::IF_SAME_THEN_ELSE,
copies::SAME_FUNCTIONS_IN_IF_CONDITION,
copy_iterator::COPY_ITERATOR,
crate_in_macro_def::CRATE_IN_MACRO_DEF,
create_dir::CREATE_DIR,
dbg_macro::DBG_MACRO,
default::DEFAULT_TRAIT_ACCESS,
@ -122,12 +124,16 @@ store.register_lints(&[
double_comparison::DOUBLE_COMPARISONS,
double_parens::DOUBLE_PARENS,
drop_forget_ref::DROP_COPY,
drop_forget_ref::DROP_NON_DROP,
drop_forget_ref::DROP_REF,
drop_forget_ref::FORGET_COPY,
drop_forget_ref::FORGET_NON_DROP,
drop_forget_ref::FORGET_REF,
drop_forget_ref::UNDROPPED_MANUALLY_DROPS,
duration_subsec::DURATION_SUBSEC,
else_if_without_else::ELSE_IF_WITHOUT_ELSE,
empty_enum::EMPTY_ENUM,
empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS,
entry::MAP_ENTRY,
enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT,
enum_variants::ENUM_VARIANT_NAMES,
@ -280,6 +286,7 @@ store.register_lints(&[
methods::CLONE_DOUBLE_REF,
methods::CLONE_ON_COPY,
methods::CLONE_ON_REF_PTR,
methods::ERR_EXPECT,
methods::EXPECT_FUN_CALL,
methods::EXPECT_USED,
methods::EXTEND_WITH_DRAIN,
@ -313,6 +320,7 @@ store.register_lints(&[
methods::MAP_FLATTEN,
methods::MAP_IDENTITY,
methods::MAP_UNWRAP_OR,
methods::NEEDLESS_OPTION_AS_DEREF,
methods::NEEDLESS_SPLITN,
methods::NEW_RET_NO_SELF,
methods::OK_EXPECT,
@ -384,7 +392,6 @@ store.register_lints(&[
needless_continue::NEEDLESS_CONTINUE,
needless_for_each::NEEDLESS_FOR_EACH,
needless_late_init::NEEDLESS_LATE_INIT,
needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF,
needless_pass_by_value::NEEDLESS_PASS_BY_VALUE,
needless_question_mark::NEEDLESS_QUESTION_MARK,
needless_update::NEEDLESS_UPDATE,
@ -505,7 +512,6 @@ store.register_lints(&[
types::TYPE_COMPLEXITY,
types::VEC_BOX,
undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS,
undropped_manually_drops::UNDROPPED_MANUALLY_DROPS,
unicode::INVISIBLE_CHARACTERS,
unicode::NON_ASCII_LITERAL,
unicode::UNICODE_NOT_NFC,

View file

@ -16,6 +16,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(default_union_representation::DEFAULT_UNION_REPRESENTATION),
LintId::of(disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS),
LintId::of(else_if_without_else::ELSE_IF_WITHOUT_ELSE),
LintId::of(empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS),
LintId::of(exhaustive_items::EXHAUSTIVE_ENUMS),
LintId::of(exhaustive_items::EXHAUSTIVE_STRUCTS),
LintId::of(exit::EXIT),

View file

@ -59,6 +59,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(methods::BYTES_NTH),
LintId::of(methods::CHARS_LAST_CMP),
LintId::of(methods::CHARS_NEXT_CMP),
LintId::of(methods::ERR_EXPECT),
LintId::of(methods::INTO_ITER_ON_REF),
LintId::of(methods::ITER_CLONED_COLLECT),
LintId::of(methods::ITER_NEXT_SLICE),

View file

@ -7,8 +7,12 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
LintId::of(casts::CAST_ABS_TO_UNSIGNED),
LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
LintId::of(casts::CAST_ENUM_TRUNCATION),
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
LintId::of(drop_forget_ref::DROP_NON_DROP),
LintId::of(drop_forget_ref::FORGET_NON_DROP),
LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),

View file

@ -1,5 +1,6 @@
// error-pattern:cargo-clippy
#![feature(array_windows)]
#![feature(binary_heap_into_iter_sorted)]
#![feature(box_patterns)]
#![feature(control_flow_enum)]
@ -190,6 +191,7 @@ mod collapsible_match;
mod comparison_chain;
mod copies;
mod copy_iterator;
mod crate_in_macro_def;
mod create_dir;
mod dbg_macro;
mod default;
@ -208,6 +210,7 @@ mod drop_forget_ref;
mod duration_subsec;
mod else_if_without_else;
mod empty_enum;
mod empty_structs_with_brackets;
mod entry;
mod enum_clike;
mod enum_variants;
@ -305,7 +308,6 @@ mod needless_borrowed_ref;
mod needless_continue;
mod needless_for_each;
mod needless_late_init;
mod needless_option_as_deref;
mod needless_pass_by_value;
mod needless_question_mark;
mod needless_update;
@ -375,7 +377,6 @@ mod transmuting_null;
mod try_err;
mod types;
mod undocumented_unsafe_blocks;
mod undropped_manually_drops;
mod unicode;
mod uninit_vec;
mod unit_hash;
@ -533,7 +534,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(ptr::Ptr));
store.register_late_pass(|| Box::new(ptr_eq::PtrEq));
store.register_late_pass(|| Box::new(needless_bool::NeedlessBool));
store.register_late_pass(|| Box::new(needless_option_as_deref::OptionNeedlessDeref));
store.register_late_pass(|| Box::new(needless_bool::BoolComparison));
store.register_late_pass(|| Box::new(needless_for_each::NeedlessForEach));
store.register_late_pass(|| Box::new(misc::MiscLints));
@ -812,7 +812,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || Box::new(disallowed_methods::DisallowedMethods::new(disallowed_methods.clone())));
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax));
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax));
store.register_late_pass(|| Box::new(undropped_manually_drops::UndroppedManuallyDrops));
store.register_late_pass(|| Box::new(strings::StrToString));
store.register_late_pass(|| Box::new(strings::StringToString));
store.register_late_pass(|| Box::new(zero_sized_map_values::ZeroSizedMapValues));
@ -847,7 +846,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
enable_raw_pointer_heuristic_for_send,
))
});
store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::default()));
store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks));
store.register_late_pass(|| Box::new(match_str_case_mismatch::MatchStrCaseMismatch));
store.register_late_pass(move || Box::new(format_args::FormatArgs));
store.register_late_pass(|| Box::new(trailing_empty_array::TrailingEmptyArray));
@ -867,6 +866,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
ignore_publish: cargo_ignore_publish,
})
});
store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
store.register_early_pass(|| Box::new(empty_structs_with_brackets::EmptyStructsWithBrackets));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -2,9 +2,12 @@ use super::SINGLE_ELEMENT_LOOP;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{indent_of, snippet_with_applicability};
use if_chain::if_chain;
use rustc_ast::util::parser::PREC_PREFIX;
use rustc_ast::Mutability;
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, Pat};
use rustc_hir::{is_range_literal, BorrowKind, Expr, ExprKind, Pat};
use rustc_lint::LateContext;
use rustc_span::edition::Edition;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
@ -13,31 +16,84 @@ pub(super) fn check<'tcx>(
body: &'tcx Expr<'_>,
expr: &'tcx Expr<'_>,
) {
let arg_expr = match arg.kind {
ExprKind::AddrOf(BorrowKind::Ref, _, ref_arg) => ref_arg,
ExprKind::MethodCall(method, [arg], _) if method.ident.name == rustc_span::sym::iter => arg,
let (arg_expression, prefix) = match arg.kind {
ExprKind::AddrOf(
BorrowKind::Ref,
Mutability::Not,
Expr {
kind: ExprKind::Array([arg]),
..
},
) => (arg, "&"),
ExprKind::AddrOf(
BorrowKind::Ref,
Mutability::Mut,
Expr {
kind: ExprKind::Array([arg]),
..
},
) => (arg, "&mut "),
ExprKind::MethodCall(
method,
[
Expr {
kind: ExprKind::Array([arg]),
..
},
],
_,
) if method.ident.name == rustc_span::sym::iter => (arg, "&"),
ExprKind::MethodCall(
method,
[
Expr {
kind: ExprKind::Array([arg]),
..
},
],
_,
) if method.ident.name.as_str() == "iter_mut" => (arg, "&mut "),
ExprKind::MethodCall(
method,
[
Expr {
kind: ExprKind::Array([arg]),
..
},
],
_,
) if method.ident.name == rustc_span::sym::into_iter => (arg, ""),
// Only check for arrays edition 2021 or later, as this case will trigger a compiler error otherwise.
ExprKind::Array([arg]) if cx.tcx.sess.edition() >= Edition::Edition2021 => (arg, ""),
_ => return,
};
if_chain! {
if let ExprKind::Array([arg_expression]) = arg_expr.kind;
if let ExprKind::Block(block, _) = body.kind;
if !block.stmts.is_empty();
then {
let mut applicability = Applicability::MachineApplicable;
let pat_snip = snippet_with_applicability(cx, pat.span, "..", &mut applicability);
let arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability);
let mut arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability);
let mut block_str = snippet_with_applicability(cx, block.span, "..", &mut applicability).into_owned();
block_str.remove(0);
block_str.pop();
let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0));
// Reference iterator from `&(mut) []` or `[].iter(_mut)()`.
if !prefix.is_empty() && (
// Precedence of internal expression is less than or equal to precedence of `&expr`.
arg_expression.precedence().order() <= PREC_PREFIX || is_range_literal(arg_expression)
) {
arg_snip = format!("({arg_snip})").into();
}
span_lint_and_sugg(
cx,
SINGLE_ELEMENT_LOOP,
expr.span,
"for loop over a single element",
"try",
format!("{{\n{}let {} = &{};{}}}", indent, pat_snip, arg_snip, block_str),
format!("{{\n{indent}let {pat_snip} = {prefix}{arg_snip};{block_str}}}"),
applicability,
)
}

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{iter_input_pats, method_chain_args};
use if_chain::if_chain;
@ -217,36 +217,33 @@ fn lint_map_unit_fn(cx: &LateContext<'_>, stmt: &hir::Stmt<'_>, expr: &hir::Expr
let fn_arg = &map_args[1];
if is_unit_function(cx, fn_arg) {
let mut applicability = Applicability::MachineApplicable;
let msg = suggestion_msg("function", map_type);
let suggestion = format!(
"if let {0}({binding}) = {1} {{ {2}({binding}) }}",
variant,
snippet(cx, var_arg.span, "_"),
snippet(cx, fn_arg.span, "_"),
snippet_with_applicability(cx, var_arg.span, "_", &mut applicability),
snippet_with_applicability(cx, fn_arg.span, "_", &mut applicability),
binding = let_binding_name(cx, var_arg)
);
span_lint_and_then(cx, lint, expr.span, &msg, |diag| {
diag.span_suggestion(stmt.span, "try this", suggestion, Applicability::MachineApplicable);
diag.span_suggestion(stmt.span, "try this", suggestion, applicability);
});
} else if let Some((binding, closure_expr)) = unit_closure(cx, fn_arg) {
let msg = suggestion_msg("closure", map_type);
span_lint_and_then(cx, lint, expr.span, &msg, |diag| {
if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) {
let mut applicability = Applicability::MachineApplicable;
let suggestion = format!(
"if let {0}({1}) = {2} {{ {3} }}",
variant,
snippet(cx, binding.pat.span, "_"),
snippet(cx, var_arg.span, "_"),
snippet(cx, reduced_expr_span, "_")
);
diag.span_suggestion(
stmt.span,
"try this",
suggestion,
Applicability::MachineApplicable, // snippet
snippet_with_applicability(cx, binding.pat.span, "_", &mut applicability),
snippet_with_applicability(cx, var_arg.span, "_", &mut applicability),
snippet_with_context(cx, reduced_expr_span, var_arg.span.ctxt(), "_", &mut applicability).0,
);
diag.span_suggestion(stmt.span, "try this", suggestion, applicability);
} else {
let suggestion = format!(
"if let {0}({1}) = {2} {{ ... }}",

View file

@ -667,7 +667,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
overlapping_arms::check(cx, ex, arms);
match_wild_enum::check(cx, ex, arms);
match_as_ref::check(cx, ex, arms, expr);
needless_match::check_match(cx, ex, arms);
needless_match::check_match(cx, ex, arms, expr);
if self.infallible_destructuring_match_linted {
self.infallible_destructuring_match_linted = false;

View file

@ -1,37 +1,25 @@
use super::NEEDLESS_MATCH;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{eq_expr_value, get_parent_expr, higher, is_else_clause, is_lang_ctor, peel_blocks_with_stmt};
use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
use clippy_utils::{
eq_expr_value, get_parent_expr_for_hir, get_parent_node, higher, is_else_clause, is_lang_ctor, over,
peel_blocks_with_stmt,
};
use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, Path, PathSegment, QPath, UnOp};
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, FnRetTy, Node, Pat, PatKind, Path, QPath};
use rustc_lint::LateContext;
use rustc_span::sym;
use rustc_typeck::hir_ty_to_ty;
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
// This is for avoiding collision with `match_single_binding`.
if arms.len() < 2 {
return;
}
for arm in arms {
if let PatKind::Wild = arm.pat.kind {
let ret_expr = strip_return(arm.body);
if !eq_expr_value(cx, ex, ret_expr) {
return;
}
} else if !pat_same_as_expr(arm.pat, arm.body) {
return;
}
}
if let Some(match_expr) = get_parent_expr(cx, ex) {
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
if arms.len() > 1 && expr_ty_matches_p_ty(cx, ex, expr) && check_all_arms(cx, ex, arms) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
NEEDLESS_MATCH,
match_expr.span,
expr.span,
"this match expression is unnecessary",
"replace it with",
snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(),
@ -60,11 +48,8 @@ pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>])
/// }
/// ```
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
if_chain! {
if let Some(ref if_let) = higher::IfLet::hir(cx, ex);
if !is_else_clause(cx.tcx, ex);
if check_if_let(cx, if_let);
then {
if let Some(ref if_let) = higher::IfLet::hir(cx, ex) {
if !is_else_clause(cx.tcx, ex) && expr_ty_matches_p_ty(cx, if_let.let_expr, ex) && check_if_let(cx, if_let) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
@ -79,6 +64,19 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
}
}
fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
for arm in arms {
let arm_expr = peel_blocks_with_stmt(arm.body);
if let PatKind::Wild = arm.pat.kind {
return eq_expr_value(cx, match_expr, strip_return(arm_expr));
} else if !pat_same_as_expr(arm.pat, arm_expr) {
return false;
}
}
true
}
fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
if let Some(if_else) = if_let.if_else {
if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
@ -92,18 +90,21 @@ fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
if matches!(if_else.kind, ExprKind::Block(..)) {
let else_expr = peel_blocks_with_stmt(if_else);
if matches!(else_expr.kind, ExprKind::Block(..)) {
return false;
}
let ret = strip_return(else_expr);
let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) {
if let ExprKind::Path(ref qpath) = ret.kind {
return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
}
} else {
return eq_expr_value(cx, if_let.let_expr, ret);
return true;
}
return true;
return eq_expr_value(cx, if_let.let_expr, ret);
}
}
false
}
@ -116,48 +117,70 @@ fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
}
}
/// Manually check for coercion casting by checking if the type of the match operand or let expr
/// differs with the assigned local variable or the funtion return type.
fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>) -> bool {
if let Some(p_node) = get_parent_node(cx.tcx, p_expr.hir_id) {
match p_node {
// Compare match_expr ty with local in `let local = match match_expr {..}`
Node::Local(local) => {
let results = cx.typeck_results();
return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr));
},
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
Node::Item(..) => {
if let Some(fn_decl) = p_node.fn_decl() {
if let FnRetTy::Return(ret_ty) = fn_decl.output {
return same_type_and_consts(hir_ty_to_ty(cx.tcx, ret_ty), cx.typeck_results().expr_ty(expr));
}
}
},
// check the parent expr for this whole block `{ match match_expr {..} }`
Node::Block(block) => {
if let Some(block_parent_expr) = get_parent_expr_for_hir(cx, block.hir_id) {
return expr_ty_matches_p_ty(cx, expr, block_parent_expr);
}
},
// recursively call on `if xxx {..}` etc.
Node::Expr(p_expr) => {
return expr_ty_matches_p_ty(cx, expr, p_expr);
},
_ => {},
}
}
false
}
fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
let expr = strip_return(expr);
match (&pat.kind, &expr.kind) {
// Example: `Some(val) => Some(val)`
(
PatKind::TupleStruct(QPath::Resolved(_, path), [first_pat, ..], _),
ExprKind::Call(call_expr, [first_param, ..]),
) => {
(PatKind::TupleStruct(QPath::Resolved(_, path), tuple_params, _), ExprKind::Call(call_expr, call_params)) => {
if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
if has_identical_segments(path.segments, call_path.segments)
&& has_same_non_ref_symbol(first_pat, first_param)
{
return true;
}
return over(path.segments, call_path.segments, |pat_seg, call_seg| {
pat_seg.ident.name == call_seg.ident.name
}) && same_non_ref_symbols(tuple_params, call_params);
}
},
// Example: `val => val`, or `ref val => *val`
(PatKind::Binding(annot, _, pat_ident, _), _) => {
let new_expr = if let (
BindingAnnotation::Ref | BindingAnnotation::RefMut,
ExprKind::Unary(UnOp::Deref, operand_expr),
) = (annot, &expr.kind)
{
operand_expr
} else {
expr
};
if let ExprKind::Path(QPath::Resolved(
// Example: `val => val`
(
PatKind::Binding(annot, _, pat_ident, _),
ExprKind::Path(QPath::Resolved(
_,
Path {
segments: [first_seg, ..],
..
},
)) = new_expr.kind
{
return pat_ident.name == first_seg.ident.name;
}
)),
) => {
return !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut)
&& pat_ident.name == first_seg.ident.name;
},
// Example: `Custom::TypeA => Custom::TypeB`, or `None => None`
(PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
return has_identical_segments(p_path.segments, e_path.segments);
return over(p_path.segments, e_path.segments, |p_seg, e_seg| {
p_seg.ident.name == e_seg.ident.name
});
},
// Example: `5 => 5`
(PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
@ -171,27 +194,16 @@ fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
false
}
fn has_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
if left_segs.len() != right_segs.len() {
fn same_non_ref_symbols(pats: &[Pat<'_>], exprs: &[Expr<'_>]) -> bool {
if pats.len() != exprs.len() {
return false;
}
for i in 0..left_segs.len() {
if left_segs[i].ident.name != right_segs[i].ident.name {
for i in 0..pats.len() {
if !pat_same_as_expr(&pats[i], &exprs[i]) {
return false;
}
}
true
}
fn has_same_non_ref_symbol(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
if_chain! {
if let PatKind::Binding(annot, _, pat_ident, _) = pat.kind;
if !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
if let ExprKind::Path(QPath::Resolved(_, Path {segments: [first_seg, ..], .. })) = expr.kind;
then {
return pat_ident.name == first_seg.ident.name;
}
}
false
}

View file

@ -22,7 +22,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx E
cx,
BYTES_NTH,
expr.span,
&format!("called `.byte().nth()` on a `{}`", caller_type),
&format!("called `.bytes().nth()` on a `{}`", caller_type),
"try",
format!(
"{}.as_bytes().get({})",

View file

@ -0,0 +1,60 @@
use super::ERR_EXPECT;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::ty::implements_trait;
use clippy_utils::{meets_msrv, msrvs, ty::is_type_diagnostic_item};
use rustc_errors::Applicability;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_middle::ty::Ty;
use rustc_semver::RustcVersion;
use rustc_span::{sym, Span};
pub(super) fn check(
cx: &LateContext<'_>,
_expr: &rustc_hir::Expr<'_>,
recv: &rustc_hir::Expr<'_>,
msrv: Option<&RustcVersion>,
expect_span: Span,
err_span: Span,
) {
if_chain! {
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
// Test the version to make sure the lint can be showed (expect_err has been
// introduced in rust 1.17.0 : https://github.com/rust-lang/rust/pull/38982)
if meets_msrv(msrv, &msrvs::EXPECT_ERR);
// Grabs the `Result<T, E>` type
let result_type = cx.typeck_results().expr_ty(recv);
// Tests if the T type in a `Result<T, E>` is not None
if let Some(data_type) = get_data_type(cx, result_type);
// Tests if the T type in a `Result<T, E>` implements debug
if has_debug_impl(data_type, cx);
then {
span_lint_and_sugg(
cx,
ERR_EXPECT,
err_span.to(expect_span),
"called `.err().expect()` on a `Result` value",
"try",
"expect_err".to_string(),
Applicability::MachineApplicable
);
}
};
}
/// Given a `Result<T, E>` type, return its data (`T`).
fn get_data_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option<Ty<'a>> {
match ty.kind() {
ty::Adt(_, substs) if is_type_diagnostic_item(cx, ty, sym::Result) => substs.types().next(),
_ => None,
}
}
/// Given a type, very if the Debug trait has been impl'd
fn has_debug_impl<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
cx.tcx
.get_diagnostic_item(sym::Debug)
.map_or(false, |debug| implements_trait(cx, ty, debug, &[]))
}

View file

@ -48,13 +48,11 @@ pub fn is_clone_like(cx: &LateContext<'_>, method_name: &str, method_def_id: hir
"to_os_string" => is_diag_item_method(cx, method_def_id, sym::OsStr),
"to_owned" => is_diag_trait_item(cx, method_def_id, sym::ToOwned),
"to_path_buf" => is_diag_item_method(cx, method_def_id, sym::Path),
"to_vec" => {
cx.tcx.impl_of_method(method_def_id)
.filter(|&impl_did| {
cx.tcx.type_of(impl_did).is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none()
})
.is_some()
},
"to_vec" => cx
.tcx
.impl_of_method(method_def_id)
.filter(|&impl_did| cx.tcx.type_of(impl_did).is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none())
.is_some(),
_ => false,
}
}

View file

@ -1,11 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::{get_iterator_item_ty, is_copy};
use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy};
use itertools::Itertools;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::sym;
use std::ops::Not;
use super::ITER_OVEREAGER_CLONED;
@ -20,9 +21,16 @@ pub(super) fn check<'tcx>(
map_arg: &[hir::Expr<'_>],
) {
// Check if it's iterator and get type associated with `Item`.
let inner_ty = match get_iterator_item_ty(cx, cx.typeck_results().expr_ty_adjusted(recv)) {
Some(ty) => ty,
_ => return,
let inner_ty = if_chain! {
if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
let recv_ty = cx.typeck_results().expr_ty(recv);
if implements_trait(cx, recv_ty, iterator_trait_id, &[]);
if let Some(inner_ty) = get_iterator_item_ty(cx, cx.typeck_results().expr_ty_adjusted(recv));
then {
inner_ty
} else {
return;
}
};
match inner_ty.kind() {

View file

@ -13,6 +13,7 @@ pub(super) fn check(
expr: &hir::Expr<'_>,
caller: &hir::Expr<'_>,
map_arg: &hir::Expr<'_>,
name: &str,
_map_span: Span,
) {
let caller_ty = cx.typeck_results().expr_ty(caller);
@ -29,7 +30,7 @@ pub(super) fn check(
MAP_IDENTITY,
sugg_span,
"unnecessary map of the identity function",
"remove the call to `map`",
&format!("remove the call to `{}`", name),
String::new(),
Applicability::MachineApplicable,
)

View file

@ -9,6 +9,7 @@ mod chars_next_cmp_with_unwrap;
mod clone_on_copy;
mod clone_on_ref_ptr;
mod cloned_instead_of_copied;
mod err_expect;
mod expect_fun_call;
mod expect_used;
mod extend_with_drain;
@ -40,6 +41,7 @@ mod map_collect_result_unit;
mod map_flatten;
mod map_identity;
mod map_unwrap_or;
mod needless_option_as_deref;
mod ok_expect;
mod option_as_ref_deref;
mod option_map_or_none;
@ -362,6 +364,29 @@ declare_clippy_lint! {
"using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `.err().expect()` calls on the `Result` type.
///
/// ### Why is this bad?
/// `.expect_err()` can be called directly to avoid the extra type conversion from `err()`.
///
/// ### Example
/// ```should_panic
/// let x: Result<u32, &str> = Ok(10);
/// x.err().expect("Testing err().expect()");
/// ```
/// Use instead:
/// ```should_panic
/// let x: Result<u32, &str> = Ok(10);
/// x.expect_err("Testing expect_err");
/// ```
#[clippy::version = "1.61.0"]
pub ERR_EXPECT,
style,
r#"using `.err().expect("")` when `.expect_err("")` can be used"#
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usages of `_.unwrap_or_else(Default::default)` on `Option` and
@ -2055,7 +2080,7 @@ declare_clippy_lint! {
/// Checks for use of `.collect::<Vec<String>>().join("")` on iterators.
///
/// ### Why is this bad?
/// `.collect::<String>()` is more concise and usually more performant
/// `.collect::<String>()` is more concise and might be more performant
///
/// ### Example
/// ```rust
@ -2070,15 +2095,42 @@ declare_clippy_lint! {
/// println!("{}", output);
/// ```
/// ### Known problems
/// While `.collect::<String>()` is more performant in most cases, there are cases where
/// While `.collect::<String>()` is sometimes more performant, there are cases where
/// using `.collect::<String>()` over `.collect::<Vec<String>>().join("")`
/// will prevent loop unrolling and will result in a negative performance impact.
///
/// Additionlly, differences have been observed between aarch64 and x86_64 assembly output,
/// with aarch64 tending to producing faster assembly in more cases when using `.collect::<String>()`
#[clippy::version = "1.61.0"]
pub UNNECESSARY_JOIN,
pedantic,
"using `.collect::<Vec<String>>().join(\"\")` on an iterator"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for no-op uses of `Option::{as_deref, as_deref_mut}`,
/// for example, `Option<&T>::as_deref()` returns the same type.
///
/// ### Why is this bad?
/// Redundant code and improving readability.
///
/// ### Example
/// ```rust
/// let a = Some(&1);
/// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32>
/// ```
/// Could be written as:
/// ```rust
/// let a = Some(&1);
/// let b = a;
/// ```
#[clippy::version = "1.57.0"]
pub NEEDLESS_OPTION_AS_DEREF,
complexity,
"no-op use of `deref` or `deref_mut` method to `Option`."
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Option<RustcVersion>,
@ -2165,6 +2217,8 @@ impl_lint_pass!(Methods => [
NEEDLESS_SPLITN,
UNNECESSARY_TO_OWNED,
UNNECESSARY_JOIN,
ERR_EXPECT,
NEEDLESS_OPTION_AS_DEREF,
]);
/// Extracts a method call name, args, and `Span` of the method name.
@ -2397,6 +2451,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
unnecessary_lazy_eval::check(cx, expr, recv, arg, "and");
}
},
("as_deref" | "as_deref_mut", []) => {
needless_option_as_deref::check(cx, expr, recv, name);
},
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
@ -2428,6 +2485,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
},
("expect", [_]) => match method_call(recv) {
Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
Some(("err", [recv], err_span)) => err_expect::check(cx, expr, recv, msrv, span, err_span),
_ => expect_used::check(cx, expr, recv),
},
("extend", [arg]) => {
@ -2472,7 +2530,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
}
}
},
("map", [m_arg]) => {
(name @ ("map" | "map_err"), [m_arg]) => {
if let Some((name, [recv2, args @ ..], span2)) = method_call(recv) {
match (name, args) {
("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, msrv),
@ -2484,7 +2542,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
_ => {},
}
}
map_identity::check(cx, expr, recv, m_arg, span);
map_identity::check(cx, expr, recv, m_arg, name, span);
},
("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map),
(name @ "next", args @ []) => {

View file

@ -0,0 +1,37 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::path_res;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::usage::local_used_after_expr;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_span::sym;
use super::NEEDLESS_OPTION_AS_DEREF;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, name: &str) {
let typeck = cx.typeck_results();
let outer_ty = typeck.expr_ty(expr);
if is_type_diagnostic_item(cx, outer_ty, sym::Option) && outer_ty == typeck.expr_ty(recv) {
if name == "as_deref_mut" && recv.is_syntactic_place_expr() {
let Res::Local(binding_id) = path_res(cx, recv) else { return };
if local_used_after_expr(cx, binding_id, recv) {
return;
}
}
span_lint_and_sugg(
cx,
NEEDLESS_OPTION_AS_DEREF,
expr.span,
"derefed type is same as origin",
"try this",
snippet_opt(cx, recv.span).unwrap(),
Applicability::MachineApplicable,
);
}
}

View file

@ -1,17 +1,14 @@
use std::{
ffi::OsString,
path::{Component, Path},
};
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{FileName, RealFileName, SourceFile, Span, SyntaxContext};
use std::ffi::OsStr;
use std::path::{Component, Path};
declare_clippy_lint! {
/// ### What it does
/// Checks that module layout uses only self named module files, bans mod.rs files.
/// Checks that module layout uses only self named module files, bans `mod.rs` files.
///
/// ### Why is this bad?
/// Having multiple module layout styles in a project can be confusing.
@ -40,7 +37,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks that module layout uses only mod.rs files.
/// Checks that module layout uses only `mod.rs` files.
///
/// ### Why is this bad?
/// Having multiple module layout styles in a project can be confusing.
@ -82,11 +79,7 @@ impl EarlyLintPass for ModStyle {
let files = cx.sess().source_map().files();
let trim_to_src = if let RealFileName::LocalPath(p) = &cx.sess().opts.working_dir {
p.to_string_lossy()
} else {
return;
};
let RealFileName::LocalPath(trim_to_src) = &cx.sess().opts.working_dir else { return };
// `folder_segments` is all unique folder path segments `path/to/foo.rs` gives
// `[path, to]` but not foo
@ -97,26 +90,27 @@ impl EarlyLintPass for ModStyle {
// `{ foo => path/to/foo.rs, .. }
let mut file_map = FxHashMap::default();
for file in files.iter() {
match &file.name {
FileName::Real(RealFileName::LocalPath(lp))
if lp.to_string_lossy().starts_with(trim_to_src.as_ref()) =>
{
let p = lp.to_string_lossy();
let path = Path::new(p.trim_start_matches(trim_to_src.as_ref()));
if let Some(stem) = path.file_stem() {
file_map.insert(stem.to_os_string(), (file, path.to_owned()));
}
process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders);
check_self_named_mod_exists(cx, path, file);
},
_ => {},
if let FileName::Real(RealFileName::LocalPath(lp)) = &file.name {
let path = if lp.is_relative() {
lp
} else if let Ok(relative) = lp.strip_prefix(trim_to_src) {
relative
} else {
continue;
};
if let Some(stem) = path.file_stem() {
file_map.insert(stem, (file, path));
}
process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders);
check_self_named_mod_exists(cx, path, file);
}
}
for folder in &folder_segments {
if !mod_folders.contains(folder) {
if let Some((file, path)) = file_map.get(folder) {
let mut correct = path.clone();
let mut correct = path.to_path_buf();
correct.pop();
correct.push(folder);
correct.push("mod.rs");
@ -138,25 +132,17 @@ impl EarlyLintPass for ModStyle {
/// For each `path` we add each folder component to `folder_segments` and if the file name
/// is `mod.rs` we add it's parent folder to `mod_folders`.
fn process_paths_for_mod_files(
path: &Path,
folder_segments: &mut FxHashSet<OsString>,
mod_folders: &mut FxHashSet<OsString>,
fn process_paths_for_mod_files<'a>(
path: &'a Path,
folder_segments: &mut FxHashSet<&'a OsStr>,
mod_folders: &mut FxHashSet<&'a OsStr>,
) {
let mut comp = path.components().rev().peekable();
let _ = comp.next();
if path.ends_with("mod.rs") {
mod_folders.insert(comp.peek().map(|c| c.as_os_str().to_owned()).unwrap_or_default());
mod_folders.insert(comp.peek().map(|c| c.as_os_str()).unwrap_or_default());
}
let folders = comp
.filter_map(|c| {
if let Component::Normal(s) = c {
Some(s.to_os_string())
} else {
None
}
})
.collect::<Vec<_>>();
let folders = comp.filter_map(|c| if let Component::Normal(s) = c { Some(s) } else { None });
folder_segments.extend(folders);
}

View file

@ -1,65 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_type_diagnostic_item;
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::symbol::sym;
declare_clippy_lint! {
/// ### What it does
/// Checks for no-op uses of Option::{as_deref,as_deref_mut},
/// for example, `Option<&T>::as_deref()` returns the same type.
///
/// ### Why is this bad?
/// Redundant code and improving readability.
///
/// ### Example
/// ```rust
/// let a = Some(&1);
/// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32>
/// ```
/// Could be written as:
/// ```rust
/// let a = Some(&1);
/// let b = a;
/// ```
#[clippy::version = "1.57.0"]
pub NEEDLESS_OPTION_AS_DEREF,
complexity,
"no-op use of `deref` or `deref_mut` method to `Option`."
}
declare_lint_pass!(OptionNeedlessDeref=> [
NEEDLESS_OPTION_AS_DEREF,
]);
impl<'tcx> LateLintPass<'tcx> for OptionNeedlessDeref {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if expr.span.from_expansion() {
return;
}
let typeck = cx.typeck_results();
let outer_ty = typeck.expr_ty(expr);
if_chain! {
if is_type_diagnostic_item(cx,outer_ty,sym::Option);
if let ExprKind::MethodCall(path, [sub_expr], _) = expr.kind;
let symbol = path.ident.as_str();
if symbol == "as_deref" || symbol == "as_deref_mut";
if outer_ty == typeck.expr_ty(sub_expr);
then{
span_lint_and_sugg(
cx,
NEEDLESS_OPTION_AS_DEREF,
expr.span,
"derefed type is same as origin",
"try this",
snippet_opt(cx,sub_expr.span).unwrap(),
Applicability::MachineApplicable
);
}
}
}
}

View file

@ -78,6 +78,10 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
if is_panic(cx, macro_call.def_id) {
if cx.tcx.hir().is_inside_const_context(expr.hir_id) {
return;
}
span_lint(
cx,
PANIC,

View file

@ -601,9 +601,7 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args:
},
// If the types match check for methods which exist on both types. e.g. `Vec::len` and
// `slice::len`
ty::Adt(def, _)
if def.did() == args.ty_did =>
{
ty::Adt(def, _) if def.did() == args.ty_did => {
set_skip_flag();
},
_ => (),

View file

@ -410,9 +410,10 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::transmute, def_id);
then {
// Avoid suggesting from/to bits and dereferencing raw pointers in const contexts.
// See https://github.com/rust-lang/rust/issues/73736 for progress on making them `const fn`.
// And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers.
// Avoid suggesting non-const operations in const contexts:
// - from/to bits (https://github.com/rust-lang/rust/issues/73736)
// - dereferencing raw pointers (https://github.com/rust-lang/rust/issues/51911)
// - char conversions (https://github.com/rust-lang/rust/issues/89259)
let const_context = in_constant(cx, e.hir_id);
let from_ty = cx.typeck_results().expr_ty_adjusted(arg);
@ -427,7 +428,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
let linted = wrong_transmute::check(cx, e, from_ty, to_ty)
| crosspointer_transmute::check(cx, e, from_ty, to_ty)
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, qpath)
| transmute_int_to_char::check(cx, e, from_ty, to_ty, arg)
| transmute_int_to_char::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg)
| transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg)

View file

@ -15,9 +15,10 @@ pub(super) fn check<'tcx>(
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
const_context: bool,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) => {
(ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) if !const_context => {
span_lint_and_then(
cx,
TRANSMUTE_INT_TO_CHAR,

View file

@ -32,18 +32,20 @@ pub(super) fn check<'tcx>(
""
};
let snippet = snippet(cx, arg.span, "..");
span_lint_and_sugg(
cx,
TRANSMUTE_BYTES_TO_STR,
e.span,
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
"consider using",
format!(
"std::str::from_utf8{}({}).unwrap()",
postfix,
snippet(cx, arg.span, ".."),
),
Applicability::Unspecified,
if const_context {
format!("std::str::from_utf8_unchecked{postfix}({snippet})")
} else {
format!("std::str::from_utf8{postfix}({snippet}).unwrap()")
},
Applicability::MaybeIncorrect,
);
triggered = true;
} else {

View file

@ -1,16 +1,13 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_lint_allowed;
use clippy_utils::source::{indent_of, reindent_multiline, snippet};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, HirId, Local, UnsafeSource};
use rustc_lexer::TokenKind;
use rustc_lint::{LateContext, LateLintPass};
use clippy_utils::source::walk_span_to_context;
use rustc_data_structures::sync::Lrc;
use rustc_hir::{Block, BlockCheckMode, UnsafeSource};
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::TyCtxt;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{BytePos, Span};
use std::borrow::Cow;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{BytePos, Pos, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
@ -18,6 +15,24 @@ declare_clippy_lint! {
/// explaining why the unsafe operations performed inside
/// the block are safe.
///
/// Note the comment must appear on the line(s) preceding the unsafe block
/// with nothing appearing in between. The following is ok:
/// ```ignore
/// foo(
/// // SAFETY:
/// // This is a valid safety comment
/// unsafe { *x }
/// )
/// ```
/// But neither of these are:
/// ```ignore
/// // SAFETY:
/// // This is not a valid safety comment
/// foo(
/// /* SAFETY: Neither is this */ unsafe { *x },
/// );
/// ```
///
/// ### Why is this bad?
/// Undocumented unsafe blocks can make it difficult to
/// read and maintain code, as well as uncover unsoundness
@ -44,179 +59,139 @@ declare_clippy_lint! {
"creating an unsafe block without explaining why it is safe"
}
impl_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS]);
#[derive(Default)]
pub struct UndocumentedUnsafeBlocks {
pub local_level: u32,
pub local_span: Option<Span>,
// The local was already checked for an overall safety comment
// There is no need to continue checking the blocks in the local
pub local_checked: bool,
// Since we can only check the blocks from expanded macros
// We have to omit the suggestion due to the actual definition
// Not being available to us
pub macro_expansion: bool,
}
declare_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS]);
impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
fn check_block(&mut self, cx: &LateContext<'_>, block: &'_ Block<'_>) {
if_chain! {
if !self.local_checked;
if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id);
if !in_external_macro(cx.tcx.sess, block.span);
if let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules;
if let Some(enclosing_scope_hir_id) = cx.tcx.hir().get_enclosing_scope(block.hir_id);
if self.block_has_safety_comment(cx.tcx, enclosing_scope_hir_id, block.span) == Some(false);
then {
let mut span = block.span;
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
&& !in_external_macro(cx.tcx.sess, block.span)
&& !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id)
&& !is_unsafe_from_proc_macro(cx, block)
&& !block_has_safety_comment(cx, block)
{
let source_map = cx.tcx.sess.source_map();
let span = if source_map.is_multiline(block.span) {
source_map.span_until_char(block.span, '\n')
} else {
block.span
};
if let Some(local_span) = self.local_span {
span = local_span;
let result = self.block_has_safety_comment(cx.tcx, enclosing_scope_hir_id, span);
if result.unwrap_or(true) {
self.local_checked = true;
return;
}
}
self.lint(cx, span);
}
}
}
fn check_local(&mut self, cx: &LateContext<'_>, local: &'_ Local<'_>) {
if_chain! {
if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, local.hir_id);
if !in_external_macro(cx.tcx.sess, local.span);
if let Some(init) = local.init;
then {
self.visit_expr(init);
if self.local_level > 0 {
self.local_span = Some(local.span);
}
}
}
}
fn check_block_post(&mut self, _: &LateContext<'_>, _: &'_ Block<'_>) {
self.local_level = self.local_level.saturating_sub(1);
if self.local_level == 0 {
self.local_checked = false;
self.local_span = None;
}
}
}
impl<'v> Visitor<'v> for UndocumentedUnsafeBlocks {
fn visit_expr(&mut self, ex: &'v Expr<'v>) {
match ex.kind {
ExprKind::Block(_, _) => self.local_level = self.local_level.saturating_add(1),
_ => walk_expr(self, ex),
}
}
}
impl UndocumentedUnsafeBlocks {
fn block_has_safety_comment(&mut self, tcx: TyCtxt<'_>, enclosing_hir_id: HirId, block_span: Span) -> Option<bool> {
let map = tcx.hir();
let source_map = tcx.sess.source_map();
let enclosing_scope_span = map.opt_span(enclosing_hir_id)?;
let between_span = if block_span.from_expansion() {
self.macro_expansion = true;
enclosing_scope_span.with_hi(block_span.hi()).source_callsite()
} else {
self.macro_expansion = false;
enclosing_scope_span.to(block_span).source_callsite()
};
let file_name = source_map.span_to_filename(between_span);
let source_file = source_map.get_source_file(&file_name)?;
let lex_start = (between_span.lo().0 - source_file.start_pos.0 + 1) as usize;
let lex_end = (between_span.hi().0 - source_file.start_pos.0) as usize;
let src_str = source_file.src.as_ref()?[lex_start..lex_end].to_string();
let source_start_pos = source_file.start_pos.0 as usize + lex_start;
let mut pos = 0;
let mut comment = false;
for token in rustc_lexer::tokenize(&src_str) {
match token.kind {
TokenKind::LineComment { doc_style: None }
| TokenKind::BlockComment {
doc_style: None,
terminated: true,
} => {
let comment_str = src_str[pos + 2..pos + token.len].to_ascii_uppercase();
if comment_str.contains("SAFETY:") {
comment = true;
}
},
// We need to add all whitespace to `pos` before checking the comment's line number
TokenKind::Whitespace => {},
_ => {
if comment {
// Get the line number of the "comment" (really wherever the trailing whitespace ended)
let comment_line_num = source_file
.lookup_file_pos(BytePos((source_start_pos + pos).try_into().unwrap()))
.0;
// Find the block/local's line number
let block_line_num = tcx.sess.source_map().lookup_char_pos(block_span.lo()).line;
// Check the comment is immediately followed by the block/local
if block_line_num == comment_line_num + 1 || block_line_num == comment_line_num {
return Some(true);
}
comment = false;
}
},
}
pos += token.len;
}
Some(false)
}
fn lint(&self, cx: &LateContext<'_>, mut span: Span) {
let source_map = cx.tcx.sess.source_map();
if source_map.is_multiline(span) {
span = source_map.span_until_char(span, '\n');
}
if self.macro_expansion {
span_lint_and_help(
cx,
UNDOCUMENTED_UNSAFE_BLOCKS,
span,
"unsafe block in macro expansion missing a safety comment",
None,
"consider adding a safety comment in the macro definition",
);
} else {
let block_indent = indent_of(cx, span);
let suggestion = format!("// SAFETY: ...\n{}", snippet(cx, span, ".."));
span_lint_and_sugg(
cx,
UNDOCUMENTED_UNSAFE_BLOCKS,
span,
"unsafe block missing a safety comment",
"consider adding a safety comment",
reindent_multiline(Cow::Borrowed(&suggestion), true, block_indent).to_string(),
Applicability::HasPlaceholders,
None,
"consider adding a safety comment on the preceding line",
);
}
}
}
fn is_unsafe_from_proc_macro(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
let source_map = cx.sess().source_map();
let file_pos = source_map.lookup_byte_offset(block.span.lo());
file_pos
.sf
.src
.as_deref()
.and_then(|src| src.get(file_pos.pos.to_usize()..))
.map_or(true, |src| !src.starts_with("unsafe"))
}
/// Checks if the lines immediately preceding the block contain a safety comment.
fn block_has_safety_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
// This intentionally ignores text before the start of a function so something like:
// ```
// // SAFETY: reason
// fn foo() { unsafe { .. } }
// ```
// won't work. This is to avoid dealing with where such a comment should be place relative to
// attributes and doc comments.
let source_map = cx.sess().source_map();
let ctxt = block.span.ctxt();
if ctxt != SyntaxContext::root() {
// From a macro expansion. Get the text from the start of the macro declaration to start of the unsafe block.
// macro_rules! foo { () => { stuff }; (x) => { unsafe { stuff } }; }
// ^--------------------------------------------^
if let Ok(unsafe_line) = source_map.lookup_line(block.span.lo())
&& let Ok(macro_line) = source_map.lookup_line(ctxt.outer_expn_data().def_site.lo())
&& Lrc::ptr_eq(&unsafe_line.sf, &macro_line.sf)
&& let Some(src) = unsafe_line.sf.src.as_deref()
{
macro_line.line < unsafe_line.line && text_has_safety_comment(
src,
&unsafe_line.sf.lines[macro_line.line + 1..=unsafe_line.line],
unsafe_line.sf.start_pos.to_usize(),
)
} else {
// Problem getting source text. Pretend a comment was found.
true
}
} else if let Ok(unsafe_line) = source_map.lookup_line(block.span.lo())
&& let Some(body) = cx.enclosing_body
&& let Some(body_span) = walk_span_to_context(cx.tcx.hir().body(body).value.span, SyntaxContext::root())
&& let Ok(body_line) = source_map.lookup_line(body_span.lo())
&& Lrc::ptr_eq(&unsafe_line.sf, &body_line.sf)
&& let Some(src) = unsafe_line.sf.src.as_deref()
{
// Get the text from the start of function body to the unsafe block.
// fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
// ^-------------^
body_line.line < unsafe_line.line && text_has_safety_comment(
src,
&unsafe_line.sf.lines[body_line.line + 1..=unsafe_line.line],
unsafe_line.sf.start_pos.to_usize(),
)
} else {
// Problem getting source text. Pretend a comment was found.
true
}
}
/// Checks if the given text has a safety comment for the immediately proceeding line.
fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) -> bool {
let mut lines = line_starts
.array_windows::<2>()
.rev()
.map_while(|[start, end]| {
src.get(start.to_usize() - offset..end.to_usize() - offset)
.map(|text| (start.to_usize(), text.trim_start()))
})
.filter(|(_, text)| !text.is_empty());
let Some((line_start, line)) = lines.next() else {
return false;
};
// Check for a sequence of line comments.
if line.starts_with("//") {
let mut line = line;
loop {
if line.to_ascii_uppercase().contains("SAFETY:") {
return true;
}
match lines.next() {
Some((_, x)) if x.starts_with("//") => line = x,
_ => return false,
}
}
}
// No line comments; look for the start of a block comment.
// This will only find them if they are at the start of a line.
let (mut line_start, mut line) = (line_start, line);
loop {
if line.starts_with("/*") {
let src = src[line_start..line_starts.last().unwrap().to_usize()].trim_start();
let mut tokens = tokenize(src);
return src[..tokens.next().unwrap().len]
.to_ascii_uppercase()
.contains("SAFETY:")
&& tokens.all(|t| t.kind == TokenKind::Whitespace);
}
match lines.next() {
Some(x) => (line_start, line) = x,
None => return false,
}
}
}

View file

@ -1,59 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::path_res;
use clippy_utils::ty::is_type_lang_item;
use rustc_hir::{lang_items, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Prevents the safe `std::mem::drop` function from being called on `std::mem::ManuallyDrop`.
///
/// ### Why is this bad?
/// The safe `drop` function does not drop the inner value of a `ManuallyDrop`.
///
/// ### Known problems
/// Does not catch cases if the user binds `std::mem::drop`
/// to a different name and calls it that way.
///
/// ### Example
/// ```rust
/// struct S;
/// drop(std::mem::ManuallyDrop::new(S));
/// ```
/// Use instead:
/// ```rust
/// struct S;
/// unsafe {
/// std::mem::ManuallyDrop::drop(&mut std::mem::ManuallyDrop::new(S));
/// }
/// ```
#[clippy::version = "1.49.0"]
pub UNDROPPED_MANUALLY_DROPS,
correctness,
"use of safe `std::mem::drop` function to drop a std::mem::ManuallyDrop, which will not drop the inner value"
}
declare_lint_pass!(UndroppedManuallyDrops => [UNDROPPED_MANUALLY_DROPS]);
impl<'tcx> LateLintPass<'tcx> for UndroppedManuallyDrops {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Call(fun, [arg_0, ..]) = expr.kind;
if path_res(cx, fun).opt_def_id() == cx.tcx.get_diagnostic_item(sym::mem_drop);
let ty = cx.typeck_results().expr_ty(arg_0);
if is_type_lang_item(cx, ty, lang_items::LangItem::ManuallyDrop);
then {
span_lint_and_help(
cx,
UNDROPPED_MANUALLY_DROPS,
expr.span,
"the inner value of this ManuallyDrop will not be dropped",
None,
"to drop a `ManuallyDrop<T>`, use std::mem::ManuallyDrop::drop",
);
}
}
}
}

View file

@ -34,7 +34,7 @@ declare_clippy_lint! {
///
/// ### Example
/// ```rust
/// struct Foo {}
/// struct Foo;
/// impl Foo {
/// fn new() -> Foo {
/// Foo {}
@ -43,7 +43,7 @@ declare_clippy_lint! {
/// ```
/// could be
/// ```rust
/// struct Foo {}
/// struct Foo;
/// impl Foo {
/// fn new() -> Self {
/// Self {}

View file

@ -156,7 +156,7 @@ define_Conf! {
///
/// Suppress lints whenever the suggested change would cause breakage for other crates.
(avoid_breaking_exported_api: bool = true),
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS.
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED.
///
/// The minimum rust version that the project supports
(msrv: Option<String> = None),

View file

@ -25,7 +25,7 @@ use rustc_hir::{
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
use rustc_middle::hir::nested_filter;
use rustc_middle::mir::interpret::ConstValue;
use rustc_middle::ty::{self, subst::GenericArgKind};
use rustc_middle::ty::{self, fast_reject::SimplifiedTypeGen, subst::GenericArgKind, FloatTy};
use rustc_semver::RustcVersion;
use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Spanned;
@ -889,7 +889,7 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Ve
}
}
},
Res::Def(DefKind::Const | DefKind::Static, def_id) => {
Res::Def(DefKind::Const | DefKind::Static(..), def_id) => {
if let Some(Node::Item(item)) = cx.tcx.hir().get_if_local(def_id) {
if let ItemKind::Const(.., body_id) | ItemKind::Static(.., body_id) = item.kind {
let body = cx.tcx.hir().body(body_id);
@ -934,7 +934,16 @@ pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
// implementations of native types. Check lang items.
let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect();
let lang_items = cx.tcx.lang_items();
for item_def_id in lang_items.items().iter().flatten() {
// This list isn't complete, but good enough for our current list of paths.
let incoherent_impls = [
SimplifiedTypeGen::FloatSimplifiedType(FloatTy::F32),
SimplifiedTypeGen::FloatSimplifiedType(FloatTy::F64),
SimplifiedTypeGen::SliceSimplifiedType,
SimplifiedTypeGen::StrSimplifiedType,
]
.iter()
.flat_map(|&ty| cx.tcx.incoherent_impls(ty));
for item_def_id in lang_items.items().iter().flatten().chain(incoherent_impls) {
let lang_item_path = cx.get_def_path(*item_def_id);
if path_syms.starts_with(&lang_item_path) {
if let [item] = &path_syms[lang_item_path.len()..] {

View file

@ -756,7 +756,7 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for LintResolver<'a, 'hir> {
let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(expr));
if match_type(self.cx, expr_ty, &paths::LINT);
then {
if let hir::def::Res::Def(DefKind::Static, _) = path.res {
if let hir::def::Res::Def(DefKind::Static(..), _) = path.res {
let lint_name = last_path_segment(qpath).ident.name;
self.lints.push(sym_to_string(lint_name).to_ascii_lowercase());
} else if let Some(local) = get_parent_local(self.cx, expr) {

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_utils"
version = "0.1.61"
version = "0.1.62"
edition = "2021"
publish = false

View file

@ -77,19 +77,22 @@ use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
use rustc_hir::itemlikevisit::ItemLikeVisitor;
use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk};
use rustc_hir::{
def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Constness, Destination, Expr,
ExprKind, FnDecl, ForeignItem, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local,
MatchSource, Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind,
TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Constness, Destination, Expr, ExprKind, FnDecl,
ForeignItem, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource,
Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind,
TraitRef, TyKind, UnOp,
};
use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::place::PlaceBase;
use rustc_middle::ty as rustc_ty;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
use rustc_middle::ty::binding::BindingMode;
use rustc_middle::ty::{IntTy, UintTy, FloatTy};
use rustc_middle::ty::fast_reject::SimplifiedTypeGen::*;
use rustc_middle::ty::fast_reject::SimplifiedTypeGen::{
ArraySimplifiedType, BoolSimplifiedType, CharSimplifiedType, FloatSimplifiedType, IntSimplifiedType,
PtrSimplifiedType, SliceSimplifiedType, StrSimplifiedType, UintSimplifiedType,
};
use rustc_middle::ty::{layout::IntegerExt, BorrowKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeFoldable, UpvarCapture};
use rustc_middle::ty::{FloatTy, IntTy, UintTy};
use rustc_semver::RustcVersion;
use rustc_session::Session;
use rustc_span::hygiene::{ExpnKind, MacroKind};
@ -522,7 +525,7 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
let tcx = cx.tcx;
let starts = find_primitive(tcx, base)
.chain(find_crate(tcx, base))
.flat_map(|id| item_child_by_name(tcx, id, first));
.filter_map(|id| item_child_by_name(tcx, id, first));
for first in starts {
let last = path

View file

@ -14,7 +14,7 @@ macro_rules! msrv_aliases {
msrv_aliases! {
1,53,0 { OR_PATTERNS, MANUAL_BITS }
1,52,0 { STR_SPLIT_ONCE }
1,51,0 { BORROW_AS_PTR }
1,51,0 { BORROW_AS_PTR, UNSIGNED_ABS }
1,50,0 { BOOL_THEN }
1,47,0 { TAU }
1,46,0 { CONST_IF_MATCH }
@ -30,6 +30,6 @@ msrv_aliases! {
1,34,0 { TRY_FROM }
1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
1,28,0 { FROM_BOOL }
1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST }
1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR }
1,16,0 { STR_REPEAT }
}

View file

@ -105,6 +105,8 @@ pub const PTR_READ_UNALIGNED: [&str; 3] = ["core", "ptr", "read_unaligned"];
pub const PTR_READ_VOLATILE: [&str; 3] = ["core", "ptr", "read_volatile"];
pub const PTR_REPLACE: [&str; 3] = ["core", "ptr", "replace"];
pub const PTR_SWAP: [&str; 3] = ["core", "ptr", "swap"];
pub const PTR_UNALIGNED_VOLATILE_LOAD: [&str; 3] = ["core", "intrinsics", "unaligned_volatile_load"];
pub const PTR_UNALIGNED_VOLATILE_STORE: [&str; 3] = ["core", "intrinsics", "unaligned_volatile_store"];
pub const PTR_WRITE: [&str; 3] = ["core", "ptr", "write"];
pub const PTR_WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];
pub const PTR_WRITE_UNALIGNED: [&str; 3] = ["core", "ptr", "write_unaligned"];

View file

@ -121,4 +121,25 @@ happened a stable backport, make sure to re-merge those changes just as with the
For this see the document on [how to update the changelog].
If you don't have time to do a complete changelog update right away, just update
the following parts:
- Remove the `(beta)` from the new stable version:
```markdown
## Rust 1.XX (beta) -> ## Rust 1.XX
```
- Update the release date line of the new stable version:
```markdown
Current beta, release 20YY-MM-DD -> Current stable, released 20YY-MM-DD
```
- Update the release date line of the previous stable version:
```markdown
Current stable, released 20YY-MM-DD -> Released 20YY-MM-DD
```
[how to update the changelog]: https://github.com/rust-lang/rust-clippy/blob/master/doc/changelog_update.md

View file

@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2022-03-24"
channel = "nightly-2022-04-07"
components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]

View file

@ -165,8 +165,7 @@ fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
// Separate the output with an empty line
eprintln!();
let fallback_bundle = rustc_errors::fallback_fluent_bundle(false)
.expect("failed to load fallback fluent bundle");
let fallback_bundle = rustc_errors::fallback_fluent_bundle(false).expect("failed to load fallback fluent bundle");
let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
rustc_errors::ColorConfig::Auto,
None,

View file

@ -1,19 +1,19 @@
error: `mod.rs` files are required, found `/bad/inner.rs`
error: `mod.rs` files are required, found `bad/inner.rs`
--> $DIR/bad/inner.rs:1:1
|
LL | pub mod stuff;
| ^
|
= note: `-D clippy::self-named-module-files` implied by `-D warnings`
= help: move `/bad/inner.rs` to `/bad/inner/mod.rs`
= help: move `bad/inner.rs` to `bad/inner/mod.rs`
error: `mod.rs` files are required, found `/bad/inner/stuff.rs`
error: `mod.rs` files are required, found `bad/inner/stuff.rs`
--> $DIR/bad/inner/stuff.rs:1:1
|
LL | pub mod most;
| ^
|
= help: move `/bad/inner/stuff.rs` to `/bad/inner/stuff/mod.rs`
= help: move `bad/inner/stuff.rs` to `bad/inner/stuff/mod.rs`
error: aborting due to 2 previous errors

View file

@ -1,11 +1,11 @@
error: `mod.rs` files are not allowed, found `/bad/mod.rs`
error: `mod.rs` files are not allowed, found `bad/mod.rs`
--> $DIR/bad/mod.rs:1:1
|
LL | pub struct Thing;
| ^
|
= note: `-D clippy::mod-module-files` implied by `-D warnings`
= help: move `/bad/mod.rs` to `/bad.rs`
= help: move `bad/mod.rs` to `bad.rs`
error: aborting due to previous error

View file

@ -46,11 +46,6 @@ LL | | report_in_external_macro: true
LL | | }
| |_^
|
note: the lint level is defined here
--> $DIR/check_clippy_version_attribute.rs:1:9
|
LL | #![deny(clippy::internal)]
| ^^^^^^^^^^^^^^^^
= note: `#[deny(clippy::missing_clippy_version_attribute)]` implied by `#[deny(clippy::internal)]`
= help: please use a `clippy::version` attribute, see `doc/adding_lints.md`
= note: this error originates in the macro `$crate::declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -4,6 +4,6 @@ struct S {
a: bool,
}
struct Foo {}
struct Foo;
fn main() {}

View file

@ -0,0 +1,18 @@
// compile-flags: --emit=link
// no-prefer-dynamic
#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::{Delimiter, Group, Ident, TokenStream, TokenTree};
#[proc_macro]
pub fn unsafe_block(input: TokenStream) -> TokenStream {
let span = input.into_iter().next().unwrap().span();
TokenStream::from_iter([TokenTree::Ident(Ident::new("unsafe", span)), {
let mut group = Group::new(Delimiter::Brace, TokenStream::new());
group.set_span(span);
TokenTree::Group(group)
}])
}

View file

@ -1,4 +1,4 @@
error: called `.byte().nth()` on a `String`
error: called `.bytes().nth()` on a `String`
--> $DIR/bytes_nth.rs:8:13
|
LL | let _ = s.bytes().nth(3);
@ -6,13 +6,13 @@ LL | let _ = s.bytes().nth(3);
|
= note: `-D clippy::bytes-nth` implied by `-D warnings`
error: called `.byte().nth()` on a `String`
error: called `.bytes().nth()` on a `String`
--> $DIR/bytes_nth.rs:9:14
|
LL | let _ = &s.bytes().nth(3);
| ^^^^^^^^^^^^^^^^ help: try: `s.as_bytes().get(3)`
error: called `.byte().nth()` on a `str`
error: called `.bytes().nth()` on a `str`
--> $DIR/bytes_nth.rs:10:13
|
LL | let _ = s[..].bytes().nth(3);

View file

@ -2,7 +2,7 @@
use std::string::String;
struct TestStruct {}
struct TestStruct;
impl TestStruct {
fn ends_with(self, arg: &str) {}

View file

@ -7,7 +7,7 @@
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
#[allow(clippy::no_effect, clippy::unnecessary_operation)]
#[allow(clippy::cast_abs_to_unsigned, clippy::no_effect, clippy::unnecessary_operation)]
fn main() {
// Test clippy::cast_precision_loss
let x0 = 1i32;

View file

@ -0,0 +1,8 @@
// run-rustfix
#![warn(clippy::cast_abs_to_unsigned)]
fn main() {
let x: i32 = -42;
let y: u32 = x.unsigned_abs();
println!("The absolute value of {} is {}", x, y);
}

View file

@ -0,0 +1,8 @@
// run-rustfix
#![warn(clippy::cast_abs_to_unsigned)]
fn main() {
let x: i32 = -42;
let y: u32 = x.abs() as u32;
println!("The absolute value of {} is {}", x, y);
}

View file

@ -0,0 +1,10 @@
error: casting the result of `i32::abs()` to u32
--> $DIR/cast_abs_to_unsigned.rs:6:18
|
LL | let y: u32 = x.abs() as u32;
| ^^^^^^^^^^^^^^ help: replace with: `x.unsigned_abs()`
|
= note: `-D clippy::cast-abs-to-unsigned` implied by `-D warnings`
error: aborting due to previous error

View file

@ -1,6 +1,7 @@
//! Test casts for alignment issues
#![feature(rustc_private)]
#![feature(core_intrinsics)]
extern crate libc;
#[warn(clippy::cast_ptr_alignment)]
@ -34,4 +35,17 @@ fn main() {
(&1u32 as *const u32 as *const libc::c_void) as *const u32;
// For ZST, we should trust the user. See #4256
(&1u32 as *const u32 as *const ()) as *const u32;
// Issue #2881
let mut data = [0u8, 0u8];
unsafe {
let ptr = &data as *const [u8; 2] as *const u8;
let _ = (ptr as *const u16).read_unaligned();
let _ = core::ptr::read_unaligned(ptr as *const u16);
let _ = core::intrinsics::unaligned_volatile_load(ptr as *const u16);
let ptr = &mut data as *mut [u8; 2] as *mut u8;
let _ = (ptr as *mut u16).write_unaligned(0);
let _ = core::ptr::write_unaligned(ptr as *mut u16, 0);
let _ = core::intrinsics::unaligned_volatile_store(ptr as *mut u16, 0);
}
}

View file

@ -1,5 +1,5 @@
error: casting from `*const u8` to a more-strictly-aligned pointer (`*const u16`) (1 < 2 bytes)
--> $DIR/cast_alignment.rs:18:5
--> $DIR/cast_alignment.rs:19:5
|
LL | (&1u8 as *const u8) as *const u16;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -7,19 +7,19 @@ LL | (&1u8 as *const u8) as *const u16;
= note: `-D clippy::cast-ptr-alignment` implied by `-D warnings`
error: casting from `*mut u8` to a more-strictly-aligned pointer (`*mut u16`) (1 < 2 bytes)
--> $DIR/cast_alignment.rs:19:5
--> $DIR/cast_alignment.rs:20:5
|
LL | (&mut 1u8 as *mut u8) as *mut u16;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: casting from `*const u8` to a more-strictly-aligned pointer (`*const u16`) (1 < 2 bytes)
--> $DIR/cast_alignment.rs:22:5
--> $DIR/cast_alignment.rs:23:5
|
LL | (&1u8 as *const u8).cast::<u16>();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: casting from `*mut u8` to a more-strictly-aligned pointer (`*mut u16`) (1 < 2 bytes)
--> $DIR/cast_alignment.rs:23:5
--> $DIR/cast_alignment.rs:24:5
|
LL | (&mut 1u8 as *mut u8).cast::<u16>();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -8,7 +8,7 @@ pub struct Bar {
}
#[derive(Eq, PartialEq, Debug, Hash)]
pub struct Foo {}
pub struct Foo;
#[allow(clippy::implicit_hasher)]
// This should not cause a "cannot relate bound region" ICE.

View file

@ -4,7 +4,7 @@
#![warn(clippy::use_self)]
#![allow(dead_code)]
struct Foo {}
struct Foo;
impl Foo {
fn new() -> Self {

View file

@ -7,7 +7,7 @@ trait Trait {
fn broken() -> Self::Ty;
}
struct Foo {}
struct Foo;
impl Trait for Foo {
type Ty = Foo;

View file

@ -5,11 +5,7 @@ LL | unsafe { 0 };
| ^^^^^^^^^^^^
|
= note: `-D clippy::undocumented-unsafe-blocks` implied by `-D warnings`
help: consider adding a safety comment
|
LL ~ // SAFETY: ...
LL ~ unsafe { 0 };
|
= help: consider adding a safety comment on the preceding line
error: aborting due to previous error

View file

@ -3,7 +3,7 @@
trait Foo {}
struct Bar {}
struct Bar;
struct Baz<'a> {
bar: &'a Bar,

View file

@ -6,6 +6,6 @@ pub fn foo(bar: *const u8) {
// Regression test for https://github.com/rust-lang/rust-clippy/issues/4917
/// <foo
struct A {}
struct A;
fn main() {}

View file

@ -0,0 +1,56 @@
// run-rustfix
#![warn(clippy::crate_in_macro_def)]
mod hygienic {
#[macro_export]
macro_rules! print_message_hygienic {
() => {
println!("{}", $crate::hygienic::MESSAGE);
};
}
pub const MESSAGE: &str = "Hello!";
}
mod unhygienic {
#[macro_export]
macro_rules! print_message_unhygienic {
() => {
println!("{}", $crate::unhygienic::MESSAGE);
};
}
pub const MESSAGE: &str = "Hello!";
}
mod unhygienic_intentionally {
// For cases where the use of `crate` is intentional, applying `allow` to the macro definition
// should suppress the lint.
#[allow(clippy::crate_in_macro_def)]
#[macro_export]
macro_rules! print_message_unhygienic_intentionally {
() => {
println!("{}", crate::CALLER_PROVIDED_MESSAGE);
};
}
}
#[macro_use]
mod not_exported {
macro_rules! print_message_not_exported {
() => {
println!("{}", crate::not_exported::MESSAGE);
};
}
pub const MESSAGE: &str = "Hello!";
}
fn main() {
print_message_hygienic!();
print_message_unhygienic!();
print_message_unhygienic_intentionally!();
print_message_not_exported!();
}
pub const CALLER_PROVIDED_MESSAGE: &str = "Hello!";

View file

@ -0,0 +1,56 @@
// run-rustfix
#![warn(clippy::crate_in_macro_def)]
mod hygienic {
#[macro_export]
macro_rules! print_message_hygienic {
() => {
println!("{}", $crate::hygienic::MESSAGE);
};
}
pub const MESSAGE: &str = "Hello!";
}
mod unhygienic {
#[macro_export]
macro_rules! print_message_unhygienic {
() => {
println!("{}", crate::unhygienic::MESSAGE);
};
}
pub const MESSAGE: &str = "Hello!";
}
mod unhygienic_intentionally {
// For cases where the use of `crate` is intentional, applying `allow` to the macro definition
// should suppress the lint.
#[allow(clippy::crate_in_macro_def)]
#[macro_export]
macro_rules! print_message_unhygienic_intentionally {
() => {
println!("{}", crate::CALLER_PROVIDED_MESSAGE);
};
}
}
#[macro_use]
mod not_exported {
macro_rules! print_message_not_exported {
() => {
println!("{}", crate::not_exported::MESSAGE);
};
}
pub const MESSAGE: &str = "Hello!";
}
fn main() {
print_message_hygienic!();
print_message_unhygienic!();
print_message_unhygienic_intentionally!();
print_message_not_exported!();
}
pub const CALLER_PROVIDED_MESSAGE: &str = "Hello!";

View file

@ -0,0 +1,10 @@
error: `crate` references the macro call's crate
--> $DIR/crate_in_macro_def.rs:19:28
|
LL | println!("{}", crate::unhygienic::MESSAGE);
| ^^^^^ help: to reference the macro definition's crate, use: `$crate`
|
= note: `-D clippy::crate-in-macro-def` implied by `-D warnings`
error: aborting due to previous error

View file

@ -134,7 +134,7 @@ mod enum_ctor {
}
mod method_calls {
struct StructForMethodCallTest {}
struct StructForMethodCallTest;
impl StructForMethodCallTest {
fn concrete_arg(&self, f: f64) {}

View file

@ -134,7 +134,7 @@ mod enum_ctor {
}
mod method_calls {
struct StructForMethodCallTest {}
struct StructForMethodCallTest;
impl StructForMethodCallTest {
fn concrete_arg(&self, f: f64) {}

View file

@ -133,7 +133,7 @@ mod enum_ctor {
}
mod method_calls {
struct StructForMethodCallTest {}
struct StructForMethodCallTest;
impl StructForMethodCallTest {
fn concrete_arg(&self, x: i32) {}

View file

@ -133,7 +133,7 @@ mod enum_ctor {
}
mod method_calls {
struct StructForMethodCallTest {}
struct StructForMethodCallTest;
impl StructForMethodCallTest {
fn concrete_arg(&self, x: i32) {}

View file

@ -5,7 +5,7 @@ use std::mem::{drop, forget};
use std::vec::Vec;
#[derive(Copy, Clone)]
struct SomeStruct {}
struct SomeStruct;
struct AnotherStruct {
x: u8,

View file

@ -5,7 +5,7 @@ LL | drop(s1);
| ^^^^^^^^
|
= note: `-D clippy::drop-copy` implied by `-D warnings`
note: argument has type SomeStruct
note: argument has type `SomeStruct`
--> $DIR/drop_forget_copy.rs:33:10
|
LL | drop(s1);
@ -17,7 +17,7 @@ error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a
LL | drop(s2);
| ^^^^^^^^
|
note: argument has type SomeStruct
note: argument has type `SomeStruct`
--> $DIR/drop_forget_copy.rs:34:10
|
LL | drop(s2);
@ -29,7 +29,7 @@ error: calls to `std::mem::drop` with a value that implements `Copy`. Dropping a
LL | drop(s4);
| ^^^^^^^^
|
note: argument has type SomeStruct
note: argument has type `SomeStruct`
--> $DIR/drop_forget_copy.rs:36:10
|
LL | drop(s4);
@ -42,7 +42,7 @@ LL | forget(s1);
| ^^^^^^^^^^
|
= note: `-D clippy::forget-copy` implied by `-D warnings`
note: argument has type SomeStruct
note: argument has type `SomeStruct`
--> $DIR/drop_forget_copy.rs:39:12
|
LL | forget(s1);
@ -54,7 +54,7 @@ error: calls to `std::mem::forget` with a value that implements `Copy`. Forgetti
LL | forget(s2);
| ^^^^^^^^^^
|
note: argument has type SomeStruct
note: argument has type `SomeStruct`
--> $DIR/drop_forget_copy.rs:40:12
|
LL | forget(s2);
@ -66,7 +66,7 @@ error: calls to `std::mem::forget` with a value that implements `Copy`. Forgetti
LL | forget(s4);
| ^^^^^^^^^^
|
note: argument has type SomeStruct
note: argument has type `SomeStruct`
--> $DIR/drop_forget_copy.rs:42:12
|
LL | forget(s4);

View file

@ -0,0 +1,40 @@
#![warn(clippy::drop_non_drop)]
use core::mem::drop;
fn make_result<T>(t: T) -> Result<T, ()> {
Ok(t)
}
#[must_use]
fn must_use<T>(t: T) -> T {
t
}
fn drop_generic<T>(t: T) {
// Don't lint
drop(t)
}
fn main() {
struct Foo;
// Lint
drop(Foo);
// Don't lint
drop(make_result(Foo));
// Don't lint
drop(must_use(Foo));
struct Bar;
impl Drop for Bar {
fn drop(&mut self) {}
}
// Don't lint
drop(Bar);
struct Baz<T>(T);
// Lint
drop(Baz(Foo));
// Don't lint
drop(Baz(Bar));
}

View file

@ -0,0 +1,27 @@
error: call to `std::mem::drop` with a value that does not implement `Drop`. Dropping such a type only extends it's contained lifetimes
--> $DIR/drop_non_drop.rs:22:5
|
LL | drop(Foo);
| ^^^^^^^^^
|
= note: `-D clippy::drop-non-drop` implied by `-D warnings`
note: argument has type `main::Foo`
--> $DIR/drop_non_drop.rs:22:10
|
LL | drop(Foo);
| ^^^
error: call to `std::mem::drop` with a value that does not implement `Drop`. Dropping such a type only extends it's contained lifetimes
--> $DIR/drop_non_drop.rs:37:5
|
LL | drop(Baz(Foo));
| ^^^^^^^^^^^^^^
|
note: argument has type `main::Baz<main::Foo>`
--> $DIR/drop_non_drop.rs:37:10
|
LL | drop(Baz(Foo));
| ^^^^^^^^
error: aborting due to 2 previous errors

View file

@ -1,7 +1,7 @@
#![warn(clippy::drop_ref)]
#![allow(clippy::toplevel_ref_arg)]
#![allow(clippy::map_err_ignore)]
#![allow(clippy::unnecessary_wraps)]
#![allow(clippy::unnecessary_wraps, clippy::drop_non_drop)]
use std::mem::drop;

View file

@ -0,0 +1,25 @@
// run-rustfix
#![warn(clippy::empty_structs_with_brackets)]
#![allow(dead_code)]
pub struct MyEmptyStruct; // should trigger lint
struct MyEmptyTupleStruct; // should trigger lint
// should not trigger lint
struct MyCfgStruct {
#[cfg(feature = "thisisneverenabled")]
field: u8,
}
// should not trigger lint
struct MyCfgTupleStruct(#[cfg(feature = "thisisneverenabled")] u8);
// should not trigger lint
struct MyStruct {
field: u8,
}
struct MyTupleStruct(usize, String); // should not trigger lint
struct MySingleTupleStruct(usize); // should not trigger lint
struct MyUnitLikeStruct; // should not trigger lint
fn main() {}

View file

@ -0,0 +1,25 @@
// run-rustfix
#![warn(clippy::empty_structs_with_brackets)]
#![allow(dead_code)]
pub struct MyEmptyStruct {} // should trigger lint
struct MyEmptyTupleStruct(); // should trigger lint
// should not trigger lint
struct MyCfgStruct {
#[cfg(feature = "thisisneverenabled")]
field: u8,
}
// should not trigger lint
struct MyCfgTupleStruct(#[cfg(feature = "thisisneverenabled")] u8);
// should not trigger lint
struct MyStruct {
field: u8,
}
struct MyTupleStruct(usize, String); // should not trigger lint
struct MySingleTupleStruct(usize); // should not trigger lint
struct MyUnitLikeStruct; // should not trigger lint
fn main() {}

View file

@ -0,0 +1,19 @@
error: found empty brackets on struct declaration
--> $DIR/empty_structs_with_brackets.rs:5:25
|
LL | pub struct MyEmptyStruct {} // should trigger lint
| ^^^
|
= note: `-D clippy::empty-structs-with-brackets` implied by `-D warnings`
= help: remove the brackets
error: found empty brackets on struct declaration
--> $DIR/empty_structs_with_brackets.rs:6:26
|
LL | struct MyEmptyTupleStruct(); // should trigger lint
| ^^^
|
= help: remove the brackets
error: aborting due to 2 previous errors

View file

@ -0,0 +1,14 @@
// run-rustfix
struct MyTypeNonDebug;
#[derive(Debug)]
struct MyTypeDebug;
fn main() {
let test_debug: Result<MyTypeDebug, u32> = Ok(MyTypeDebug);
test_debug.expect_err("Testing debug type");
let test_non_debug: Result<MyTypeNonDebug, u32> = Ok(MyTypeNonDebug);
test_non_debug.err().expect("Testing non debug type");
}

View file

@ -0,0 +1,14 @@
// run-rustfix
struct MyTypeNonDebug;
#[derive(Debug)]
struct MyTypeDebug;
fn main() {
let test_debug: Result<MyTypeDebug, u32> = Ok(MyTypeDebug);
test_debug.err().expect("Testing debug type");
let test_non_debug: Result<MyTypeNonDebug, u32> = Ok(MyTypeNonDebug);
test_non_debug.err().expect("Testing non debug type");
}

View file

@ -0,0 +1,10 @@
error: called `.err().expect()` on a `Result` value
--> $DIR/err_expect.rs:10:16
|
LL | test_debug.err().expect("Testing debug type");
| ^^^^^^^^^^^^ help: try: `expect_err`
|
= note: `-D clippy::err-expect` implied by `-D warnings`
error: aborting due to previous error

View file

@ -20,7 +20,7 @@ fn h(_: bool, _: bool, _: bool) {}
fn e(_: S, _: S, _: Box<S>, _: Vec<u32>) {}
fn t(_: S, _: S, _: Box<S>, _: Vec<u32>, _: bool, _: bool, _: bool, _: bool) {}
struct S {}
struct S;
trait Trait {
fn f(_: bool, _: bool, _: bool, _: bool);
fn g(_: bool, _: bool, _: bool, _: Vec<u32>);

View file

@ -0,0 +1,27 @@
#![warn(clippy::forget_non_drop)]
use core::mem::forget;
fn forget_generic<T>(t: T) {
// Don't lint
forget(t)
}
fn main() {
struct Foo;
// Lint
forget(Foo);
struct Bar;
impl Drop for Bar {
fn drop(&mut self) {}
}
// Don't lint
forget(Bar);
struct Baz<T>(T);
// Lint
forget(Baz(Foo));
// Don't lint
forget(Baz(Bar));
}

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