Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
Philipp Krones 2022-06-30 10:27:25 +02:00
commit f26cf11fe8
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
251 changed files with 9082 additions and 3517 deletions

View file

@ -21,7 +21,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v1.4.4
with:
node-version: '12.x'
node-version: '14.x'
- name: Install remark
run: npm install remark-cli remark-lint remark-lint-maximum-line-length remark-preset-lint-recommended remark-gfm

View file

@ -3348,6 +3348,7 @@ Released 2018-09-13
[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation
[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const
[`default_instead_of_iter_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_instead_of_iter_empty
[`default_numeric_fallback`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_numeric_fallback
[`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access
[`default_union_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_union_representation
@ -3399,6 +3400,7 @@ Released 2018-09-13
[`expect_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call
[`expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_used
[`expl_impl_clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#expl_impl_clone_on_copy
[`explicit_auto_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_auto_deref
[`explicit_counter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_counter_loop
[`explicit_deref_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods
[`explicit_into_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_into_iter_loop
@ -3519,6 +3521,7 @@ Released 2018-09-13
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
[`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map
@ -3526,6 +3529,8 @@ Released 2018-09-13
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
[`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid
[`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain
[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
[`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once
[`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat

View file

@ -31,6 +31,7 @@ termize = "0.1"
compiletest_rs = { version = "0.8", features = ["tmp"] }
tester = "0.9"
regex = "1.5"
toml = "0.5"
# This is used by the `collect-metadata` alias.
filetime = "0.2"
@ -40,6 +41,7 @@ filetime = "0.2"
rustc-workspace-hack = "1.0"
# UI test dependencies
clap = { version = "3.1", features = ["derive"] }
clippy_utils = { path = "clippy_utils" }
derive-new = "0.5"
if_chain = "1.0"

View file

@ -5,7 +5,7 @@
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
[There are over 500 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are over 550 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.
@ -214,6 +214,14 @@ specifying the minimum supported Rust version (MSRV) in the clippy configuration
msrv = "1.30.0"
```
Alternatively, the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field)
in the `Cargo.toml` can be used.
```toml
# Cargo.toml
rust-version = "1.30"
```
The MSRV can also be specified as an inner attribute, like below.
```rust

View file

@ -6,7 +6,7 @@
A collection of lints to catch common mistakes and improve your
[Rust](https://github.com/rust-lang/rust) code.
[There are over 500 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are over 550 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
Lints are divided into categories, each with a default [lint
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how

View file

@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
aho-corasick = "0.7"
clap = "3.1"
clap = "3.2"
indoc = "1.0"
itertools = "0.10.1"
opener = "0.5"

View file

@ -5,6 +5,7 @@
use clap::{Arg, ArgAction, ArgMatches, Command, PossibleValue};
use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints};
use indoc::indoc;
fn main() {
let matches = get_clap_config();
@ -85,6 +86,11 @@ fn main() {
let uplift = matches.contains_id("uplift");
update_lints::rename(old_name, new_name, uplift);
},
Some(("deprecate", matches)) => {
let name = matches.get_one::<String>("name").unwrap();
let reason = matches.get_one("reason");
update_lints::deprecate(name, reason);
},
_ => {},
}
}
@ -266,6 +272,18 @@ fn get_clap_config() -> ArgMatches {
.long("uplift")
.help("This lint will be uplifted into rustc"),
]),
Command::new("deprecate").about("Deprecates the given lint").args([
Arg::new("name")
.index(1)
.required(true)
.help("The name of the lint to deprecate"),
Arg::new("reason")
.long("reason")
.short('r')
.required(false)
.takes_value(true)
.help("The reason for deprecation"),
]),
])
.get_matches()
}

View file

@ -138,7 +138,7 @@ fn to_camel_case(name: &str) -> String {
.collect()
}
fn get_stabilization_version() -> String {
pub(crate) fn get_stabilization_version() -> String {
fn parse_manifest(contents: &str) -> Option<String> {
let version = contents
.lines()

View file

@ -1,16 +1,17 @@
use crate::clippy_project_root;
use aho_corasick::AhoCorasickBuilder;
use core::fmt::Write as _;
use indoc::writedoc;
use itertools::Itertools;
use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;
use std::fs;
use std::io::{self, Read as _, Seek as _, Write as _};
use std::fmt::Write;
use std::fs::{self, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write as _};
use std::ops::Range;
use std::path::{Path, PathBuf};
use walkdir::{DirEntry, WalkDir};
use crate::clippy_project_root;
const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\
// Use that command to update this file and do not edit by hand.\n\
// Manual edits will be overwritten.\n\n";
@ -326,6 +327,200 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
println!("note: `cargo uitest` still needs to be run to update the test results");
}
const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note";
/// Runs the `deprecate` command
///
/// This does the following:
/// * Adds an entry to `deprecated_lints.rs`.
/// * Removes the lint declaration (and the entire file if applicable)
///
/// # Panics
///
/// If a file path could not read from or written to
pub fn deprecate(name: &str, reason: Option<&String>) {
fn finish(
(lints, mut deprecated_lints, renamed_lints): (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>),
name: &str,
reason: &str,
) {
deprecated_lints.push(DeprecatedLint {
name: name.to_string(),
reason: reason.to_string(),
declaration_range: Range::default(),
});
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
println!("info: `{}` has successfully been deprecated", name);
if reason == DEFAULT_DEPRECATION_REASON {
println!("note: the deprecation reason must be updated in `clippy_lints/src/deprecated_lints.rs`");
}
println!("note: you must run `cargo uitest` to update the test results");
}
let reason = reason.map_or(DEFAULT_DEPRECATION_REASON, String::as_str);
let name_lower = name.to_lowercase();
let name_upper = name.to_uppercase();
let (mut lints, deprecated_lints, renamed_lints) = gather_all();
let Some(lint) = lints.iter().find(|l| l.name == name_lower) else { eprintln!("error: failed to find lint `{}`", name); return; };
let mod_path = {
let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module));
if mod_path.is_dir() {
mod_path = mod_path.join("mod");
}
mod_path.set_extension("rs");
mod_path
};
let deprecated_lints_path = &*clippy_project_root().join("clippy_lints/src/deprecated_lints.rs");
if remove_lint_declaration(&name_lower, &mod_path, &mut lints).unwrap_or(false) {
declare_deprecated(&name_upper, deprecated_lints_path, reason).unwrap();
finish((lints, deprecated_lints, renamed_lints), name, reason);
return;
}
eprintln!("error: lint not found");
}
fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io::Result<bool> {
fn remove_lint(name: &str, lints: &mut Vec<Lint>) {
lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos));
}
fn remove_test_assets(name: &str) {
let test_file_stem = format!("tests/ui/{}", name);
let path = Path::new(&test_file_stem);
// Some lints have their own directories, delete them
if path.is_dir() {
fs::remove_dir_all(path).ok();
return;
}
// Remove all related test files
fs::remove_file(path.with_extension("rs")).ok();
fs::remove_file(path.with_extension("stderr")).ok();
fs::remove_file(path.with_extension("fixed")).ok();
}
fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) {
let impl_lint_pass_start = content.find("impl_lint_pass!").unwrap_or_else(|| {
content
.find("declare_lint_pass!")
.unwrap_or_else(|| panic!("failed to find `impl_lint_pass`"))
});
let mut impl_lint_pass_end = content[impl_lint_pass_start..]
.find(']')
.expect("failed to find `impl_lint_pass` terminator");
impl_lint_pass_end += impl_lint_pass_start;
if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(&lint_name_upper) {
let mut lint_name_end = impl_lint_pass_start + (lint_name_pos + lint_name_upper.len());
for c in content[lint_name_end..impl_lint_pass_end].chars() {
// Remove trailing whitespace
if c == ',' || c.is_whitespace() {
lint_name_end += 1;
} else {
break;
}
}
content.replace_range(impl_lint_pass_start + lint_name_pos..lint_name_end, "");
}
}
if path.exists() {
if let Some(lint) = lints.iter().find(|l| l.name == name) {
if lint.module == name {
// The lint name is the same as the file, we can just delete the entire file
fs::remove_file(path)?;
} else {
// We can't delete the entire file, just remove the declaration
if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) {
// Remove clippy_lints/src/some_mod/some_lint.rs
let mut lint_mod_path = path.to_path_buf();
lint_mod_path.set_file_name(name);
lint_mod_path.set_extension("rs");
fs::remove_file(lint_mod_path).ok();
}
let mut content =
fs::read_to_string(&path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy()));
eprintln!(
"warn: you will have to manually remove any code related to `{}` from `{}`",
name,
path.display()
);
assert!(
content[lint.declaration_range.clone()].contains(&name.to_uppercase()),
"error: `{}` does not contain lint `{}`'s declaration",
path.display(),
lint.name
);
// Remove lint declaration (declare_clippy_lint!)
content.replace_range(lint.declaration_range.clone(), "");
// Remove the module declaration (mod xyz;)
let mod_decl = format!("\nmod {};", name);
content = content.replacen(&mod_decl, "", 1);
remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content);
fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy()));
}
remove_test_assets(name);
remove_lint(name, lints);
return Ok(true);
}
}
Ok(false)
}
fn declare_deprecated(name: &str, path: &Path, reason: &str) -> io::Result<()> {
let mut file = OpenOptions::new().write(true).open(path)?;
file.seek(SeekFrom::End(0))?;
let version = crate::new_lint::get_stabilization_version();
let deprecation_reason = if reason == DEFAULT_DEPRECATION_REASON {
"TODO"
} else {
reason
};
writedoc!(
file,
"
declare_deprecated_lint! {{
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// {}
#[clippy::version = \"{}\"]
pub {},
\"{}\"
}}
",
deprecation_reason,
version,
name,
reason,
)
}
/// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there
/// were no replacements.
fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<String> {
@ -393,16 +588,18 @@ struct Lint {
group: String,
desc: String,
module: String,
declaration_range: Range<usize>,
}
impl Lint {
#[must_use]
fn new(name: &str, group: &str, desc: &str, module: &str) -> Self {
fn new(name: &str, group: &str, desc: &str, module: &str, declaration_range: Range<usize>) -> Self {
Self {
name: name.to_lowercase(),
group: group.into(),
desc: remove_line_splices(desc),
module: module.into(),
declaration_range,
}
}
@ -433,12 +630,14 @@ impl Lint {
struct DeprecatedLint {
name: String,
reason: String,
declaration_range: Range<usize>,
}
impl DeprecatedLint {
fn new(name: &str, reason: &str) -> Self {
fn new(name: &str, reason: &str, declaration_range: Range<usize>) -> Self {
Self {
name: name.to_lowercase(),
reason: remove_line_splices(reason),
declaration_range,
}
}
}
@ -610,7 +809,11 @@ fn clippy_lints_src_files() -> impl Iterator<Item = (PathBuf, DirEntry)> {
macro_rules! match_tokens {
($iter:ident, $($token:ident $({$($fields:tt)*})? $(($capture:ident))?)*) => {
{
$($(let $capture =)? if let Some((TokenKind::$token $({$($fields)*})?, _x)) = $iter.next() {
$($(let $capture =)? if let Some(LintDeclSearchResult {
token_kind: TokenKind::$token $({$($fields)*})?,
content: _x,
..
}) = $iter.next() {
_x
} else {
continue;
@ -621,40 +824,72 @@ macro_rules! match_tokens {
}
}
struct LintDeclSearchResult<'a> {
token_kind: TokenKind,
content: &'a str,
range: Range<usize>,
}
/// 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])
LintDeclSearchResult {
token_kind: t.kind,
content: &contents[range.clone()],
range,
}
});
while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_clippy_lint") {
while let Some(LintDeclSearchResult { range, .. }) = iter.find(
|LintDeclSearchResult {
token_kind, content, ..
}| token_kind == &TokenKind::Ident && *content == "declare_clippy_lint",
) {
let start = range.start;
let mut iter = iter
.by_ref()
.filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
.filter(|t| !matches!(t.token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
// matches `!{`
match_tokens!(iter, Bang OpenBrace);
match iter.next() {
// #[clippy::version = "version"] pub
Some((TokenKind::Pound, _)) => {
Some(LintDeclSearchResult {
token_kind: TokenKind::Pound,
..
}) => {
match_tokens!(iter, OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Ident);
},
// pub
Some((TokenKind::Ident, _)) => (),
Some(LintDeclSearchResult {
token_kind: TokenKind::Ident,
..
}) => (),
_ => continue,
}
let (name, group, desc) = match_tokens!(
iter,
// LINT_NAME
Ident(name) Comma
// group,
Ident(group) Comma
// "description" }
Literal{..}(desc) CloseBrace
// "description"
Literal{..}(desc)
);
lints.push(Lint::new(name, group, desc, module));
if let Some(LintDeclSearchResult {
token_kind: TokenKind::CloseBrace,
range,
..
}) = iter.next()
{
lints.push(Lint::new(name, group, desc, module, start..range.end));
}
}
}
@ -664,12 +899,24 @@ fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) {
let mut iter = tokenize(contents).map(|t| {
let range = offset..offset + t.len;
offset = range.end;
(t.kind, &contents[range])
LintDeclSearchResult {
token_kind: t.kind,
content: &contents[range.clone()],
range,
}
});
while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_deprecated_lint") {
let mut iter = iter
.by_ref()
.filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
while let Some(LintDeclSearchResult { range, .. }) = iter.find(
|LintDeclSearchResult {
token_kind, content, ..
}| token_kind == &TokenKind::Ident && *content == "declare_deprecated_lint",
) {
let start = range.start;
let mut iter = iter.by_ref().filter(|LintDeclSearchResult { ref token_kind, .. }| {
!matches!(token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. })
});
let (name, reason) = match_tokens!(
iter,
// !{
@ -680,10 +927,16 @@ fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) {
Ident Ident(name) Comma
// "description"
Literal{kind: LiteralKind::Str{..},..}(reason)
// }
CloseBrace
);
lints.push(DeprecatedLint::new(name, reason));
if let Some(LintDeclSearchResult {
token_kind: TokenKind::CloseBrace,
range,
..
}) = iter.next()
{
lints.push(DeprecatedLint::new(name, reason, start..range.end));
}
}
}
@ -693,8 +946,14 @@ fn parse_renamed_contents(contents: &str, lints: &mut Vec<RenamedLint>) {
let mut iter = tokenize(line).map(|t| {
let range = offset..offset + t.len;
offset = range.end;
(t.kind, &line[range])
LintDeclSearchResult {
token_kind: t.kind,
content: &line[range.clone()],
range,
}
});
let (old_name, new_name) = match_tokens!(
iter,
// ("old_name",
@ -844,10 +1103,25 @@ mod tests {
"#;
let mut result = Vec::new();
parse_contents(CONTENTS, "module_name", &mut result);
for r in &mut result {
r.declaration_range = Range::default();
}
let expected = vec![
Lint::new("ptr_arg", "style", "\"really long text\"", "module_name"),
Lint::new("doc_markdown", "pedantic", "\"single line\"", "module_name"),
Lint::new(
"ptr_arg",
"style",
"\"really long text\"",
"module_name",
Range::default(),
),
Lint::new(
"doc_markdown",
"pedantic",
"\"single line\"",
"module_name",
Range::default(),
),
];
assert_eq!(expected, result);
}
@ -865,10 +1139,14 @@ mod tests {
let mut result = Vec::new();
parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result);
for r in &mut result {
r.declaration_range = Range::default();
}
let expected = vec![DeprecatedLint::new(
"should_assert_eq",
"\"`assert!()` will be more flexible with RFC 2011\"",
Range::default(),
)];
assert_eq!(expected, result);
}
@ -876,15 +1154,34 @@ mod tests {
#[test]
fn test_usable_lints() {
let lints = vec![
Lint::new("should_assert_eq2", "Not Deprecated", "\"abc\"", "module_name"),
Lint::new("should_assert_eq2", "internal", "\"abc\"", "module_name"),
Lint::new("should_assert_eq2", "internal_style", "\"abc\"", "module_name"),
Lint::new(
"should_assert_eq2",
"Not Deprecated",
"\"abc\"",
"module_name",
Range::default(),
),
Lint::new(
"should_assert_eq2",
"internal",
"\"abc\"",
"module_name",
Range::default(),
),
Lint::new(
"should_assert_eq2",
"internal_style",
"\"abc\"",
"module_name",
Range::default(),
),
];
let expected = vec![Lint::new(
"should_assert_eq2",
"Not Deprecated",
"\"abc\"",
"module_name",
Range::default(),
)];
assert_eq!(expected, Lint::usable_lints(&lints));
}
@ -892,21 +1189,33 @@ mod tests {
#[test]
fn test_by_lint_group() {
let lints = vec![
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name"),
Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"),
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()),
Lint::new(
"should_assert_eq2",
"group2",
"\"abc\"",
"module_name",
Range::default(),
),
Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()),
];
let mut expected: HashMap<String, Vec<Lint>> = HashMap::new();
expected.insert(
"group1".to_string(),
vec![
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"),
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()),
Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()),
],
);
expected.insert(
"group2".to_string(),
vec![Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name")],
vec![Lint::new(
"should_assert_eq2",
"group2",
"\"abc\"",
"module_name",
Range::default(),
)],
);
assert_eq!(expected, Lint::by_lint_group(lints.into_iter()));
}
@ -914,8 +1223,12 @@ mod tests {
#[test]
fn test_gen_deprecated() {
let lints = vec![
DeprecatedLint::new("should_assert_eq", "\"has been superseded by should_assert_eq2\""),
DeprecatedLint::new("another_deprecated", "\"will be removed\""),
DeprecatedLint::new(
"should_assert_eq",
"\"has been superseded by should_assert_eq2\"",
Range::default(),
),
DeprecatedLint::new("another_deprecated", "\"will be removed\"", Range::default()),
];
let expected = GENERATED_FILE_COMMENT.to_string()
@ -940,9 +1253,9 @@ mod tests {
#[test]
fn test_gen_lint_group_list() {
let lints = vec![
Lint::new("abc", "group1", "\"abc\"", "module_name"),
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
Lint::new("internal", "internal_style", "\"abc\"", "module_name"),
Lint::new("abc", "group1", "\"abc\"", "module_name", Range::default()),
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()),
Lint::new("internal", "internal_style", "\"abc\"", "module_name", Range::default()),
];
let expected = GENERATED_FILE_COMMENT.to_string()
+ &[

View file

@ -10,7 +10,6 @@ edition = "2021"
[dependencies]
cargo_metadata = "0.14"
clippy_dev = { path = "../clippy_dev", optional = true }
clippy_utils = { path = "../clippy_utils" }
if_chain = "1.0"
itertools = "0.10.1"
@ -19,7 +18,7 @@ quine-mc_cluskey = "0.2"
regex-syntax = "0.6"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", optional = true }
tempfile = { version = "3.2", optional = true }
tempfile = { version = "3.3.0", optional = true }
toml = "0.5"
unicode-normalization = "0.1"
unicode-script = { version = "0.5", default-features = false }
@ -32,7 +31,7 @@ url = { version = "2.2", features = ["serde"] }
[features]
deny-warnings = ["clippy_utils/deny-warnings"]
# build clippy with internal lints enabled, off by default
internal = ["clippy_utils/internal", "serde_json", "tempfile", "clippy_dev"]
internal = ["clippy_utils/internal", "serde_json", "tempfile"]
[package.metadata.rust-analyzer]
# This crate uses #[feature(rustc_private)]

View file

@ -1,235 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::implements_trait;
use clippy_utils::{binop_traits, sugg};
use clippy_utils::{eq_expr_value, trait_ref_of_method};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks for `a = a op b` or `a = b commutative_op a`
/// patterns.
///
/// ### Why is this bad?
/// These can be written as the shorter `a op= b`.
///
/// ### Known problems
/// While forbidden by the spec, `OpAssign` traits may have
/// implementations that differ from the regular `Op` impl.
///
/// ### Example
/// ```rust
/// let mut a = 5;
/// let b = 0;
/// // ...
///
/// a = a + b;
/// ```
///
/// Use instead:
/// ```rust
/// let mut a = 5;
/// let b = 0;
/// // ...
///
/// a += b;
/// ```
#[clippy::version = "pre 1.29.0"]
pub ASSIGN_OP_PATTERN,
style,
"assigning the result of an operation on a variable to that same variable"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `a op= a op b` or `a op= b op a` patterns.
///
/// ### Why is this bad?
/// Most likely these are bugs where one meant to write `a
/// op= b`.
///
/// ### Known problems
/// Clippy cannot know for sure if `a op= a op b` should have
/// been `a = a op a op b` or `a = a op b`/`a op= b`. Therefore, it suggests both.
/// If `a op= a op b` is really the correct behavior it should be
/// written as `a = a op a op b` as it's less confusing.
///
/// ### Example
/// ```rust
/// let mut a = 5;
/// let b = 2;
/// // ...
/// a += a + b;
/// ```
#[clippy::version = "pre 1.29.0"]
pub MISREFACTORED_ASSIGN_OP,
suspicious,
"having a variable on both sides of an assign op"
}
declare_lint_pass!(AssignOps => [ASSIGN_OP_PATTERN, MISREFACTORED_ASSIGN_OP]);
impl<'tcx> LateLintPass<'tcx> for AssignOps {
#[allow(clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
match &expr.kind {
hir::ExprKind::AssignOp(op, lhs, rhs) => {
if let hir::ExprKind::Binary(binop, l, r) = &rhs.kind {
if op.node != binop.node {
return;
}
// lhs op= l op r
if eq_expr_value(cx, lhs, l) {
lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, r);
}
// lhs op= l commutative_op r
if is_commutative(op.node) && eq_expr_value(cx, lhs, r) {
lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, l);
}
}
},
hir::ExprKind::Assign(assignee, e, _) => {
if let hir::ExprKind::Binary(op, l, r) = &e.kind {
let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| {
let ty = cx.typeck_results().expr_ty(assignee);
let rty = cx.typeck_results().expr_ty(rhs);
if_chain! {
if let Some((_, lang_item)) = binop_traits(op.node);
if let Ok(trait_id) = cx.tcx.lang_items().require(lang_item);
let parent_fn = cx.tcx.hir().get_parent_item(e.hir_id);
if trait_ref_of_method(cx, parent_fn)
.map_or(true, |t| t.path.res.def_id() != trait_id);
if implements_trait(cx, ty, trait_id, &[rty.into()]);
then {
span_lint_and_then(
cx,
ASSIGN_OP_PATTERN,
expr.span,
"manual implementation of an assign operation",
|diag| {
if let (Some(snip_a), Some(snip_r)) =
(snippet_opt(cx, assignee.span), snippet_opt(cx, rhs.span))
{
diag.span_suggestion(
expr.span,
"replace it with",
format!("{} {}= {}", snip_a, op.node.as_str(), snip_r),
Applicability::MachineApplicable,
);
}
},
);
}
}
};
let mut visitor = ExprVisitor {
assignee,
counter: 0,
cx,
};
walk_expr(&mut visitor, e);
if visitor.counter == 1 {
// a = a op b
if eq_expr_value(cx, assignee, l) {
lint(assignee, r);
}
// a = b commutative_op a
// Limited to primitive type as these ops are know to be commutative
if eq_expr_value(cx, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() {
match op.node {
hir::BinOpKind::Add
| hir::BinOpKind::Mul
| hir::BinOpKind::And
| hir::BinOpKind::Or
| hir::BinOpKind::BitXor
| hir::BinOpKind::BitAnd
| hir::BinOpKind::BitOr => {
lint(assignee, l);
},
_ => {},
}
}
}
}
},
_ => {},
}
}
}
fn lint_misrefactored_assign_op(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
op: hir::BinOp,
rhs: &hir::Expr<'_>,
assignee: &hir::Expr<'_>,
rhs_other: &hir::Expr<'_>,
) {
span_lint_and_then(
cx,
MISREFACTORED_ASSIGN_OP,
expr.span,
"variable appears on both sides of an assignment operation",
|diag| {
if let (Some(snip_a), Some(snip_r)) = (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs_other.span)) {
let a = &sugg::Sugg::hir(cx, assignee, "..");
let r = &sugg::Sugg::hir(cx, rhs, "..");
let long = format!("{} = {}", snip_a, sugg::make_binop(op.node.into(), a, r));
diag.span_suggestion(
expr.span,
&format!(
"did you mean `{} = {} {} {}` or `{}`? Consider replacing it with",
snip_a,
snip_a,
op.node.as_str(),
snip_r,
long
),
format!("{} {}= {}", snip_a, op.node.as_str(), snip_r),
Applicability::MaybeIncorrect,
);
diag.span_suggestion(
expr.span,
"or",
long,
Applicability::MaybeIncorrect, // snippet
);
}
},
);
}
#[must_use]
fn is_commutative(op: hir::BinOpKind) -> bool {
use rustc_hir::BinOpKind::{
Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
};
match op {
Add | Mul | And | Or | BitXor | BitAnd | BitOr | Eq | Ne => true,
Sub | Div | Rem | Shl | Shr | Lt | Le | Ge | Gt => false,
}
}
struct ExprVisitor<'a, 'tcx> {
assignee: &'a hir::Expr<'a>,
counter: u8,
cx: &'a LateContext<'tcx>,
}
impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
if eq_expr_value(self.cx, self.assignee, expr) {
self.counter += 1;
}
walk_expr(self, expr);
}
}

View file

@ -78,10 +78,17 @@ declare_clippy_lint! {
/// Checks for `extern crate` and `use` items annotated with
/// lint attributes.
///
/// This lint permits `#[allow(unused_imports)]`, `#[allow(deprecated)]`,
/// `#[allow(unreachable_pub)]`, `#[allow(clippy::wildcard_imports)]` and
/// `#[allow(clippy::enum_glob_use)]` on `use` items and `#[allow(unused_imports)]` on
/// `extern crate` items with a `#[macro_use]` attribute.
/// This lint permits lint attributes for lints emitted on the items themself.
/// For `use` items these lints are:
/// * deprecated
/// * unreachable_pub
/// * unused_imports
/// * clippy::enum_glob_use
/// * clippy::macro_use_imports
/// * clippy::wildcard_imports
///
/// For `extern crate` items these lints are:
/// * `unused_imports` on items with `#[macro_use]`
///
/// ### Why is this bad?
/// Lint attributes have no effect on crate imports. Most
@ -347,7 +354,10 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|| extract_clippy_lint(lint).map_or(false, |s| {
matches!(
s.as_str(),
"wildcard_imports" | "enum_glob_use" | "redundant_pub_crate",
"wildcard_imports"
| "enum_glob_use"
| "redundant_pub_crate"
| "macro_use_imports",
)
})
{

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{eq_expr_value, get_trait_def_id, paths};
@ -394,9 +394,10 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
continue 'simplified;
}
if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
span_lint_and_then(
span_lint_hir_and_then(
self.cx,
LOGIC_BUG,
e.hir_id,
e.span,
"this boolean expression contains a logic bug",
|diag| {
@ -429,9 +430,10 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
}
}
let nonminimal_bool_lint = |suggestions: Vec<_>| {
span_lint_and_then(
span_lint_hir_and_then(
self.cx,
NONMINIMAL_BOOL,
e.hir_id,
e.span,
"this boolean expression can be simplified",
|diag| {

View file

@ -0,0 +1,68 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::last_path_segment;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{match_def_path, paths};
use rustc_errors::Applicability;
use rustc_hir::{def, Expr, ExprKind, GenericArg, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// It checks for `std::iter::Empty::default()` and suggests replacing it with
/// `std::iter::empty()`.
/// ### Why is this bad?
/// `std::iter::empty()` is the more idiomatic way.
/// ### Example
/// ```rust
/// let _ = std::iter::Empty::<usize>::default();
/// let iter: std::iter::Empty<usize> = std::iter::Empty::default();
/// ```
/// Use instead:
/// ```rust
/// let _ = std::iter::empty::<usize>();
/// let iter: std::iter::Empty<usize> = std::iter::empty();
/// ```
#[clippy::version = "1.63.0"]
pub DEFAULT_INSTEAD_OF_ITER_EMPTY,
style,
"check `std::iter::Empty::default()` and replace with `std::iter::empty()`"
}
declare_lint_pass!(DefaultIterEmpty => [DEFAULT_INSTEAD_OF_ITER_EMPTY]);
impl<'tcx> LateLintPass<'tcx> for DefaultIterEmpty {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Call(iter_expr, []) = &expr.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, _)) = &iter_expr.kind
&& let TyKind::Path(ty_path) = &ty.kind
&& let QPath::Resolved(None, path) = ty_path
&& let def::Res::Def(_, def_id) = &path.res
&& match_def_path(cx, *def_id, &paths::ITER_EMPTY)
{
let mut applicability = Applicability::MachineApplicable;
let sugg = make_sugg(cx, ty_path, &mut applicability);
span_lint_and_sugg(
cx,
DEFAULT_INSTEAD_OF_ITER_EMPTY,
expr.span,
"`std::iter::empty()` is the more idiomatic way",
"try",
sugg,
applicability,
);
}
}
}
fn make_sugg(cx: &LateContext<'_>, ty_path: &rustc_hir::QPath<'_>, applicability: &mut Applicability) -> String {
if let Some(last) = last_path_segment(ty_path).args
&& let Some(iter_ty) = last.args.iter().find_map(|arg| match arg {
GenericArg::Type(ty) => Some(ty),
_ => None,
})
{
format!("std::iter::empty::<{}>()", snippet_with_applicability(cx, iter_ty.span, "..", applicability))
} else {
"std::iter::empty()".to_owned()
}
}

View file

@ -1,16 +1,21 @@
// NOTE: if you add a deprecated lint in this file, please add a corresponding test in
// tests/ui/deprecated.rs
// NOTE: Entries should be created with `cargo dev deprecate`
/// This struct fakes the `Lint` declaration that is usually created by `declare_lint!`. This
/// enables the simple extraction of the metadata without changing the current deprecation
/// declaration.
pub struct ClippyDeprecatedLint;
pub struct ClippyDeprecatedLint {
#[allow(dead_code)]
pub desc: &'static str,
}
#[macro_export]
macro_rules! declare_deprecated_lint {
{ $(#[$attr:meta])* pub $name: ident, $_reason: expr} => {
{ $(#[$attr:meta])* pub $name: ident, $reason: literal} => {
$(#[$attr])*
#[allow(dead_code)]
pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint {};
pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint {
desc: $reason
};
}
}

View file

@ -1,20 +1,24 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::peel_mid_ty_refs;
use clippy_utils::{get_parent_expr, get_parent_node, is_lint_allowed, path_to_local};
use clippy_utils::ty::{expr_sig, peel_mid_ty_refs, variant_of_res};
use clippy_utils::{get_parent_expr, is_lint_allowed, path_to_local, walk_to_expr_usage};
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_ty, Visitor};
use rustc_hir::{
BindingAnnotation, Body, BodyId, BorrowKind, Destination, Expr, ExprKind, HirId, MatchSource, Mutability, Node,
Pat, PatKind, UnOp,
self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, GenericArg, HirId, ImplItem,
ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
TraitItemKind, TyKind, UnOp,
};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeckResults};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeckResults};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{symbol::sym, Span};
use rustc_span::{symbol::sym, Span, Symbol};
use rustc_trait_selection::infer::InferCtxtExt;
declare_clippy_lint! {
/// ### What it does
@ -104,10 +108,34 @@ declare_clippy_lint! {
"`ref` binding to a reference"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for dereferencing expressions which would be covered by auto-deref.
///
/// ### Why is this bad?
/// This unnecessarily complicates the code.
///
/// ### Example
/// ```rust
/// let x = String::new();
/// let y: &str = &*x;
/// ```
/// Use instead:
/// ```rust
/// let x = String::new();
/// let y: &str = &x;
/// ```
#[clippy::version = "1.60.0"]
pub EXPLICIT_AUTO_DEREF,
complexity,
"dereferencing when the compiler would automatically dereference"
}
impl_lint_pass!(Dereferencing => [
EXPLICIT_DEREF_METHODS,
NEEDLESS_BORROW,
REF_BINDING_TO_REFERENCE,
EXPLICIT_AUTO_DEREF,
]);
#[derive(Default)]
@ -136,6 +164,12 @@ struct StateData {
/// Span of the top level expression
span: Span,
hir_id: HirId,
position: Position,
}
struct DerefedBorrow {
count: usize,
msg: &'static str,
}
enum State {
@ -147,11 +181,19 @@ enum State {
/// The required mutability
target_mut: Mutability,
},
DerefedBorrow {
count: usize,
required_precedence: i8,
msg: &'static str,
DerefedBorrow(DerefedBorrow),
ExplicitDeref {
// Span and id of the top-level deref expression if the parent expression is a borrow.
deref_span_id: Option<(Span, HirId)>,
},
ExplicitDerefField {
name: Symbol,
},
Reborrow {
deref_span: Span,
deref_hir_id: HirId,
},
Borrow,
}
// A reference operation considered by this lint pass
@ -207,13 +249,28 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
match (self.state.take(), kind) {
(None, kind) => {
let parent = get_parent_node(cx.tcx, expr.hir_id);
let expr_ty = typeck.expr_ty(expr);
let (position, adjustments) = walk_parents(cx, expr);
match kind {
RefOp::Deref => {
if let Position::FieldAccess(name) = position
&& !ty_contains_field(typeck.expr_ty(sub_expr), name)
{
self.state = Some((
State::ExplicitDerefField { name },
StateData { span: expr.span, hir_id: expr.hir_id, position },
));
} else if position.is_deref_stable() {
self.state = Some((
State::ExplicitDeref { deref_span_id: None },
StateData { span: expr.span, hir_id: expr.hir_id, position },
));
}
}
RefOp::Method(target_mut)
if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id)
&& is_linted_explicit_deref_position(parent, expr.hir_id, expr.span) =>
&& position.lint_explicit_deref() =>
{
self.state = Some((
State::DerefMethod {
@ -228,12 +285,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
StateData {
span: expr.span,
hir_id: expr.hir_id,
position
},
));
},
RefOp::AddrOf => {
// Find the number of times the borrow is auto-derefed.
let mut iter = find_adjustments(cx.tcx, typeck, expr).iter();
let mut iter = adjustments.iter();
let mut deref_count = 0usize;
let next_adjust = loop {
match iter.next() {
@ -274,40 +332,43 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
"this expression creates a reference which is immediately dereferenced by the compiler";
let borrow_msg = "this expression borrows a value the compiler would automatically borrow";
let (required_refs, required_precedence, msg) = if is_auto_borrow_position(parent, expr.hir_id)
{
(1, PREC_POSTFIX, if deref_count == 1 { borrow_msg } else { deref_msg })
let (required_refs, msg) = if position.can_auto_borrow() {
(1, if deref_count == 1 { borrow_msg } else { deref_msg })
} else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) =
next_adjust.map(|a| &a.kind)
{
if matches!(mutability, AutoBorrowMutability::Mut { .. })
&& !is_auto_reborrow_position(parent)
if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !position.is_reborrow_stable()
{
(3, 0, deref_msg)
(3, deref_msg)
} else {
(2, 0, deref_msg)
(2, deref_msg)
}
} else {
(2, 0, deref_msg)
(2, deref_msg)
};
if deref_count >= required_refs {
self.state = Some((
State::DerefedBorrow {
State::DerefedBorrow(DerefedBorrow {
// One of the required refs is for the current borrow expression, the remaining ones
// can't be removed without breaking the code. See earlier comment.
count: deref_count - required_refs,
required_precedence,
msg,
},
}),
StateData { span: expr.span, hir_id: expr.hir_id, position },
));
} else if position.is_deref_stable() {
self.state = Some((
State::Borrow,
StateData {
span: expr.span,
hir_id: expr.hir_id,
position
},
));
}
},
_ => (),
RefOp::Method(..) => (),
}
},
(
@ -334,26 +395,90 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
data,
));
},
(Some((State::DerefedBorrow(state), data)), RefOp::AddrOf) if state.count != 0 => {
self.state = Some((
State::DerefedBorrow(DerefedBorrow {
count: state.count - 1,
..state
}),
data,
));
},
(Some((State::DerefedBorrow(state), data)), RefOp::AddrOf) => {
let position = data.position;
report(cx, expr, State::DerefedBorrow(state), data);
if position.is_deref_stable() {
self.state = Some((
State::Borrow,
StateData {
span: expr.span,
hir_id: expr.hir_id,
position,
},
));
}
},
(Some((State::DerefedBorrow(state), data)), RefOp::Deref) => {
let position = data.position;
report(cx, expr, State::DerefedBorrow(state), data);
if let Position::FieldAccess(name) = position
&& !ty_contains_field(typeck.expr_ty(sub_expr), name)
{
self.state = Some((
State::ExplicitDerefField { name },
StateData { span: expr.span, hir_id: expr.hir_id, position },
));
} else if position.is_deref_stable() {
self.state = Some((
State::ExplicitDeref { deref_span_id: None },
StateData { span: expr.span, hir_id: expr.hir_id, position },
));
}
},
(Some((State::Borrow, data)), RefOp::Deref) => {
if typeck.expr_ty(sub_expr).is_ref() {
self.state = Some((
State::Reborrow {
deref_span: expr.span,
deref_hir_id: expr.hir_id,
},
data,
));
} else {
self.state = Some((
State::ExplicitDeref {
deref_span_id: Some((expr.span, expr.hir_id)),
},
data,
));
}
},
(
Some((
State::DerefedBorrow {
count,
required_precedence,
msg,
State::Reborrow {
deref_span,
deref_hir_id,
},
data,
)),
RefOp::AddrOf,
) if count != 0 => {
RefOp::Deref,
) => {
self.state = Some((
State::DerefedBorrow {
count: count - 1,
required_precedence,
msg,
State::ExplicitDeref {
deref_span_id: Some((deref_span, deref_hir_id)),
},
data,
));
},
(state @ Some((State::ExplicitDeref { .. }, _)), RefOp::Deref) => {
self.state = state;
},
(Some((State::ExplicitDerefField { name }, data)), RefOp::Deref)
if !ty_contains_field(typeck.expr_ty(sub_expr), name) =>
{
self.state = Some((State::ExplicitDerefField { name }, data));
},
(Some((state, data)), _) => report(cx, expr, state, data),
}
@ -473,131 +598,362 @@ fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
}
}
// Checks whether the parent node is a suitable context for switching from a deref method to the
// deref operator.
fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId, child_span: Span) -> bool {
let parent = match parent {
Some(Node::Expr(e)) if e.span.ctxt() == child_span.ctxt() => e,
_ => return true,
};
match parent.kind {
// Leave deref calls in the middle of a method chain.
// e.g. x.deref().foo()
ExprKind::MethodCall(_, [self_arg, ..], _) if self_arg.hir_id == child_id => false,
// Leave deref calls resulting in a called function
// e.g. (x.deref())()
ExprKind::Call(func_expr, _) if func_expr.hir_id == child_id => false,
// Makes an ugly suggestion
// e.g. *x.deref() => *&*x
ExprKind::Unary(UnOp::Deref, _)
// Postfix expressions would require parens
| ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
| ExprKind::Field(..)
| ExprKind::Index(..)
| ExprKind::Err => false,
ExprKind::Box(..)
| ExprKind::ConstBlock(..)
| ExprKind::Array(_)
| ExprKind::Call(..)
| ExprKind::MethodCall(..)
| ExprKind::Tup(..)
| ExprKind::Binary(..)
| ExprKind::Unary(..)
| ExprKind::Lit(..)
| ExprKind::Cast(..)
| ExprKind::Type(..)
| ExprKind::DropTemps(..)
| ExprKind::If(..)
| ExprKind::Loop(..)
| ExprKind::Match(..)
| ExprKind::Let(..)
| ExprKind::Closure{..}
| ExprKind::Block(..)
| ExprKind::Assign(..)
| ExprKind::AssignOp(..)
| ExprKind::Path(..)
| ExprKind::AddrOf(..)
| ExprKind::Break(..)
| ExprKind::Continue(..)
| ExprKind::Ret(..)
| ExprKind::InlineAsm(..)
| ExprKind::Struct(..)
| ExprKind::Repeat(..)
| ExprKind::Yield(..) => true,
}
/// The position of an expression relative to it's parent.
#[derive(Clone, Copy)]
enum Position {
MethodReceiver,
/// The method is defined on a reference type. e.g. `impl Foo for &T`
MethodReceiverRefImpl,
Callee,
FieldAccess(Symbol),
Postfix,
Deref,
/// Any other location which will trigger auto-deref to a specific time.
DerefStable(i8),
/// Any other location which will trigger auto-reborrowing.
ReborrowStable(i8),
Other(i8),
}
/// Checks if the given expression is in a position which can be auto-reborrowed.
/// Note: This is only correct assuming auto-deref is already occurring.
fn is_auto_reborrow_position(parent: Option<Node<'_>>) -> bool {
match parent {
Some(Node::Expr(parent)) => matches!(parent.kind, ExprKind::MethodCall(..) | ExprKind::Call(..)),
Some(Node::Local(_)) => true,
_ => false,
impl Position {
fn is_deref_stable(self) -> bool {
matches!(self, Self::DerefStable(_))
}
}
/// Checks if the given expression is a position which can auto-borrow.
fn is_auto_borrow_position(parent: Option<Node<'_>>, child_id: HirId) -> bool {
if let Some(Node::Expr(parent)) = parent {
match parent.kind {
// ExprKind::MethodCall(_, [self_arg, ..], _) => self_arg.hir_id == child_id,
ExprKind::Field(..) => true,
ExprKind::Call(f, _) => f.hir_id == child_id,
_ => false,
fn is_reborrow_stable(self) -> bool {
matches!(self, Self::DerefStable(_) | Self::ReborrowStable(_))
}
fn can_auto_borrow(self) -> bool {
matches!(self, Self::MethodReceiver | Self::FieldAccess(_) | Self::Callee)
}
fn lint_explicit_deref(self) -> bool {
matches!(self, Self::Other(_) | Self::DerefStable(_) | Self::ReborrowStable(_))
}
fn precedence(self) -> i8 {
match self {
Self::MethodReceiver
| Self::MethodReceiverRefImpl
| Self::Callee
| Self::FieldAccess(_)
| Self::Postfix => PREC_POSTFIX,
Self::Deref => PREC_PREFIX,
Self::DerefStable(p) | Self::ReborrowStable(p) | Self::Other(p) => p,
}
}
}
/// Walks up the parent expressions attempting to determine both how stable the auto-deref result
/// is, and which adjustments will be applied to it. Note this will not consider auto-borrow
/// locations as those follow different rules.
#[allow(clippy::too_many_lines)]
fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &'tcx [Adjustment<'tcx>]) {
let mut adjustments = [].as_slice();
let mut precedence = 0i8;
let ctxt = e.span.ctxt();
let position = walk_to_expr_usage(cx, e, &mut |parent, child_id| {
// LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead.
if adjustments.is_empty() && let Node::Expr(e) = cx.tcx.hir().get(child_id) {
adjustments = cx.typeck_results().expr_adjustments(e);
}
match parent {
Node::Local(Local { ty: Some(ty), span, .. }) if span.ctxt() == ctxt => {
Some(binding_ty_auto_deref_stability(ty, precedence))
},
Node::Item(&Item {
kind: ItemKind::Static(..) | ItemKind::Const(..),
def_id,
span,
..
})
| Node::TraitItem(&TraitItem {
kind: TraitItemKind::Const(..),
def_id,
span,
..
})
| Node::ImplItem(&ImplItem {
kind: ImplItemKind::Const(..),
def_id,
span,
..
}) if span.ctxt() == ctxt => {
let ty = cx.tcx.type_of(def_id);
Some(if ty.is_ref() {
Position::DerefStable(precedence)
} else {
Position::Other(precedence)
})
},
Node::Item(&Item {
kind: ItemKind::Fn(..),
def_id,
span,
..
})
| Node::TraitItem(&TraitItem {
kind: TraitItemKind::Fn(..),
def_id,
span,
..
})
| Node::ImplItem(&ImplItem {
kind: ImplItemKind::Fn(..),
def_id,
span,
..
}) if span.ctxt() == ctxt => {
let output = cx.tcx.fn_sig(def_id.to_def_id()).skip_binder().output();
Some(if !output.is_ref() {
Position::Other(precedence)
} else if output.has_placeholders() || output.has_opaque_types() {
Position::ReborrowStable(precedence)
} else {
Position::DerefStable(precedence)
})
},
Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind {
ExprKind::Ret(_) => {
let output = cx
.tcx
.fn_sig(cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()))
.skip_binder()
.output();
Some(if !output.is_ref() {
Position::Other(precedence)
} else if output.has_placeholders() || output.has_opaque_types() {
Position::ReborrowStable(precedence)
} else {
Position::DerefStable(precedence)
})
},
ExprKind::Call(func, _) if func.hir_id == child_id => (child_id == e.hir_id).then(|| Position::Callee),
ExprKind::Call(func, args) => args
.iter()
.position(|arg| arg.hir_id == child_id)
.zip(expr_sig(cx, func))
.and_then(|(i, sig)| sig.input_with_hir(i))
.map(|(hir_ty, ty)| match hir_ty {
// Type inference for closures can depend on how they're called. Only go by the explicit
// types here.
Some(ty) => binding_ty_auto_deref_stability(ty, precedence),
None => param_auto_deref_stability(ty.skip_binder(), precedence),
}),
ExprKind::MethodCall(_, args, _) => {
let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap();
args.iter().position(|arg| arg.hir_id == child_id).map(|i| {
if i == 0 {
// Check for calls to trait methods where the trait is implemented on a reference.
// Two cases need to be handled:
// * `self` methods on `&T` will never have auto-borrow
// * `&self` methods on `&T` can have auto-borrow, but `&self` methods on `T` will take
// priority.
if e.hir_id != child_id {
Position::ReborrowStable(precedence)
} else if let Some(trait_id) = cx.tcx.trait_of_item(id)
&& let arg_ty = cx.tcx.erase_regions(cx.typeck_results().expr_ty_adjusted(e))
&& let ty::Ref(_, sub_ty, _) = *arg_ty.kind()
&& let subs = cx.typeck_results().node_substs_opt(child_id).unwrap_or_else(
|| cx.tcx.mk_substs([].iter())
) && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() {
// Trait methods taking `&self`
sub_ty
} else {
// Trait methods taking `self`
arg_ty
} && impl_ty.is_ref()
&& cx.tcx.infer_ctxt().enter(|infcx|
infcx
.type_implements_trait(trait_id, impl_ty, subs, cx.param_env)
.must_apply_modulo_regions()
)
{
Position::MethodReceiverRefImpl
} else {
Position::MethodReceiver
}
} else {
param_auto_deref_stability(cx.tcx.fn_sig(id).skip_binder().inputs()[i], precedence)
}
})
},
ExprKind::Struct(path, fields, _) => {
let variant = variant_of_res(cx, cx.qpath_res(path, parent.hir_id));
fields
.iter()
.find(|f| f.expr.hir_id == child_id)
.zip(variant)
.and_then(|(field, variant)| variant.fields.iter().find(|f| f.name == field.ident.name))
.map(|field| param_auto_deref_stability(cx.tcx.type_of(field.did), precedence))
},
ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(Position::FieldAccess(name.name)),
ExprKind::Unary(UnOp::Deref, child) if child.hir_id == e.hir_id => Some(Position::Deref),
ExprKind::Match(child, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
| ExprKind::Index(child, _)
if child.hir_id == e.hir_id =>
{
Some(Position::Postfix)
},
_ if child_id == e.hir_id => {
precedence = parent.precedence().order();
None
},
_ => None,
},
_ => None,
}
})
.unwrap_or(Position::Other(precedence));
(position, adjustments)
}
// Checks the stability of auto-deref when assigned to a binding with the given explicit type.
//
// e.g.
// let x = Box::new(Box::new(0u32));
// let y1: &Box<_> = x.deref();
// let y2: &Box<_> = &x;
//
// Here `y1` and `y2` would resolve to different types, so the type `&Box<_>` is not stable when
// switching to auto-dereferencing.
fn binding_ty_auto_deref_stability(ty: &hir::Ty<'_>, precedence: i8) -> Position {
let TyKind::Rptr(_, ty) = &ty.kind else {
return Position::Other(precedence);
};
let mut ty = ty;
loop {
break match ty.ty.kind {
TyKind::Rptr(_, ref ref_ty) => {
ty = ref_ty;
continue;
},
TyKind::Path(
QPath::TypeRelative(_, path)
| QPath::Resolved(
_,
Path {
segments: [.., path], ..
},
),
) => {
if let Some(args) = path.args
&& args.args.iter().any(|arg| match arg {
GenericArg::Infer(_) => true,
GenericArg::Type(ty) => ty_contains_infer(ty),
_ => false,
})
{
Position::ReborrowStable(precedence)
} else {
Position::DerefStable(precedence)
}
},
TyKind::Slice(_)
| TyKind::Array(..)
| TyKind::BareFn(_)
| TyKind::Never
| TyKind::Tup(_)
| TyKind::Ptr(_)
| TyKind::TraitObject(..)
| TyKind::Path(_) => Position::DerefStable(precedence),
TyKind::OpaqueDef(..)
| TyKind::Infer
| TyKind::Typeof(..)
| TyKind::Err => Position::ReborrowStable(precedence),
};
}
}
// Checks whether a type is inferred at some point.
// e.g. `_`, `Box<_>`, `[_]`
fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
struct V(bool);
impl Visitor<'_> for V {
fn visit_ty(&mut self, ty: &hir::Ty<'_>) {
if self.0
|| matches!(
ty.kind,
TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(_) | TyKind::Err
)
{
self.0 = true;
} else {
walk_ty(self, ty);
}
}
fn visit_generic_arg(&mut self, arg: &GenericArg<'_>) {
if self.0 || matches!(arg, GenericArg::Infer(_)) {
self.0 = true;
} else if let GenericArg::Type(ty) = arg {
self.visit_ty(ty);
}
}
}
let mut v = V(false);
v.visit_ty(ty);
v.0
}
// Checks whether a type is stable when switching to auto dereferencing,
fn param_auto_deref_stability(ty: Ty<'_>, precedence: i8) -> Position {
let ty::Ref(_, mut ty, _) = *ty.kind() else {
return Position::Other(precedence);
};
loop {
break match *ty.kind() {
ty::Ref(_, ref_ty, _) => {
ty = ref_ty;
continue;
},
ty::Infer(_)
| ty::Error(_)
| ty::Param(_)
| ty::Bound(..)
| ty::Opaque(..)
| ty::Placeholder(_)
| ty::Dynamic(..) => Position::ReborrowStable(precedence),
ty::Adt(..) if ty.has_placeholders() || ty.has_param_types_or_consts() => {
Position::ReborrowStable(precedence)
},
ty::Adt(..)
| ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Foreign(_)
| ty::Str
| ty::Array(..)
| ty::Slice(..)
| ty::RawPtr(..)
| ty::FnDef(..)
| ty::FnPtr(_)
| ty::Closure(..)
| ty::Generator(..)
| ty::GeneratorWitness(..)
| ty::Never
| ty::Tuple(_)
| ty::Projection(_) => Position::DerefStable(precedence),
};
}
}
fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool {
if let ty::Adt(adt, _) = *ty.kind() {
adt.is_struct() && adt.all_fields().any(|f| f.name == name)
} else {
false
}
}
/// Adjustments are sometimes made in the parent block rather than the expression itself.
fn find_adjustments<'tcx>(
tcx: TyCtxt<'tcx>,
typeck: &'tcx TypeckResults<'tcx>,
expr: &'tcx Expr<'tcx>,
) -> &'tcx [Adjustment<'tcx>] {
let map = tcx.hir();
let mut iter = map.parent_iter(expr.hir_id);
let mut prev = expr;
loop {
match typeck.expr_adjustments(prev) {
[] => (),
a => break a,
};
match iter.next().map(|(_, x)| x) {
Some(Node::Block(_)) => {
if let Some((_, Node::Expr(e))) = iter.next() {
prev = e;
} else {
// This shouldn't happen. Blocks are always contained in an expression.
break &[];
}
},
Some(Node::Expr(&Expr {
kind: ExprKind::Break(Destination { target_id: Ok(id), .. }, _),
..
})) => {
if let Some(Node::Expr(e)) = map.find(id) {
prev = e;
iter = map.parent_iter(id);
} else {
// This shouldn't happen. The destination should exist.
break &[];
}
},
_ => break &[],
}
}
}
#[expect(clippy::needless_pass_by_value)]
fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: StateData) {
#[expect(clippy::needless_pass_by_value, clippy::too_many_lines)]
fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData) {
match state {
State::DerefMethod {
ty_changed_count,
@ -647,15 +1003,14 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: S
app,
);
},
State::DerefedBorrow {
required_precedence,
msg,
..
} => {
State::DerefedBorrow(state) => {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0;
span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, msg, |diag| {
let sugg = if required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) {
let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| {
let sugg = if !snip_is_macro
&& expr.precedence().order() < data.position.precedence()
&& !has_enclosing_paren(&snip)
{
format!("({})", snip)
} else {
snip.into()
@ -663,6 +1018,48 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: S
diag.span_suggestion(data.span, "change this to", sugg, app);
});
},
State::ExplicitDeref { deref_span_id } => {
let (span, hir_id, precedence) = if let Some((span, hir_id)) = deref_span_id
&& !cx.typeck_results().expr_ty(expr).is_ref()
{
(span, hir_id, PREC_PREFIX)
} else {
(data.span, data.hir_id, data.position.precedence())
};
span_lint_hir_and_then(
cx,
EXPLICIT_AUTO_DEREF,
hir_id,
span,
"deref which would be done by auto-deref",
|diag| {
let mut app = Applicability::MachineApplicable;
let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, span.ctxt(), "..", &mut app);
let sugg =
if !snip_is_macro && expr.precedence().order() < precedence && !has_enclosing_paren(&snip) {
format!("({})", snip)
} else {
snip.into()
};
diag.span_suggestion(span, "try this", sugg, app);
},
);
},
State::ExplicitDerefField { .. } => {
span_lint_hir_and_then(
cx,
EXPLICIT_AUTO_DEREF,
data.hir_id,
data.span,
"deref which would be done by auto-deref",
|diag| {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0;
diag.span_suggestion(data.span, "try this", snip.into_owned(), app);
},
);
},
State::Borrow | State::Reborrow { .. } => (),
}
}

View file

@ -1,96 +0,0 @@
//! Lint on unnecessary double comparisons. Some examples:
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eq_expr_value;
use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
declare_clippy_lint! {
/// ### What it does
/// Checks for double comparisons that could be simplified to a single expression.
///
///
/// ### Why is this bad?
/// Readability.
///
/// ### Example
/// ```rust
/// # let x = 1;
/// # let y = 2;
/// if x == y || x < y {}
/// ```
///
/// Use instead:
///
/// ```rust
/// # let x = 1;
/// # let y = 2;
/// if x <= y {}
/// ```
#[clippy::version = "pre 1.29.0"]
pub DOUBLE_COMPARISONS,
complexity,
"unnecessary double comparisons that can be simplified"
}
declare_lint_pass!(DoubleComparisons => [DOUBLE_COMPARISONS]);
impl<'tcx> DoubleComparisons {
#[expect(clippy::similar_names)]
fn check_binop(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) {
let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) {
(ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => {
(lb.node, llhs, lrhs, rb.node, rlhs, rrhs)
},
_ => return,
};
if !(eq_expr_value(cx, llhs, rlhs) && eq_expr_value(cx, lrhs, rrhs)) {
return;
}
macro_rules! lint_double_comparison {
($op:tt) => {{
let mut applicability = Applicability::MachineApplicable;
let lhs_str = snippet_with_applicability(cx, llhs.span, "", &mut applicability);
let rhs_str = snippet_with_applicability(cx, lrhs.span, "", &mut applicability);
let sugg = format!("{} {} {}", lhs_str, stringify!($op), rhs_str);
span_lint_and_sugg(
cx,
DOUBLE_COMPARISONS,
span,
"this binary expression can be simplified",
"try",
sugg,
applicability,
);
}};
}
#[rustfmt::skip]
match (op, lkind, rkind) {
(BinOpKind::Or, BinOpKind::Eq, BinOpKind::Lt) | (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Eq) => {
lint_double_comparison!(<=);
},
(BinOpKind::Or, BinOpKind::Eq, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Eq) => {
lint_double_comparison!(>=);
},
(BinOpKind::Or, BinOpKind::Lt, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Lt) => {
lint_double_comparison!(!=);
},
(BinOpKind::And, BinOpKind::Le, BinOpKind::Ge) | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => {
lint_double_comparison!(==);
},
_ => (),
};
}
}
impl<'tcx> LateLintPass<'tcx> for DoubleComparisons {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Binary(ref kind, lhs, rhs) = expr.kind {
Self::check_binop(cx, kind.node, lhs, rhs, expr.span);
}
}
}

View file

@ -1,75 +0,0 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Checks for calculation of subsecond microseconds or milliseconds
/// from other `Duration` methods.
///
/// ### Why is this bad?
/// It's more concise to call `Duration::subsec_micros()` or
/// `Duration::subsec_millis()` than to calculate them.
///
/// ### Example
/// ```rust
/// # use std::time::Duration;
/// # let duration = Duration::new(5, 0);
/// let micros = duration.subsec_nanos() / 1_000;
/// let millis = duration.subsec_nanos() / 1_000_000;
/// ```
///
/// Use instead:
/// ```rust
/// # use std::time::Duration;
/// # let duration = Duration::new(5, 0);
/// let micros = duration.subsec_micros();
/// let millis = duration.subsec_millis();
/// ```
#[clippy::version = "pre 1.29.0"]
pub DURATION_SUBSEC,
complexity,
"checks for calculation of subsecond microseconds or milliseconds"
}
declare_lint_pass!(DurationSubsec => [DURATION_SUBSEC]);
impl<'tcx> LateLintPass<'tcx> for DurationSubsec {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Div, .. }, left, right) = expr.kind;
if let ExprKind::MethodCall(method_path, args, _) = left.kind;
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), sym::Duration);
if let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right);
then {
let suggested_fn = match (method_path.ident.as_str(), divisor) {
("subsec_micros", 1_000) | ("subsec_nanos", 1_000_000) => "subsec_millis",
("subsec_nanos", 1_000) => "subsec_micros",
_ => return,
};
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
DURATION_SUBSEC,
expr.span,
&format!("calling `{}()` is more concise than this calculation", suggested_fn),
"try",
format!(
"{}.{}()",
snippet_with_applicability(cx, args[0].span, "_", &mut applicability),
suggested_fn
),
applicability,
);
}
}
}
}

View file

@ -190,7 +190,7 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n
.map(|e| *e.0)
.collect();
}
let (what, value) = match (pre.is_empty(), post.is_empty()) {
let (what, value) = match (have_no_extra_prefix(&pre), post.is_empty()) {
(true, true) => return,
(false, _) => ("pre", pre.join("")),
(true, false) => {
@ -212,6 +212,11 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n
);
}
#[must_use]
fn have_no_extra_prefix(prefixes: &[&str]) -> bool {
prefixes.iter().all(|p| p == &"" || p == &"_")
}
#[must_use]
fn to_camel_case(item_name: &str) -> String {
let mut s = String::new();

View file

@ -1,319 +0,0 @@
use clippy_utils::diagnostics::{multispan_sugg, span_lint, span_lint_and_then};
use clippy_utils::get_enclosing_block;
use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace};
use clippy_utils::source::snippet;
use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, is_in_test_function};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{def::Res, def_id::DefId, BinOpKind, BorrowKind, Expr, ExprKind, GenericArg, ItemKind, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks for equal operands to comparison, logical and
/// bitwise, difference and division binary operators (`==`, `>`, etc., `&&`,
/// `||`, `&`, `|`, `^`, `-` and `/`).
///
/// ### Why is this bad?
/// This is usually just a typo or a copy and paste error.
///
/// ### Known problems
/// False negatives: We had some false positives regarding
/// calls (notably [racer](https://github.com/phildawes/racer) had one instance
/// of `x.pop() && x.pop()`), so we removed matching any function or method
/// calls. We may introduce a list of known pure functions in the future.
///
/// ### Example
/// ```rust
/// # let x = 1;
/// if x + 1 == x + 1 {}
///
/// // or
///
/// # let a = 3;
/// # let b = 4;
/// assert_eq!(a, a);
/// ```
#[clippy::version = "pre 1.29.0"]
pub EQ_OP,
correctness,
"equal operands on both sides of a comparison or bitwise combination (e.g., `x == x`)"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for arguments to `==` which have their address
/// taken to satisfy a bound
/// and suggests to dereference the other argument instead
///
/// ### Why is this bad?
/// It is more idiomatic to dereference the other argument.
///
/// ### Example
/// ```rust,ignore
/// &x == y
/// ```
///
/// Use instead:
/// ```rust,ignore
/// x == *y
/// ```
#[clippy::version = "pre 1.29.0"]
pub OP_REF,
style,
"taking a reference to satisfy the type constraints on `==`"
}
declare_lint_pass!(EqOp => [EQ_OP, OP_REF]);
impl<'tcx> LateLintPass<'tcx> for EqOp {
#[expect(clippy::similar_names, clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if_chain! {
if let Some((macro_call, macro_name)) = first_node_macro_backtrace(cx, e).find_map(|macro_call| {
let name = cx.tcx.item_name(macro_call.def_id);
matches!(name.as_str(), "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne")
.then(|| (macro_call, name))
});
if let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn);
if eq_expr_value(cx, lhs, rhs);
if macro_call.is_local();
if !is_in_test_function(cx.tcx, e.hir_id);
then {
span_lint(
cx,
EQ_OP,
lhs.span.to(rhs.span),
&format!("identical args used in this `{}!` macro call", macro_name),
);
}
}
if let ExprKind::Binary(op, left, right) = e.kind {
if e.span.from_expansion() {
return;
}
let macro_with_not_op = |expr_kind: &ExprKind<'_>| {
if let ExprKind::Unary(_, expr) = *expr_kind {
expr.span.from_expansion()
} else {
false
}
};
if macro_with_not_op(&left.kind) || macro_with_not_op(&right.kind) {
return;
}
if is_useless_with_eq_exprs(op.node.into())
&& eq_expr_value(cx, left, right)
&& !is_in_test_function(cx.tcx, e.hir_id)
{
span_lint(
cx,
EQ_OP,
e.span,
&format!("equal expressions as operands to `{}`", op.node.as_str()),
);
return;
}
let (trait_id, requires_ref) = match op.node {
BinOpKind::Add => (cx.tcx.lang_items().add_trait(), false),
BinOpKind::Sub => (cx.tcx.lang_items().sub_trait(), false),
BinOpKind::Mul => (cx.tcx.lang_items().mul_trait(), false),
BinOpKind::Div => (cx.tcx.lang_items().div_trait(), false),
BinOpKind::Rem => (cx.tcx.lang_items().rem_trait(), false),
// don't lint short circuiting ops
BinOpKind::And | BinOpKind::Or => return,
BinOpKind::BitXor => (cx.tcx.lang_items().bitxor_trait(), false),
BinOpKind::BitAnd => (cx.tcx.lang_items().bitand_trait(), false),
BinOpKind::BitOr => (cx.tcx.lang_items().bitor_trait(), false),
BinOpKind::Shl => (cx.tcx.lang_items().shl_trait(), false),
BinOpKind::Shr => (cx.tcx.lang_items().shr_trait(), false),
BinOpKind::Ne | BinOpKind::Eq => (cx.tcx.lang_items().eq_trait(), true),
BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ge | BinOpKind::Gt => {
(cx.tcx.lang_items().partial_ord_trait(), true)
},
};
if let Some(trait_id) = trait_id {
match (&left.kind, &right.kind) {
// do not suggest to dereference literals
(&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {},
// &foo == &bar
(&ExprKind::AddrOf(BorrowKind::Ref, _, l), &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
let lty = cx.typeck_results().expr_ty(l);
let rty = cx.typeck_results().expr_ty(r);
let lcpy = is_copy(cx, lty);
let rcpy = is_copy(cx, rty);
if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) {
if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty))
|| (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))
{
return; // Don't lint
}
}
// either operator autorefs or both args are copyable
if (requires_ref || (lcpy && rcpy)) && implements_trait(cx, lty, trait_id, &[rty.into()]) {
span_lint_and_then(
cx,
OP_REF,
e.span,
"needlessly taken reference of both operands",
|diag| {
let lsnip = snippet(cx, l.span, "...").to_string();
let rsnip = snippet(cx, r.span, "...").to_string();
multispan_sugg(
diag,
"use the values directly",
vec![(left.span, lsnip), (right.span, rsnip)],
);
},
);
} else if lcpy
&& !rcpy
&& implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()])
{
span_lint_and_then(
cx,
OP_REF,
e.span,
"needlessly taken reference of left operand",
|diag| {
let lsnip = snippet(cx, l.span, "...").to_string();
diag.span_suggestion(
left.span,
"use the left value directly",
lsnip,
Applicability::MaybeIncorrect, // FIXME #2597
);
},
);
} else if !lcpy
&& rcpy
&& implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()])
{
span_lint_and_then(
cx,
OP_REF,
e.span,
"needlessly taken reference of right operand",
|diag| {
let rsnip = snippet(cx, r.span, "...").to_string();
diag.span_suggestion(
right.span,
"use the right value directly",
rsnip,
Applicability::MaybeIncorrect, // FIXME #2597
);
},
);
}
},
// &foo == bar
(&ExprKind::AddrOf(BorrowKind::Ref, _, l), _) => {
let lty = cx.typeck_results().expr_ty(l);
if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) {
let rty = cx.typeck_results().expr_ty(right);
if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty))
|| (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))
{
return; // Don't lint
}
}
let lcpy = is_copy(cx, lty);
if (requires_ref || lcpy)
&& implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()])
{
span_lint_and_then(
cx,
OP_REF,
e.span,
"needlessly taken reference of left operand",
|diag| {
let lsnip = snippet(cx, l.span, "...").to_string();
diag.span_suggestion(
left.span,
"use the left value directly",
lsnip,
Applicability::MaybeIncorrect, // FIXME #2597
);
},
);
}
},
// foo == &bar
(_, &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
let rty = cx.typeck_results().expr_ty(r);
if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) {
let lty = cx.typeck_results().expr_ty(left);
if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty))
|| (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))
{
return; // Don't lint
}
}
let rcpy = is_copy(cx, rty);
if (requires_ref || rcpy)
&& implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()])
{
span_lint_and_then(cx, OP_REF, e.span, "taken reference of right operand", |diag| {
let rsnip = snippet(cx, r.span, "...").to_string();
diag.span_suggestion(
right.span,
"use the right value directly",
rsnip,
Applicability::MaybeIncorrect, // FIXME #2597
);
});
}
},
_ => {},
}
}
}
}
}
fn in_impl<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
bin_op: DefId,
) -> Option<(&'tcx rustc_hir::Ty<'tcx>, &'tcx rustc_hir::Ty<'tcx>)> {
if_chain! {
if let Some(block) = get_enclosing_block(cx, e.hir_id);
if let Some(impl_def_id) = cx.tcx.impl_of_method(block.hir_id.owner.to_def_id());
let item = cx.tcx.hir().expect_item(impl_def_id.expect_local());
if let ItemKind::Impl(item) = &item.kind;
if let Some(of_trait) = &item.of_trait;
if let Some(seg) = of_trait.path.segments.last();
if let Some(Res::Def(_, trait_id)) = seg.res;
if trait_id == bin_op;
if let Some(generic_args) = seg.args;
if let Some(GenericArg::Type(other_ty)) = generic_args.args.last();
then {
Some((item.self_ty, other_ty))
}
else {
None
}
}
}
fn are_equal<'tcx>(cx: &LateContext<'tcx>, middle_ty: Ty<'_>, hir_ty: &rustc_hir::Ty<'_>) -> bool {
if_chain! {
if let ty::Adt(adt_def, _) = middle_ty.kind();
if let Some(local_did) = adt_def.did().as_local();
let item = cx.tcx.hir().expect_item(local_did);
let middle_ty_id = item.def_id.to_def_id();
if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind;
if let Res::Def(_, hir_ty_id) = path.res;
then {
hir_ty_id == middle_ty_id
}
else {
false
}
}
}

View file

@ -1,77 +0,0 @@
use clippy_utils::consts::{constant_simple, Constant};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::same_type_and_consts;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TypeckResults;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks for erasing operations, e.g., `x * 0`.
///
/// ### Why is this bad?
/// The whole expression can be replaced by zero.
/// This is most likely not the intended outcome and should probably be
/// corrected
///
/// ### Example
/// ```rust
/// let x = 1;
/// 0 / x;
/// 0 * x;
/// x & 0;
/// ```
#[clippy::version = "pre 1.29.0"]
pub ERASING_OP,
correctness,
"using erasing operations, e.g., `x * 0` or `y & 0`"
}
declare_lint_pass!(ErasingOp => [ERASING_OP]);
impl<'tcx> LateLintPass<'tcx> for ErasingOp {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if e.span.from_expansion() {
return;
}
if let ExprKind::Binary(ref cmp, left, right) = e.kind {
let tck = cx.typeck_results();
match cmp.node {
BinOpKind::Mul | BinOpKind::BitAnd => {
check(cx, tck, left, right, e);
check(cx, tck, right, left, e);
},
BinOpKind::Div => check(cx, tck, left, right, e),
_ => (),
}
}
}
}
fn different_types(tck: &TypeckResults<'_>, input: &Expr<'_>, output: &Expr<'_>) -> bool {
let input_ty = tck.expr_ty(input).peel_refs();
let output_ty = tck.expr_ty(output).peel_refs();
!same_type_and_consts(input_ty, output_ty)
}
fn check<'tcx>(
cx: &LateContext<'tcx>,
tck: &TypeckResults<'tcx>,
op: &Expr<'tcx>,
other: &Expr<'tcx>,
parent: &Expr<'tcx>,
) {
if constant_simple(cx, tck, op) == Some(Constant::Int(0)) {
if different_types(tck, other, parent) {
return;
}
span_lint(
cx,
ERASING_OP,
parent.span,
"this operation will always return zero. This is likely not the intended outcome",
);
}
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::diagnostics::span_lint_hir;
use clippy_utils::ty::contains_ty;
use rustc_hir::intravisit;
use rustc_hir::{self, AssocItemKind, Body, FnDecl, HirId, HirIdSet, Impl, ItemKind, Node};
@ -118,9 +118,10 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
});
for node in v.set {
span_lint(
span_lint_hir(
cx,
BOXED_LOCAL,
node,
cx.tcx.hir().span(node),
"local variable doesn't need to be boxed here",
);

View file

@ -1,116 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{match_def_path, paths, sugg};
use if_chain::if_chain;
use rustc_ast::util::parser::AssocOp;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;
declare_clippy_lint! {
/// ### What it does
/// Checks for statements of the form `(a - b) < f32::EPSILON` or
/// `(a - b) < f64::EPSILON`. Notes the missing `.abs()`.
///
/// ### Why is this bad?
/// The code without `.abs()` is more likely to have a bug.
///
/// ### Known problems
/// If the user can ensure that b is larger than a, the `.abs()` is
/// technically unnecessary. However, it will make the code more robust and doesn't have any
/// large performance implications. If the abs call was deliberately left out for performance
/// reasons, it is probably better to state this explicitly in the code, which then can be done
/// with an allow.
///
/// ### Example
/// ```rust
/// pub fn is_roughly_equal(a: f32, b: f32) -> bool {
/// (a - b) < f32::EPSILON
/// }
/// ```
/// Use instead:
/// ```rust
/// pub fn is_roughly_equal(a: f32, b: f32) -> bool {
/// (a - b).abs() < f32::EPSILON
/// }
/// ```
#[clippy::version = "1.48.0"]
pub FLOAT_EQUALITY_WITHOUT_ABS,
suspicious,
"float equality check without `.abs()`"
}
declare_lint_pass!(FloatEqualityWithoutAbs => [FLOAT_EQUALITY_WITHOUT_ABS]);
impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let lhs;
let rhs;
// check if expr is a binary expression with a lt or gt operator
if let ExprKind::Binary(op, left, right) = expr.kind {
match op.node {
BinOpKind::Lt => {
lhs = left;
rhs = right;
},
BinOpKind::Gt => {
lhs = right;
rhs = left;
},
_ => return,
};
} else {
return;
}
if_chain! {
// left hand side is a subtraction
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Sub,
..
},
val_l,
val_r,
) = lhs.kind;
// right hand side matches either f32::EPSILON or f64::EPSILON
if let ExprKind::Path(ref epsilon_path) = rhs.kind;
if let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id);
if match_def_path(cx, def_id, &paths::F32_EPSILON) || match_def_path(cx, def_id, &paths::F64_EPSILON);
// values of the subtractions on the left hand side are of the type float
let t_val_l = cx.typeck_results().expr_ty(val_l);
let t_val_r = cx.typeck_results().expr_ty(val_r);
if let ty::Float(_) = t_val_l.kind();
if let ty::Float(_) = t_val_r.kind();
then {
let sug_l = sugg::Sugg::hir(cx, val_l, "..");
let sug_r = sugg::Sugg::hir(cx, val_r, "..");
// format the suggestion
let suggestion = format!("{}.abs()", sugg::make_assoc(AssocOp::Subtract, &sug_l, &sug_r).maybe_par());
// spans the lint
span_lint_and_then(
cx,
FLOAT_EQUALITY_WITHOUT_ABS,
expr.span,
"float equality check without `.abs()`",
| diag | {
diag.span_suggestion(
lhs.span,
"add `.abs()`",
suggestion,
Applicability::MaybeIncorrect,
);
}
);
}
}
}
}

View file

@ -1,5 +1,5 @@
use clippy_utils::{
diagnostics::span_lint_and_sugg,
diagnostics::span_lint_hir_and_then,
get_async_fn_body, is_async_fn,
source::{snippet_with_applicability, snippet_with_context, walk_span_to_context},
visitors::expr_visitor_no_bodies,
@ -43,31 +43,38 @@ declare_clippy_lint! {
declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]);
fn lint_return(cx: &LateContext<'_>, span: Span) {
fn lint_return(cx: &LateContext<'_>, emission_place: HirId, span: Span) {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_applicability(cx, span, "..", &mut app);
span_lint_and_sugg(
span_lint_hir_and_then(
cx,
IMPLICIT_RETURN,
emission_place,
span,
"missing `return` statement",
"add `return` as shown",
format!("return {}", snip),
app,
|diag| {
diag.span_suggestion(span, "add `return` as shown", format!("return {}", snip), app);
},
);
}
fn lint_break(cx: &LateContext<'_>, break_span: Span, expr_span: Span) {
fn lint_break(cx: &LateContext<'_>, emission_place: HirId, break_span: Span, expr_span: Span) {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0;
span_lint_and_sugg(
span_lint_hir_and_then(
cx,
IMPLICIT_RETURN,
emission_place,
break_span,
"missing `return` statement",
"change `break` to `return` as shown",
format!("return {}", snip),
app,
|diag| {
diag.span_suggestion(
break_span,
"change `break` to `return` as shown",
format!("return {}", snip),
app,
);
},
);
}
@ -152,7 +159,7 @@ fn lint_implicit_returns(
// At this point sub_expr can be `None` in async functions which either diverge, or return
// the unit type.
if let Some(sub_expr) = sub_expr {
lint_break(cx, e.span, sub_expr.span);
lint_break(cx, e.hir_id, e.span, sub_expr.span);
}
} else {
// the break expression is from a macro call, add a return to the loop
@ -166,10 +173,10 @@ fn lint_implicit_returns(
if add_return {
#[expect(clippy::option_if_let_else)]
if let Some(span) = call_site_span {
lint_return(cx, span);
lint_return(cx, expr.hir_id, span);
LintLocation::Parent
} else {
lint_return(cx, expr.span);
lint_return(cx, expr.hir_id, expr.span);
LintLocation::Inner
}
} else {
@ -198,10 +205,10 @@ fn lint_implicit_returns(
{
#[expect(clippy::option_if_let_else)]
if let Some(span) = call_site_span {
lint_return(cx, span);
lint_return(cx, expr.hir_id, span);
LintLocation::Parent
} else {
lint_return(cx, expr.span);
lint_return(cx, expr.hir_id, expr.span);
LintLocation::Inner
}
},

View file

@ -1,61 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_help;
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks for division of integers
///
/// ### Why is this bad?
/// When outside of some very specific algorithms,
/// integer division is very often a mistake because it discards the
/// remainder.
///
/// ### Example
/// ```rust
/// let x = 3 / 2;
/// println!("{}", x);
/// ```
///
/// Use instead:
/// ```rust
/// let x = 3f32 / 2f32;
/// println!("{}", x);
/// ```
#[clippy::version = "1.37.0"]
pub INTEGER_DIVISION,
restriction,
"integer division may cause loss of precision"
}
declare_lint_pass!(IntegerDivision => [INTEGER_DIVISION]);
impl<'tcx> LateLintPass<'tcx> for IntegerDivision {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if is_integer_division(cx, expr) {
span_lint_and_help(
cx,
INTEGER_DIVISION,
expr.span,
"integer division",
None,
"division of integers may cause loss of precision. consider using floats",
);
}
}
}
fn is_integer_division<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) -> bool {
if_chain! {
if let hir::ExprKind::Binary(binop, left, right) = &expr.kind;
if binop.node == hir::BinOpKind::Div;
then {
let (left_ty, right_ty) = (cx.typeck_results().expr_ty(left), cx.typeck_results().expr_ty(right));
return left_ty.is_integral() && right_ty.is_integral();
}
}
false
}

View file

@ -24,7 +24,7 @@ declare_clippy_lint! {
/// ```
///
/// Use instead:
/// ```rust.ignore
/// ```rust,ignore
/// pub static a = [0u32; 1_000_000];
/// ```
#[clippy::version = "1.44.0"]

View file

@ -99,12 +99,13 @@ declare_clippy_lint! {
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_DROP]);
const SYNC_GUARD_PATHS: [&[&str]; 5] = [
const SYNC_GUARD_PATHS: [&[&str]; 6] = [
&paths::MUTEX_GUARD,
&paths::RWLOCK_READ_GUARD,
&paths::RWLOCK_WRITE_GUARD,
&paths::PARKING_LOT_RAWMUTEX,
&paths::PARKING_LOT_RAWRWLOCK,
&paths::PARKING_LOT_MUTEX_GUARD,
&paths::PARKING_LOT_RWLOCK_READ_GUARD,
&paths::PARKING_LOT_RWLOCK_WRITE_GUARD,
];
impl<'tcx> LateLintPass<'tcx> for LetUnderscore {

View file

@ -3,12 +3,9 @@
// Manual edits will be overwritten.
store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS),
LintId::of(almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE),
LintId::of(approx_const::APPROX_CONSTANT),
LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
LintId::of(assign_ops::ASSIGN_OP_PATTERN),
LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP),
LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC),
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
LintId::of(attrs::DEPRECATED_CFG_ATTR),
@ -18,8 +15,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE),
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
LintId::of(bit_mask::BAD_BIT_MASK),
LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
LintId::of(blacklisted_name::BLACKLISTED_NAME),
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
@ -43,6 +38,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(copies::IF_SAME_THEN_ELSE),
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
LintId::of(default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY),
LintId::of(dereference::EXPLICIT_AUTO_DEREF),
LintId::of(dereference::NEEDLESS_BORROW),
LintId::of(derivable_impls::DERIVABLE_IMPLS),
LintId::of(derive::DERIVE_HASH_XOR_EQ),
@ -52,7 +49,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(disallowed_types::DISALLOWED_TYPES),
LintId::of(doc::MISSING_SAFETY_DOC),
LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
LintId::of(double_comparison::DOUBLE_COMPARISONS),
LintId::of(double_parens::DOUBLE_PARENS),
LintId::of(drop_forget_ref::DROP_COPY),
LintId::of(drop_forget_ref::DROP_NON_DROP),
@ -62,18 +58,13 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(drop_forget_ref::FORGET_REF),
LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS),
LintId::of(duplicate_mod::DUPLICATE_MOD),
LintId::of(duration_subsec::DURATION_SUBSEC),
LintId::of(entry::MAP_ENTRY),
LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
LintId::of(enum_variants::ENUM_VARIANT_NAMES),
LintId::of(enum_variants::MODULE_INCEPTION),
LintId::of(eq_op::EQ_OP),
LintId::of(eq_op::OP_REF),
LintId::of(erasing_op::ERASING_OP),
LintId::of(escape::BOXED_LOCAL),
LintId::of(eta_reduction::REDUNDANT_CLOSURE),
LintId::of(explicit_write::EXPLICIT_WRITE),
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
LintId::of(float_literal::EXCESSIVE_PRECISION),
LintId::of(format::USELESS_FORMAT),
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
@ -93,7 +84,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(functions::RESULT_UNIT_ERR),
LintId::of(functions::TOO_MANY_ARGUMENTS),
LintId::of(get_first::GET_FIRST),
LintId::of(identity_op::IDENTITY_OP),
LintId::of(if_let_mutex::IF_LET_MUTEX),
LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING),
LintId::of(infinite_iter::INFINITE_ITER),
@ -118,6 +108,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(loops::FOR_KV_MAP),
LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
LintId::of(loops::ITER_NEXT_LOOP),
LintId::of(loops::MANUAL_FIND),
LintId::of(loops::MANUAL_FLATTEN),
LintId::of(loops::MANUAL_MEMCPY),
LintId::of(loops::MISSING_SPIN_LOOP),
@ -134,6 +125,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(manual_async_fn::MANUAL_ASYNC_FN),
LintId::of(manual_bits::MANUAL_BITS),
LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
LintId::of(manual_rem_euclid::MANUAL_REM_EUCLID),
LintId::of(manual_retain::MANUAL_RETAIN),
LintId::of(manual_strip::MANUAL_STRIP),
LintId::of(map_clone::MAP_CLONE),
LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
@ -220,9 +213,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(methods::WRONG_SELF_CONVENTION),
LintId::of(methods::ZST_OFFSET),
LintId::of(minmax::MIN_MAX),
LintId::of(misc::CMP_NAN),
LintId::of(misc::CMP_OWNED),
LintId::of(misc::MODULO_ONE),
LintId::of(misc::SHORT_CIRCUIT_STATEMENT),
LintId::of(misc::TOPLEVEL_REF_ARG),
LintId::of(misc::ZERO_PTR),
@ -256,6 +246,23 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
LintId::of(octal_escapes::OCTAL_ESCAPES),
LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
LintId::of(operators::ABSURD_EXTREME_COMPARISONS),
LintId::of(operators::ASSIGN_OP_PATTERN),
LintId::of(operators::BAD_BIT_MASK),
LintId::of(operators::CMP_NAN),
LintId::of(operators::CMP_OWNED),
LintId::of(operators::DOUBLE_COMPARISONS),
LintId::of(operators::DURATION_SUBSEC),
LintId::of(operators::EQ_OP),
LintId::of(operators::ERASING_OP),
LintId::of(operators::FLOAT_EQUALITY_WITHOUT_ABS),
LintId::of(operators::IDENTITY_OP),
LintId::of(operators::INEFFECTIVE_BIT_MASK),
LintId::of(operators::MISREFACTORED_ASSIGN_OP),
LintId::of(operators::MODULO_ONE),
LintId::of(operators::OP_REF),
LintId::of(operators::PTR_EQ),
LintId::of(operators::SELF_ASSIGNMENT),
LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
@ -264,7 +271,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(ptr::INVALID_NULL_PTR_USAGE),
LintId::of(ptr::MUT_FROM_REF),
LintId::of(ptr::PTR_ARG),
LintId::of(ptr_eq::PTR_EQ),
LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST),
LintId::of(question_mark::QUESTION_MARK),
LintId::of(ranges::MANUAL_RANGE_CONTAINS),
@ -282,7 +288,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(repeat_once::REPEAT_ONCE),
LintId::of(returns::LET_AND_RETURN),
LintId::of(returns::NEEDLESS_RETURN),
LintId::of(self_assignment::SELF_ASSIGNMENT),
LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS),
LintId::of(serde_api::SERDE_API_MISUSE),
LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),

View file

@ -9,21 +9,21 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN),
LintId::of(casts::CHAR_LIT_AS_U8),
LintId::of(casts::UNNECESSARY_CAST),
LintId::of(dereference::EXPLICIT_AUTO_DEREF),
LintId::of(derivable_impls::DERIVABLE_IMPLS),
LintId::of(double_comparison::DOUBLE_COMPARISONS),
LintId::of(double_parens::DOUBLE_PARENS),
LintId::of(duration_subsec::DURATION_SUBSEC),
LintId::of(explicit_write::EXPLICIT_WRITE),
LintId::of(format::USELESS_FORMAT),
LintId::of(functions::TOO_MANY_ARGUMENTS),
LintId::of(identity_op::IDENTITY_OP),
LintId::of(int_plus_one::INT_PLUS_ONE),
LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
LintId::of(lifetimes::NEEDLESS_LIFETIMES),
LintId::of(loops::EXPLICIT_COUNTER_LOOP),
LintId::of(loops::MANUAL_FIND),
LintId::of(loops::MANUAL_FLATTEN),
LintId::of(loops::SINGLE_ELEMENT_LOOP),
LintId::of(loops::WHILE_LET_LOOP),
LintId::of(manual_rem_euclid::MANUAL_REM_EUCLID),
LintId::of(manual_strip::MANUAL_STRIP),
LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
@ -69,6 +69,9 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
LintId::of(no_effect::NO_EFFECT),
LintId::of(no_effect::UNNECESSARY_OPERATION),
LintId::of(operators::DOUBLE_COMPARISONS),
LintId::of(operators::DURATION_SUBSEC),
LintId::of(operators::IDENTITY_OP),
LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
LintId::of(precedence::PRECEDENCE),

View file

@ -3,14 +3,11 @@
// Manual edits will be overwritten.
store.register_group(true, "clippy::correctness", Some("clippy_correctness"), vec![
LintId::of(absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS),
LintId::of(approx_const::APPROX_CONSTANT),
LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC),
LintId::of(attrs::DEPRECATED_SEMVER),
LintId::of(attrs::MISMATCHED_TARGET_OS),
LintId::of(attrs::USELESS_ATTRIBUTE),
LintId::of(bit_mask::BAD_BIT_MASK),
LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
LintId::of(booleans::LOGIC_BUG),
LintId::of(casts::CAST_REF_TO_MUT),
LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
@ -24,8 +21,6 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
LintId::of(drop_forget_ref::FORGET_REF),
LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS),
LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
LintId::of(eq_op::EQ_OP),
LintId::of(erasing_op::ERASING_OP),
LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF),
@ -47,17 +42,22 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
LintId::of(methods::UNINIT_ASSUMED_INIT),
LintId::of(methods::ZST_OFFSET),
LintId::of(minmax::MIN_MAX),
LintId::of(misc::CMP_NAN),
LintId::of(misc::MODULO_ONE),
LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
LintId::of(operators::ABSURD_EXTREME_COMPARISONS),
LintId::of(operators::BAD_BIT_MASK),
LintId::of(operators::CMP_NAN),
LintId::of(operators::EQ_OP),
LintId::of(operators::ERASING_OP),
LintId::of(operators::INEFFECTIVE_BIT_MASK),
LintId::of(operators::MODULO_ONE),
LintId::of(operators::SELF_ASSIGNMENT),
LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
LintId::of(ptr::INVALID_NULL_PTR_USAGE),
LintId::of(ptr::MUT_FROM_REF),
LintId::of(ranges::REVERSED_EMPTY_RANGES),
LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC),
LintId::of(regex::INVALID_REGEX),
LintId::of(self_assignment::SELF_ASSIGNMENT),
LintId::of(serde_api::SERDE_API_MISUSE),
LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
LintId::of(swap::ALMOST_SWAPPED),

View file

@ -6,6 +6,7 @@ store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
LintId::of(utils::internal_lints::CLIPPY_LINTS_INTERNAL),
LintId::of(utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
LintId::of(utils::internal_lints::COMPILER_LINT_FUNCTIONS),
LintId::of(utils::internal_lints::DEFAULT_DEPRECATION_REASON),
LintId::of(utils::internal_lints::DEFAULT_LINT),
LintId::of(utils::internal_lints::IF_CHAIN_STYLE),
LintId::of(utils::internal_lints::INTERNING_DEFINED_SYMBOL),

View file

@ -10,6 +10,8 @@ store.register_lints(&[
#[cfg(feature = "internal")]
utils::internal_lints::COMPILER_LINT_FUNCTIONS,
#[cfg(feature = "internal")]
utils::internal_lints::DEFAULT_DEPRECATION_REASON,
#[cfg(feature = "internal")]
utils::internal_lints::DEFAULT_LINT,
#[cfg(feature = "internal")]
utils::internal_lints::IF_CHAIN_STYLE,
@ -33,7 +35,6 @@ store.register_lints(&[
utils::internal_lints::PRODUCE_ICE,
#[cfg(feature = "internal")]
utils::internal_lints::UNNECESSARY_SYMBOL_STR,
absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS,
almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE,
approx_const::APPROX_CONSTANT,
as_conversions::AS_CONVERSIONS,
@ -41,8 +42,6 @@ store.register_lints(&[
asm_syntax::INLINE_ASM_X86_ATT_SYNTAX,
asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX,
assertions_on_constants::ASSERTIONS_ON_CONSTANTS,
assign_ops::ASSIGN_OP_PATTERN,
assign_ops::MISREFACTORED_ASSIGN_OP,
async_yields_async::ASYNC_YIELDS_ASYNC,
attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON,
attrs::BLANKET_CLIPPY_RESTRICTION_LINTS,
@ -55,9 +54,6 @@ store.register_lints(&[
await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE,
await_holding_invalid::AWAIT_HOLDING_LOCK,
await_holding_invalid::AWAIT_HOLDING_REFCELL_REF,
bit_mask::BAD_BIT_MASK,
bit_mask::INEFFECTIVE_BIT_MASK,
bit_mask::VERBOSE_BIT_MASK,
blacklisted_name::BLACKLISTED_NAME,
blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS,
bool_assert_comparison::BOOL_ASSERT_COMPARISON,
@ -105,8 +101,10 @@ store.register_lints(&[
dbg_macro::DBG_MACRO,
default::DEFAULT_TRAIT_ACCESS,
default::FIELD_REASSIGN_WITH_DEFAULT,
default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY,
default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK,
default_union_representation::DEFAULT_UNION_REPRESENTATION,
dereference::EXPLICIT_AUTO_DEREF,
dereference::EXPLICIT_DEREF_METHODS,
dereference::NEEDLESS_BORROW,
dereference::REF_BINDING_TO_REFERENCE,
@ -125,7 +123,6 @@ store.register_lints(&[
doc::MISSING_SAFETY_DOC,
doc::NEEDLESS_DOCTEST_MAIN,
doc_link_with_quotes::DOC_LINK_WITH_QUOTES,
double_comparison::DOUBLE_COMPARISONS,
double_parens::DOUBLE_PARENS,
drop_forget_ref::DROP_COPY,
drop_forget_ref::DROP_NON_DROP,
@ -135,7 +132,6 @@ store.register_lints(&[
drop_forget_ref::FORGET_REF,
drop_forget_ref::UNDROPPED_MANUALLY_DROPS,
duplicate_mod::DUPLICATE_MOD,
duration_subsec::DURATION_SUBSEC,
else_if_without_else::ELSE_IF_WITHOUT_ELSE,
empty_drop::EMPTY_DROP,
empty_enum::EMPTY_ENUM,
@ -145,10 +141,7 @@ store.register_lints(&[
enum_variants::ENUM_VARIANT_NAMES,
enum_variants::MODULE_INCEPTION,
enum_variants::MODULE_NAME_REPETITIONS,
eq_op::EQ_OP,
eq_op::OP_REF,
equatable_if_let::EQUATABLE_IF_LET,
erasing_op::ERASING_OP,
escape::BOXED_LOCAL,
eta_reduction::REDUNDANT_CLOSURE,
eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
@ -159,7 +152,6 @@ store.register_lints(&[
exit::EXIT,
explicit_write::EXPLICIT_WRITE,
fallible_impl_from::FALLIBLE_IMPL_FROM,
float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS,
float_literal::EXCESSIVE_PRECISION,
float_literal::LOSSY_FLOAT_LITERAL,
floating_point_arithmetic::IMPRECISE_FLOPS,
@ -185,7 +177,6 @@ store.register_lints(&[
functions::TOO_MANY_LINES,
future_not_send::FUTURE_NOT_SEND,
get_first::GET_FIRST,
identity_op::IDENTITY_OP,
if_let_mutex::IF_LET_MUTEX,
if_not_else::IF_NOT_ELSE,
if_then_some_else_none::IF_THEN_SOME_ELSE_NONE,
@ -204,7 +195,6 @@ store.register_lints(&[
init_numbered_fields::INIT_NUMBERED_FIELDS,
inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
int_plus_one::INT_PLUS_ONE,
integer_division::INTEGER_DIVISION,
invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS,
items_after_statements::ITEMS_AFTER_STATEMENTS,
iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR,
@ -234,6 +224,7 @@ store.register_lints(&[
loops::FOR_KV_MAP,
loops::FOR_LOOPS_OVER_FALLIBLES,
loops::ITER_NEXT_LOOP,
loops::MANUAL_FIND,
loops::MANUAL_FLATTEN,
loops::MANUAL_MEMCPY,
loops::MISSING_SPIN_LOOP,
@ -253,6 +244,8 @@ store.register_lints(&[
manual_bits::MANUAL_BITS,
manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
manual_ok_or::MANUAL_OK_OR,
manual_rem_euclid::MANUAL_REM_EUCLID,
manual_retain::MANUAL_RETAIN,
manual_strip::MANUAL_STRIP,
map_clone::MAP_CLONE,
map_err_ignore::MAP_ERR_IGNORE,
@ -364,11 +357,6 @@ store.register_lints(&[
methods::WRONG_SELF_CONVENTION,
methods::ZST_OFFSET,
minmax::MIN_MAX,
misc::CMP_NAN,
misc::CMP_OWNED,
misc::FLOAT_CMP,
misc::FLOAT_CMP_CONST,
misc::MODULO_ONE,
misc::SHORT_CIRCUIT_STATEMENT,
misc::TOPLEVEL_REF_ARG,
misc::USED_UNDERSCORE_BINDING,
@ -392,7 +380,6 @@ store.register_lints(&[
mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION,
module_style::MOD_MODULE_FILES,
module_style::SELF_NAMED_MODULE_FILES,
modulo_arithmetic::MODULO_ARITHMETIC,
mut_key::MUTABLE_KEY_TYPE,
mut_mut::MUT_MUT,
mut_mutex_lock::MUT_MUTEX_LOCK,
@ -401,7 +388,6 @@ store.register_lints(&[
mutex_atomic::MUTEX_ATOMIC,
mutex_atomic::MUTEX_INTEGER,
needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE,
needless_bitwise_bool::NEEDLESS_BITWISE_BOOL,
needless_bool::BOOL_COMPARISON,
needless_bool::NEEDLESS_BOOL,
needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE,
@ -426,11 +412,34 @@ store.register_lints(&[
non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS,
non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY,
nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES,
numeric_arithmetic::FLOAT_ARITHMETIC,
numeric_arithmetic::INTEGER_ARITHMETIC,
octal_escapes::OCTAL_ESCAPES,
only_used_in_recursion::ONLY_USED_IN_RECURSION,
open_options::NONSENSICAL_OPEN_OPTIONS,
operators::ABSURD_EXTREME_COMPARISONS,
operators::ASSIGN_OP_PATTERN,
operators::BAD_BIT_MASK,
operators::CMP_NAN,
operators::CMP_OWNED,
operators::DOUBLE_COMPARISONS,
operators::DURATION_SUBSEC,
operators::EQ_OP,
operators::ERASING_OP,
operators::FLOAT_ARITHMETIC,
operators::FLOAT_CMP,
operators::FLOAT_CMP_CONST,
operators::FLOAT_EQUALITY_WITHOUT_ABS,
operators::IDENTITY_OP,
operators::INEFFECTIVE_BIT_MASK,
operators::INTEGER_ARITHMETIC,
operators::INTEGER_DIVISION,
operators::MISREFACTORED_ASSIGN_OP,
operators::MODULO_ARITHMETIC,
operators::MODULO_ONE,
operators::NEEDLESS_BITWISE_BOOL,
operators::OP_REF,
operators::PTR_EQ,
operators::SELF_ASSIGNMENT,
operators::VERBOSE_BIT_MASK,
option_env_unwrap::OPTION_ENV_UNWRAP,
option_if_let_else::OPTION_IF_LET_ELSE,
overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL,
@ -449,7 +458,6 @@ store.register_lints(&[
ptr::INVALID_NULL_PTR_USAGE,
ptr::MUT_FROM_REF,
ptr::PTR_ARG,
ptr_eq::PTR_EQ,
ptr_offset_with_cast::PTR_OFFSET_WITH_CAST,
pub_use::PUB_USE,
question_mark::QUESTION_MARK,
@ -477,7 +485,6 @@ store.register_lints(&[
returns::LET_AND_RETURN,
returns::NEEDLESS_RETURN,
same_name_method::SAME_NAME_METHOD,
self_assignment::SELF_ASSIGNMENT,
self_named_constructors::SELF_NAMED_CONSTRUCTORS,
semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED,
serde_api::SERDE_API_MISUSE,

View file

@ -28,6 +28,8 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
LintId::of(strings::STRING_LIT_AS_BYTES),
LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY),
LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS),
LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS),
LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
LintId::of(unused_rounding::UNUSED_ROUNDING),
LintId::of(use_self::USE_SELF),

View file

@ -4,7 +4,6 @@
store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
LintId::of(attrs::INLINE_ALWAYS),
LintId::of(bit_mask::VERBOSE_BIT_MASK),
LintId::of(borrow_as_ptr::BORROW_AS_PTR),
LintId::of(bytecount::NAIVE_BYTECOUNT),
LintId::of(case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS),
@ -65,17 +64,18 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
LintId::of(methods::INEFFICIENT_TO_STRING),
LintId::of(methods::MAP_UNWRAP_OR),
LintId::of(methods::UNNECESSARY_JOIN),
LintId::of(misc::FLOAT_CMP),
LintId::of(misc::USED_UNDERSCORE_BINDING),
LintId::of(mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER),
LintId::of(mut_mut::MUT_MUT),
LintId::of(needless_bitwise_bool::NEEDLESS_BITWISE_BOOL),
LintId::of(needless_continue::NEEDLESS_CONTINUE),
LintId::of(needless_for_each::NEEDLESS_FOR_EACH),
LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE),
LintId::of(no_effect::NO_EFFECT_UNDERSCORE_BINDING),
LintId::of(non_expressive_names::MANY_SINGLE_CHAR_NAMES),
LintId::of(non_expressive_names::SIMILAR_NAMES),
LintId::of(operators::FLOAT_CMP),
LintId::of(operators::NEEDLESS_BITWISE_BOOL),
LintId::of(operators::VERBOSE_BIT_MASK),
LintId::of(pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE),
LintId::of(pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF),
LintId::of(ranges::RANGE_MINUS_ONE),
@ -86,8 +86,6 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
LintId::of(semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED),
LintId::of(stable_sort_primitive::STABLE_SORT_PRIMITIVE),
LintId::of(strings::STRING_ADD_ASSIGN),
LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS),
LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS),
LintId::of(transmute::TRANSMUTE_PTR_TO_PTR),
LintId::of(types::LINKEDLIST),
LintId::of(types::OPTION_OPTION),

View file

@ -13,6 +13,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
LintId::of(loops::MANUAL_MEMCPY),
LintId::of(loops::MISSING_SPIN_LOOP),
LintId::of(loops::NEEDLESS_COLLECT),
LintId::of(manual_retain::MANUAL_RETAIN),
LintId::of(methods::EXPECT_FUN_CALL),
LintId::of(methods::EXTEND_WITH_DRAIN),
LintId::of(methods::ITER_NTH),
@ -21,7 +22,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
LintId::of(methods::OR_FUN_CALL),
LintId::of(methods::SINGLE_CHAR_PATTERN),
LintId::of(methods::UNNECESSARY_TO_OWNED),
LintId::of(misc::CMP_OWNED),
LintId::of(operators::CMP_OWNED),
LintId::of(redundant_clone::REDUNDANT_CLONE),
LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
LintId::of(types::BOX_COLLECTION),

View file

@ -25,7 +25,6 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(implicit_return::IMPLICIT_RETURN),
LintId::of(indexing_slicing::INDEXING_SLICING),
LintId::of(inherent_impl::MULTIPLE_INHERENT_IMPL),
LintId::of(integer_division::INTEGER_DIVISION),
LintId::of(large_include_file::LARGE_INCLUDE_FILE),
LintId::of(let_underscore::LET_UNDERSCORE_MUST_USE),
LintId::of(literal_representation::DECIMAL_LITERAL_REPRESENTATION),
@ -39,7 +38,6 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(methods::FILETYPE_IS_FILE),
LintId::of(methods::GET_UNWRAP),
LintId::of(methods::UNWRAP_USED),
LintId::of(misc::FLOAT_CMP_CONST),
LintId::of(misc_early::SEPARATED_LITERAL_SUFFIX),
LintId::of(misc_early::UNNEEDED_FIELD_PATTERN),
LintId::of(misc_early::UNSEPARATED_LITERAL_SUFFIX),
@ -49,9 +47,11 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION),
LintId::of(module_style::MOD_MODULE_FILES),
LintId::of(module_style::SELF_NAMED_MODULE_FILES),
LintId::of(modulo_arithmetic::MODULO_ARITHMETIC),
LintId::of(numeric_arithmetic::FLOAT_ARITHMETIC),
LintId::of(numeric_arithmetic::INTEGER_ARITHMETIC),
LintId::of(operators::FLOAT_ARITHMETIC),
LintId::of(operators::FLOAT_CMP_CONST),
LintId::of(operators::INTEGER_ARITHMETIC),
LintId::of(operators::INTEGER_DIVISION),
LintId::of(operators::MODULO_ARITHMETIC),
LintId::of(panic_in_result_fn::PANIC_IN_RESULT_FN),
LintId::of(panic_unimplemented::PANIC),
LintId::of(panic_unimplemented::TODO),

View file

@ -4,7 +4,6 @@
store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
LintId::of(assign_ops::ASSIGN_OP_PATTERN),
LintId::of(blacklisted_name::BLACKLISTED_NAME),
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
@ -14,6 +13,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(collapsible_if::COLLAPSIBLE_IF),
LintId::of(comparison_chain::COMPARISON_CHAIN),
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
LintId::of(default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY),
LintId::of(dereference::NEEDLESS_BORROW),
LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
LintId::of(disallowed_methods::DISALLOWED_METHODS),
@ -22,7 +22,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
LintId::of(enum_variants::ENUM_VARIANT_NAMES),
LintId::of(enum_variants::MODULE_INCEPTION),
LintId::of(eq_op::OP_REF),
LintId::of(eta_reduction::REDUNDANT_CLOSURE),
LintId::of(float_literal::EXCESSIVE_PRECISION),
LintId::of(from_over_into::FROM_OVER_INTO),
@ -97,9 +96,11 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
LintId::of(non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
LintId::of(operators::ASSIGN_OP_PATTERN),
LintId::of(operators::OP_REF),
LintId::of(operators::PTR_EQ),
LintId::of(ptr::CMP_NULL),
LintId::of(ptr::PTR_ARG),
LintId::of(ptr_eq::PTR_EQ),
LintId::of(question_mark::QUESTION_MARK),
LintId::of(ranges::MANUAL_RANGE_CONTAINS),
LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES),

View file

@ -4,7 +4,6 @@
store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec![
LintId::of(almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE),
LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP),
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE),
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
@ -16,7 +15,6 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
LintId::of(drop_forget_ref::DROP_NON_DROP),
LintId::of(drop_forget_ref::FORGET_NON_DROP),
LintId::of(duplicate_mod::DUPLICATE_MOD),
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
@ -29,6 +27,8 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
LintId::of(methods::SUSPICIOUS_MAP),
LintId::of(mut_key::MUTABLE_KEY_TYPE),
LintId::of(octal_escapes::OCTAL_ESCAPES),
LintId::of(operators::FLOAT_EQUALITY_WITHOUT_ABS),
LintId::of(operators::MISREFACTORED_ASSIGN_OP),
LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT),
LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),

View file

@ -7,6 +7,7 @@
#![feature(let_chains)]
#![feature(let_else)]
#![feature(lint_reasons)]
#![feature(never_type)]
#![feature(once_cell)]
#![feature(rustc_private)]
#![feature(stmt_expr_attributes)]
@ -52,6 +53,7 @@ extern crate clippy_utils;
use clippy_utils::parse_msrv;
use rustc_data_structures::fx::FxHashSet;
use rustc_lint::LintId;
use rustc_semver::RustcVersion;
use rustc_session::Session;
/// Macro used to declare a Clippy lint.
@ -159,25 +161,22 @@ macro_rules! declare_clippy_lint {
}
#[cfg(feature = "internal")]
mod deprecated_lints;
pub mod deprecated_lints;
#[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))]
mod utils;
mod renamed_lints;
// begin lints modules, do not remove this comment, its used in `update_lints`
mod absurd_extreme_comparisons;
mod almost_complete_letter_range;
mod approx_const;
mod as_conversions;
mod as_underscore;
mod asm_syntax;
mod assertions_on_constants;
mod assign_ops;
mod async_yields_async;
mod attrs;
mod await_holding_invalid;
mod bit_mask;
mod blacklisted_name;
mod blocks_in_if_conditions;
mod bool_assert_comparison;
@ -199,6 +198,7 @@ mod crate_in_macro_def;
mod create_dir;
mod dbg_macro;
mod default;
mod default_instead_of_iter_empty;
mod default_numeric_fallback;
mod default_union_representation;
mod dereference;
@ -209,11 +209,9 @@ mod disallowed_script_idents;
mod disallowed_types;
mod doc;
mod doc_link_with_quotes;
mod double_comparison;
mod double_parens;
mod drop_forget_ref;
mod duplicate_mod;
mod duration_subsec;
mod else_if_without_else;
mod empty_drop;
mod empty_enum;
@ -221,9 +219,7 @@ mod empty_structs_with_brackets;
mod entry;
mod enum_clike;
mod enum_variants;
mod eq_op;
mod equatable_if_let;
mod erasing_op;
mod escape;
mod eta_reduction;
mod excessive_bools;
@ -231,7 +227,6 @@ mod exhaustive_items;
mod exit;
mod explicit_write;
mod fallible_impl_from;
mod float_equality_without_abs;
mod float_literal;
mod floating_point_arithmetic;
mod format;
@ -244,7 +239,6 @@ mod from_str_radix_10;
mod functions;
mod future_not_send;
mod get_first;
mod identity_op;
mod if_let_mutex;
mod if_not_else;
mod if_then_some_else_none;
@ -260,7 +254,6 @@ mod inherent_to_string;
mod init_numbered_fields;
mod inline_fn_without_body;
mod int_plus_one;
mod integer_division;
mod invalid_upcast_comparisons;
mod items_after_statements;
mod iter_not_returning_iterator;
@ -281,6 +274,8 @@ mod manual_async_fn;
mod manual_bits;
mod manual_non_exhaustive;
mod manual_ok_or;
mod manual_rem_euclid;
mod manual_retain;
mod manual_strip;
mod map_clone;
mod map_err_ignore;
@ -300,7 +295,6 @@ mod missing_enforced_import_rename;
mod missing_inline;
mod mixed_read_write_in_expression;
mod module_style;
mod modulo_arithmetic;
mod mut_key;
mod mut_mut;
mod mut_mutex_lock;
@ -308,7 +302,6 @@ mod mut_reference;
mod mutable_debug_assertion;
mod mutex_atomic;
mod needless_arbitrary_self_type;
mod needless_bitwise_bool;
mod needless_bool;
mod needless_borrowed_ref;
mod needless_continue;
@ -327,10 +320,10 @@ mod non_expressive_names;
mod non_octal_unix_permissions;
mod non_send_fields_in_send_ty;
mod nonstandard_macro_braces;
mod numeric_arithmetic;
mod octal_escapes;
mod only_used_in_recursion;
mod open_options;
mod operators;
mod option_env_unwrap;
mod option_if_let_else;
mod overflow_check_conditional;
@ -342,7 +335,6 @@ mod path_buf_push_overwrite;
mod pattern_type_mismatch;
mod precedence;
mod ptr;
mod ptr_eq;
mod ptr_offset_with_cast;
mod pub_use;
mod question_mark;
@ -363,7 +355,6 @@ mod repeat_once;
mod return_self_not_must_use;
mod returns;
mod same_name_method;
mod self_assignment;
mod self_named_constructors;
mod semicolon_if_nothing_returned;
mod serde_api;
@ -448,6 +439,39 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Se
store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv }));
}
fn read_msrv(conf: &Conf, sess: &Session) -> Option<RustcVersion> {
let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION")
.ok()
.and_then(|v| parse_msrv(&v, None, None));
let clippy_msrv = conf.msrv.as_ref().and_then(|s| {
parse_msrv(s, None, None).or_else(|| {
sess.err(&format!(
"error reading Clippy's configuration file. `{}` is not a valid Rust version",
s
));
None
})
});
if let Some(cargo_msrv) = cargo_msrv {
if let Some(clippy_msrv) = clippy_msrv {
// if both files have an msrv, let's compare them and emit a warning if they differ
if clippy_msrv != cargo_msrv {
sess.warn(&format!(
"the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{}` from `clippy.toml`",
clippy_msrv
));
}
Some(clippy_msrv)
} else {
Some(cargo_msrv)
}
} else {
clippy_msrv
}
}
#[doc(hidden)]
pub fn read_conf(sess: &Session) -> Conf {
let file_name = match utils::conf::lookup_conf_file() {
@ -463,12 +487,11 @@ pub fn read_conf(sess: &Session) -> Conf {
let TryConf { conf, errors } = utils::conf::read(&file_name);
// all conf errors are non-fatal, we just use the default conf in case of error
for error in errors {
sess.struct_err(&format!(
sess.err(&format!(
"error reading Clippy's configuration file `{}`: {}",
file_name.display(),
format_error(error)
))
.emit();
));
}
conf
@ -543,21 +566,14 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
))
});
store.register_late_pass(|| Box::new(booleans::NonminimalBool));
store.register_late_pass(|| Box::new(needless_bitwise_bool::NeedlessBitwiseBool));
store.register_late_pass(|| Box::new(eq_op::EqOp));
store.register_late_pass(|| Box::new(enum_clike::UnportableVariant));
store.register_late_pass(|| Box::new(float_literal::FloatLiteral));
let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
store.register_late_pass(move || Box::new(bit_mask::BitMask::new(verbose_bit_mask_threshold)));
store.register_late_pass(|| Box::new(ptr::Ptr));
store.register_late_pass(|| Box::new(ptr_eq::PtrEq));
store.register_late_pass(|| Box::new(needless_bool::NeedlessBool));
store.register_late_pass(|| Box::new(needless_bool::BoolComparison));
store.register_late_pass(|| Box::new(needless_for_each::NeedlessForEach));
store.register_late_pass(|| Box::new(misc::MiscLints));
store.register_late_pass(|| Box::new(eta_reduction::EtaReduction));
store.register_late_pass(|| Box::new(identity_op::IdentityOp));
store.register_late_pass(|| Box::new(erasing_op::ErasingOp));
store.register_late_pass(|| Box::new(mut_mut::MutMut));
store.register_late_pass(|| Box::new(mut_reference::UnnecessaryMutPassed));
store.register_late_pass(|| Box::new(len_zero::LenZero));
@ -575,16 +591,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
let msrv = conf.msrv.as_ref().and_then(|s| {
parse_msrv(s, None, None).or_else(|| {
sess.err(&format!(
"error reading Clippy's configuration file. `{}` is not a valid Rust version",
s
));
None
})
});
let msrv = read_msrv(conf, sess);
let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
let allow_expect_in_tests = conf.allow_expect_in_tests;
let allow_unwrap_in_tests = conf.allow_unwrap_in_tests;
@ -639,7 +646,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(borrow_deref_ref::BorrowDerefRef));
store.register_late_pass(|| Box::new(no_effect::NoEffect));
store.register_late_pass(|| Box::new(temporary_assignment::TemporaryAssignment));
store.register_late_pass(|| Box::new(transmute::Transmute));
store.register_late_pass(move || Box::new(transmute::Transmute::new(msrv)));
let cognitive_complexity_threshold = conf.cognitive_complexity_threshold;
store.register_late_pass(move || {
Box::new(cognitive_complexity::CognitiveComplexity::new(
@ -655,7 +662,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(derivable_impls::DerivableImpls));
store.register_late_pass(|| Box::new(drop_forget_ref::DropForgetRef));
store.register_late_pass(|| Box::new(empty_enum::EmptyEnum));
store.register_late_pass(|| Box::new(absurd_extreme_comparisons::AbsurdExtremeComparisons));
store.register_late_pass(|| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
store.register_late_pass(|| Box::new(regex::Regex));
store.register_late_pass(|| Box::new(copies::CopyAndPaste));
@ -678,8 +684,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || Box::new(doc::DocMarkdown::new(doc_valid_idents.clone())));
store.register_late_pass(|| Box::new(neg_multiply::NegMultiply));
store.register_late_pass(|| Box::new(mem_forget::MemForget));
store.register_late_pass(|| Box::new(numeric_arithmetic::NumericArithmetic::default()));
store.register_late_pass(|| Box::new(assign_ops::AssignOps));
store.register_late_pass(|| Box::new(let_if_seq::LetIfSeq));
store.register_late_pass(|| Box::new(mixed_read_write_in_expression::EvalOrderDependence));
store.register_late_pass(|| Box::new(missing_doc::MissingDoc::new()));
@ -706,7 +710,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(useless_conversion::UselessConversion::default()));
store.register_late_pass(|| Box::new(implicit_hasher::ImplicitHasher));
store.register_late_pass(|| Box::new(fallible_impl_from::FallibleImplFrom));
store.register_late_pass(|| Box::new(double_comparison::DoubleComparisons));
store.register_late_pass(|| Box::new(question_mark::QuestionMark));
store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings));
store.register_late_pass(|| Box::new(suspicious_trait_impl::SuspiciousImpl));
@ -714,7 +717,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(inherent_impl::MultipleInherentImpl));
store.register_late_pass(|| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd));
store.register_late_pass(|| Box::new(unwrap::Unwrap));
store.register_late_pass(|| Box::new(duration_subsec::DurationSubsec));
store.register_late_pass(|| Box::new(indexing_slicing::IndexingSlicing));
store.register_late_pass(|| Box::new(non_copy_const::NonCopyConst));
store.register_late_pass(|| Box::new(ptr_offset_with_cast::PtrOffsetWithCast));
@ -725,13 +727,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(assertions_on_constants::AssertionsOnConstants));
store.register_late_pass(|| Box::new(transmuting_null::TransmutingNull));
store.register_late_pass(|| Box::new(path_buf_push_overwrite::PathBufPushOverwrite));
store.register_late_pass(|| Box::new(integer_division::IntegerDivision));
store.register_late_pass(|| Box::new(inherent_to_string::InherentToString));
let max_trait_bounds = conf.max_trait_bounds;
store.register_late_pass(move || Box::new(trait_bounds::TraitBounds::new(max_trait_bounds)));
store.register_late_pass(|| Box::new(comparison_chain::ComparisonChain));
store.register_late_pass(|| Box::new(mut_key::MutableKeyType));
store.register_late_pass(|| Box::new(modulo_arithmetic::ModuloArithmetic));
store.register_early_pass(|| Box::new(reference::DerefAddrOf));
store.register_early_pass(|| Box::new(double_parens::DoubleParens));
store.register_late_pass(|| Box::new(format_impl::FormatImpl::new()));
@ -828,9 +828,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(stable_sort_primitive::StableSortPrimitive));
store.register_late_pass(|| Box::new(repeat_once::RepeatOnce));
store.register_late_pass(|| Box::new(unwrap_in_result::UnwrapInResult));
store.register_late_pass(|| Box::new(self_assignment::SelfAssignment));
store.register_late_pass(|| Box::new(manual_ok_or::ManualOkOr));
store.register_late_pass(|| Box::new(float_equality_without_abs::FloatEqualityWithoutAbs));
store.register_late_pass(|| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
store.register_late_pass(|| Box::new(async_yields_async::AsyncYieldsAsync));
let disallowed_methods = conf.disallowed_methods.clone();
@ -910,6 +908,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(mismatching_type_param_order::TypeParamMismatch));
store.register_late_pass(|| Box::new(as_underscore::AsUnderscore));
store.register_late_pass(|| Box::new(read_zero_byte_vec::ReadZeroByteVec));
store.register_late_pass(|| Box::new(default_instead_of_iter_empty::DefaultIterEmpty));
store.register_late_pass(move || Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv)));
store.register_late_pass(move || Box::new(manual_retain::ManualRetain::new(msrv)));
let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
store.register_late_pass(move || Box::new(operators::Operators::new(verbose_bit_mask_threshold)));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -92,7 +92,9 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
if let ItemKind::Fn(ref sig, generics, id) = item.kind {
check_fn_inner(cx, sig.decl, Some(id), None, generics, item.span, true);
} else if let ItemKind::Impl(impl_) = item.kind {
report_extra_impl_lifetimes(cx, impl_);
if !item.span.from_expansion() {
report_extra_impl_lifetimes(cx, impl_);
}
}
}

View file

@ -0,0 +1,158 @@
use super::utils::make_iterator_snippet;
use super::MANUAL_FIND;
use clippy_utils::{
diagnostics::span_lint_and_then, higher, is_lang_ctor, path_res, peel_blocks_with_stmt,
source::snippet_with_applicability, ty::implements_trait,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{
def::Res, lang_items::LangItem, BindingAnnotation, Block, Expr, ExprKind, HirId, Node, Pat, PatKind, Stmt, StmtKind,
};
use rustc_lint::LateContext;
use rustc_span::source_map::Span;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
pat: &'tcx Pat<'_>,
arg: &'tcx Expr<'_>,
body: &'tcx Expr<'_>,
span: Span,
expr: &'tcx Expr<'_>,
) {
let inner_expr = peel_blocks_with_stmt(body);
// Check for the specific case that the result is returned and optimize suggestion for that (more
// cases can be added later)
if_chain! {
if let Some(higher::If { cond, then, r#else: None, }) = higher::If::hir(inner_expr);
if let Some(binding_id) = get_binding(pat);
if let ExprKind::Block(block, _) = then.kind;
if let [stmt] = block.stmts;
if let StmtKind::Semi(semi) = stmt.kind;
if let ExprKind::Ret(Some(ret_value)) = semi.kind;
if let ExprKind::Call(Expr { kind: ExprKind::Path(ctor), .. }, [inner_ret]) = ret_value.kind;
if is_lang_ctor(cx, ctor, LangItem::OptionSome);
if path_res(cx, inner_ret) == Res::Local(binding_id);
if let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr);
then {
let mut applicability = Applicability::MachineApplicable;
let mut snippet = make_iterator_snippet(cx, arg, &mut applicability);
// Checks if `pat` is a single reference to a binding (`&x`)
let is_ref_to_binding =
matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..)));
// If `pat` is not a binding or a reference to a binding (`x` or `&x`)
// we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`)
if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) {
snippet.push_str(
&format!(
".map(|{}| {})",
snippet_with_applicability(cx, pat.span, "..", &mut applicability),
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
)[..],
);
}
let ty = cx.typeck_results().expr_ty(inner_ret);
if cx.tcx.lang_items().copy_trait().map_or(false, |id| implements_trait(cx, ty, id, &[])) {
snippet.push_str(
&format!(
".find(|{}{}| {})",
"&".repeat(1 + usize::from(is_ref_to_binding)),
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
)[..],
);
if is_ref_to_binding {
snippet.push_str(".copied()");
}
} else {
applicability = Applicability::MaybeIncorrect;
snippet.push_str(
&format!(
".find(|{}| {})",
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
)[..],
);
}
// Extends to `last_stmt` to include semicolon in case of `return None;`
let lint_span = span.to(last_stmt.span).to(last_ret.span);
span_lint_and_then(
cx,
MANUAL_FIND,
lint_span,
"manual implementation of `Iterator::find`",
|diag| {
if applicability == Applicability::MaybeIncorrect {
diag.note("you may need to dereference some variables");
}
diag.span_suggestion(
lint_span,
"replace with an iterator",
snippet,
applicability,
);
},
);
}
}
}
fn get_binding(pat: &Pat<'_>) -> Option<HirId> {
let mut hir_id = None;
let mut count = 0;
pat.each_binding(|annotation, id, _, _| {
count += 1;
if count > 1 {
hir_id = None;
return;
}
if let BindingAnnotation::Unannotated = annotation {
hir_id = Some(id);
}
});
hir_id
}
// Returns the last statement and last return if function fits format for lint
fn last_stmt_and_ret<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
) -> Option<(&'tcx Stmt<'tcx>, &'tcx Expr<'tcx>)> {
// Returns last non-return statement and the last return
fn extract<'tcx>(block: &Block<'tcx>) -> Option<(&'tcx Stmt<'tcx>, &'tcx Expr<'tcx>)> {
if let [.., last_stmt] = block.stmts {
if let Some(ret) = block.expr {
return Some((last_stmt, ret));
}
if_chain! {
if let [.., snd_last, _] = block.stmts;
if let StmtKind::Semi(last_expr) = last_stmt.kind;
if let ExprKind::Ret(Some(ret)) = last_expr.kind;
then {
return Some((snd_last, ret));
}
}
}
None
}
let mut parent_iter = cx.tcx.hir().parent_iter(expr.hir_id);
if_chain! {
// This should be the loop
if let Some((node_hir, Node::Stmt(..))) = parent_iter.next();
// This should be the funciton body
if let Some((_, Node::Block(block))) = parent_iter.next();
if let Some((last_stmt, last_ret)) = extract(block);
if last_stmt.hir_id == node_hir;
if let ExprKind::Path(path) = &last_ret.kind;
if is_lang_ctor(cx, path, LangItem::OptionNone);
if let Some((_, Node::Expr(_block))) = parent_iter.next();
// This includes the function header
if let Some((_, func)) = parent_iter.next();
if func.fn_kind().is_some();
then {
Some((block.stmts.last().unwrap(), last_ret))
} else {
None
}
}
}

View file

@ -5,6 +5,7 @@ mod explicit_iter_loop;
mod for_kv_map;
mod for_loops_over_fallibles;
mod iter_next_loop;
mod manual_find;
mod manual_flatten;
mod manual_memcpy;
mod missing_spin_loop;
@ -346,7 +347,14 @@ declare_clippy_lint! {
///
/// ### Example
/// ```ignore
/// while let Some(val) = iter() {
/// while let Some(val) = iter.next() {
/// ..
/// }
/// ```
///
/// Use instead:
/// ```ignore
/// for val in &mut iter {
/// ..
/// }
/// ```
@ -602,6 +610,37 @@ declare_clippy_lint! {
"An empty busy waiting loop"
}
declare_clippy_lint! {
/// ### What it does
/// Check for manual implementations of Iterator::find
///
/// ### Why is this bad?
/// It doesn't affect performance, but using `find` is shorter and easier to read.
///
/// ### Example
///
/// ```rust
/// fn example(arr: Vec<i32>) -> Option<i32> {
/// for el in arr {
/// if el == 1 {
/// return Some(el);
/// }
/// }
/// None
/// }
/// ```
/// Use instead:
/// ```rust
/// fn example(arr: Vec<i32>) -> Option<i32> {
/// arr.into_iter().find(|&el| el == 1)
/// }
/// ```
#[clippy::version = "1.61.0"]
pub MANUAL_FIND,
complexity,
"manual implementation of `Iterator::find`"
}
declare_lint_pass!(Loops => [
MANUAL_MEMCPY,
MANUAL_FLATTEN,
@ -622,6 +661,7 @@ declare_lint_pass!(Loops => [
SAME_ITEM_PUSH,
SINGLE_ELEMENT_LOOP,
MISSING_SPIN_LOOP,
MANUAL_FIND,
]);
impl<'tcx> LateLintPass<'tcx> for Loops {
@ -696,6 +736,7 @@ fn check_for_loop<'tcx>(
single_element_loop::check(cx, pat, arg, body, expr);
same_item_push::check(cx, pat, arg, body, expr);
manual_flatten::check(cx, pat, arg, body, span);
manual_find::check(cx, pat, arg, body, span, expr);
}
fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) {

View file

@ -2,71 +2,60 @@ use super::WHILE_LET_LOOP;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::needs_ordered_drop;
use clippy_utils::visitors::any_temporaries_need_ordered_drop;
use rustc_errors::Applicability;
use rustc_hir::{Block, Expr, ExprKind, MatchSource, Pat, StmtKind};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_hir::{Block, Expr, ExprKind, Local, MatchSource, Pat, StmtKind};
use rustc_lint::LateContext;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) {
// extract the expression from the first statement (if any) in a block
let inner_stmt_expr = extract_expr_from_first_stmt(loop_block);
// or extract the first expression (if any) from the block
if let Some(inner) = inner_stmt_expr.or_else(|| extract_first_expr(loop_block)) {
if let Some(higher::IfLet {
let_pat,
let_expr,
if_else: Some(if_else),
..
}) = higher::IfLet::hir(cx, inner)
{
if is_simple_break_expr(if_else) {
could_be_while_let(cx, expr, let_pat, let_expr);
let (init, has_trailing_exprs) = match (loop_block.stmts, loop_block.expr) {
([stmt, stmts @ ..], expr) => {
if let StmtKind::Local(&Local { init: Some(e), .. }) | StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind {
(e, !stmts.is_empty() || expr.is_some())
} else {
return;
}
}
if let ExprKind::Match(matchexpr, arms, MatchSource::Normal) = inner.kind {
if arms.len() == 2
&& arms[0].guard.is_none()
&& arms[1].guard.is_none()
&& is_simple_break_expr(arms[1].body)
{
could_be_while_let(cx, expr, arms[0].pat, matchexpr);
}
}
}
}
/// If a block begins with a statement (possibly a `let` binding) and has an
/// expression, return it.
fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
if let Some(first_stmt) = block.stmts.get(0) {
if let StmtKind::Local(local) = first_stmt.kind {
return local.init;
}
}
None
}
/// If a block begins with an expression (with or without semicolon), return it.
fn extract_first_expr<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
match block.expr {
Some(expr) if block.stmts.is_empty() => Some(expr),
None if !block.stmts.is_empty() => match block.stmts[0].kind {
StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some(expr),
StmtKind::Local(..) | StmtKind::Item(..) => None,
},
_ => None,
([], Some(e)) => (e, false),
_ => return,
};
if let Some(if_let) = higher::IfLet::hir(cx, init)
&& let Some(else_expr) = if_let.if_else
&& is_simple_break_expr(else_expr)
{
could_be_while_let(cx, expr, if_let.let_pat, if_let.let_expr, has_trailing_exprs);
} else if let ExprKind::Match(scrutinee, [arm1, arm2], MatchSource::Normal) = init.kind
&& arm1.guard.is_none()
&& arm2.guard.is_none()
&& is_simple_break_expr(arm2.body)
{
could_be_while_let(cx, expr, arm1.pat, scrutinee, has_trailing_exprs);
}
}
/// Returns `true` if expr contains a single break expr without destination label
/// and
/// passed expression. The expression may be within a block.
fn is_simple_break_expr(expr: &Expr<'_>) -> bool {
match expr.kind {
ExprKind::Break(dest, ref passed_expr) if dest.label.is_none() && passed_expr.is_none() => true,
ExprKind::Block(b, _) => extract_first_expr(b).map_or(false, is_simple_break_expr),
_ => false,
/// Returns `true` if expr contains a single break expression without a label or eub-expression.
fn is_simple_break_expr(e: &Expr<'_>) -> bool {
matches!(peel_blocks(e).kind, ExprKind::Break(dest, None) if dest.label.is_none())
}
/// Removes any blocks containing only a single expression.
fn peel_blocks<'tcx>(e: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
if let ExprKind::Block(b, _) = e.kind {
match (b.stmts, b.expr) {
([s], None) => {
if let StmtKind::Expr(e) | StmtKind::Semi(e) = s.kind {
peel_blocks(e)
} else {
e
}
},
([], Some(e)) => peel_blocks(e),
_ => e,
}
} else {
e
}
}
@ -75,8 +64,13 @@ fn could_be_while_let<'tcx>(
expr: &'tcx Expr<'_>,
let_pat: &'tcx Pat<'_>,
let_expr: &'tcx Expr<'_>,
has_trailing_exprs: bool,
) {
if in_external_macro(cx.sess(), expr.span) {
if has_trailing_exprs
&& (needs_ordered_drop(cx, cx.typeck_results().expr_ty(let_expr))
|| any_temporaries_need_ordered_drop(cx, let_expr))
{
// Switching to a `while let` loop will extend the lifetime of some values.
return;
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet;
use hir::def::{DefKind, Res};
use if_chain::if_chain;
@ -35,7 +35,8 @@ struct PathAndSpan {
span: Span,
}
/// `MacroRefData` includes the name of the macro.
/// `MacroRefData` includes the name of the macro
/// and the path from `SourceMap::span_to_filename`.
#[derive(Debug, Clone)]
pub struct MacroRefData {
name: String,
@ -50,8 +51,9 @@ impl MacroRefData {
#[derive(Default)]
#[expect(clippy::module_name_repetitions)]
pub struct MacroUseImports {
/// the actual import path used and the span of the attribute above it.
imports: Vec<(String, Span)>,
/// the actual import path used and the span of the attribute above it. The value is
/// the location, where the lint should be emitted.
imports: Vec<(String, Span, hir::HirId)>,
/// the span of the macro reference, kept to ensure only one reference is used per macro call.
collected: FxHashSet<Span>,
mac_refs: Vec<MacroRefData>,
@ -90,7 +92,8 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
if_chain! {
if cx.sess().opts.edition >= Edition::Edition2018;
if let hir::ItemKind::Use(path, _kind) = &item.kind;
let attrs = cx.tcx.hir().attrs(item.hir_id());
let hir_id = item.hir_id();
let attrs = cx.tcx.hir().attrs(hir_id);
if let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use));
if let Res::Def(DefKind::Mod, id) = path.res;
if !id.is_local();
@ -99,7 +102,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
let span = mac_attr.span;
let def_path = cx.tcx.def_path_str(mac_id);
self.imports.push((def_path, span));
self.imports.push((def_path, span, hir_id));
}
}
} else {
@ -137,7 +140,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
fn check_crate_post(&mut self, cx: &LateContext<'_>) {
let mut used = FxHashMap::default();
let mut check_dup = vec![];
for (import, span) in &self.imports {
for (import, span, hir_id) in &self.imports {
let found_idx = self.mac_refs.iter().position(|mac| import.ends_with(&mac.name));
if let Some(idx) = found_idx {
@ -150,7 +153,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
[] | [_] => return,
[root, item] => {
if !check_dup.contains(&(*item).to_string()) {
used.entry(((*root).to_string(), span))
used.entry(((*root).to_string(), span, hir_id))
.or_insert_with(Vec::new)
.push((*item).to_string());
check_dup.push((*item).to_string());
@ -168,13 +171,13 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
}
})
.collect::<Vec<_>>();
used.entry(((*root).to_string(), span))
used.entry(((*root).to_string(), span, hir_id))
.or_insert_with(Vec::new)
.push(filtered.join("::"));
check_dup.extend(filtered);
} else {
let rest = rest.to_vec();
used.entry(((*root).to_string(), span))
used.entry(((*root).to_string(), span, hir_id))
.or_insert_with(Vec::new)
.push(rest.join("::"));
check_dup.extend(rest.iter().map(ToString::to_string));
@ -185,27 +188,33 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
}
let mut suggestions = vec![];
for ((root, span), path) in used {
for ((root, span, hir_id), path) in used {
if path.len() == 1 {
suggestions.push((span, format!("{}::{}", root, path[0])));
suggestions.push((span, format!("{}::{}", root, path[0]), hir_id));
} else {
suggestions.push((span, format!("{}::{{{}}}", root, path.join(", "))));
suggestions.push((span, format!("{}::{{{}}}", root, path.join(", ")), hir_id));
}
}
// If mac_refs is not empty we have encountered an import we could not handle
// such as `std::prelude::v1::foo` or some other macro that expands to an import.
if self.mac_refs.is_empty() {
for (span, import) in suggestions {
for (span, import, hir_id) in suggestions {
let help = format!("use {};", import);
span_lint_and_sugg(
span_lint_hir_and_then(
cx,
MACRO_USE_IMPORTS,
*hir_id,
*span,
"`macro_use` attributes are no longer needed in the Rust 2018 edition",
"remove the attribute and import the macro directly, try",
help,
Applicability::MaybeIncorrect,
|diag| {
diag.span_suggestion(
*span,
"remove the attribute and import the macro directly, try",
help,
Applicability::MaybeIncorrect,
);
},
);
}
}

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::source::snippet_opt;
use clippy_utils::{is_doc_hidden, is_lint_allowed, meets_msrv, msrvs};
use clippy_utils::{is_doc_hidden, meets_msrv, msrvs};
use rustc_ast::ast::{self, VisibilityKind};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
@ -190,12 +190,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
!self
.constructed_enum_variants
.contains(&(enum_id.to_def_id(), variant_id.to_def_id()))
&& !is_lint_allowed(cx, MANUAL_NON_EXHAUSTIVE, cx.tcx.hir().local_def_id_to_hir_id(enum_id))
})
{
span_lint_and_then(
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(enum_id);
span_lint_hir_and_then(
cx,
MANUAL_NON_EXHAUSTIVE,
hir_id,
enum_span,
"this seems like a manual implementation of the non-exhaustive pattern",
|diag| {

View file

@ -0,0 +1,123 @@
use clippy_utils::consts::{constant_full_int, FullInt};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{in_constant, meets_msrv, msrvs, path_to_local};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
declare_clippy_lint! {
/// ### What it does
/// Checks for an expression like `((x % 4) + 4) % 4` which is a common manual reimplementation
/// of `x.rem_euclid(4)`.
///
/// ### Why is this bad?
/// It's simpler and more readable.
///
/// ### Example
/// ```rust
/// let x: i32 = 24;
/// let rem = ((x % 4) + 4) % 4;
/// ```
/// Use instead:
/// ```rust
/// let x: i32 = 24;
/// let rem = x.rem_euclid(4);
/// ```
#[clippy::version = "1.63.0"]
pub MANUAL_REM_EUCLID,
complexity,
"manually reimplementing `rem_euclid`"
}
pub struct ManualRemEuclid {
msrv: Option<RustcVersion>,
}
impl ManualRemEuclid {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
}
}
impl_lint_pass!(ManualRemEuclid => [MANUAL_REM_EUCLID]);
impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !meets_msrv(self.msrv, msrvs::REM_EUCLID) {
return;
}
if in_constant(cx, expr.hir_id) && !meets_msrv(self.msrv, msrvs::REM_EUCLID_CONST) {
return;
}
if in_external_macro(cx.sess(), expr.span) {
return;
}
if let ExprKind::Binary(op1, expr1, right) = expr.kind
&& op1.node == BinOpKind::Rem
&& let Some(const1) = check_for_unsigned_int_constant(cx, right)
&& let ExprKind::Binary(op2, left, right) = expr1.kind
&& op2.node == BinOpKind::Add
&& let Some((const2, expr2)) = check_for_either_unsigned_int_constant(cx, left, right)
&& let ExprKind::Binary(op3, expr3, right) = expr2.kind
&& op3.node == BinOpKind::Rem
&& let Some(const3) = check_for_unsigned_int_constant(cx, right)
// Also ensures the const is nonzero since zero can't be a divisor
&& const1 == const2 && const2 == const3
&& let Some(hir_id) = path_to_local(expr3)
&& let Some(Node::Binding(_)) = cx.tcx.hir().find(hir_id) {
// Apply only to params or locals with annotated types
match cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
Some(Node::Param(..)) => (),
Some(Node::Local(local)) => {
let Some(ty) = local.ty else { return };
if matches!(ty.kind, TyKind::Infer) {
return;
}
}
_ => return,
};
let mut app = Applicability::MachineApplicable;
let rem_of = snippet_with_applicability(cx, expr3.span, "_", &mut app);
span_lint_and_sugg(
cx,
MANUAL_REM_EUCLID,
expr.span,
"manual `rem_euclid` implementation",
"consider using",
format!("{rem_of}.rem_euclid({const1})"),
app,
);
}
}
extract_msrv_attr!(LateContext);
}
// Checks if either the left or right expressions can be an unsigned int constant and returns that
// constant along with the other expression unchanged if so
fn check_for_either_unsigned_int_constant<'a>(
cx: &'a LateContext<'_>,
left: &'a Expr<'_>,
right: &'a Expr<'_>,
) -> Option<(u128, &'a Expr<'a>)> {
check_for_unsigned_int_constant(cx, left)
.map(|int_const| (int_const, right))
.or_else(|| check_for_unsigned_int_constant(cx, right).map(|int_const| (int_const, left)))
}
fn check_for_unsigned_int_constant<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option<u128> {
let Some(int_const) = constant_full_int(cx, cx.typeck_results(), expr) else { return None };
match int_const {
FullInt::S(s) => s.try_into().ok(),
FullInt::U(u) => Some(u),
}
}

View file

@ -0,0 +1,228 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{get_parent_expr, match_def_path, paths, SpanlessEq};
use clippy_utils::{meets_msrv, msrvs};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_hir::ExprKind::Assign;
use rustc_lint::{LateContext, LateLintPass};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::sym;
const ACCEPTABLE_METHODS: [&[&str]; 4] = [
&paths::HASHSET_ITER,
&paths::BTREESET_ITER,
&paths::SLICE_INTO,
&paths::VEC_DEQUE_ITER,
];
const ACCEPTABLE_TYPES: [(rustc_span::Symbol, Option<RustcVersion>); 6] = [
(sym::BTreeSet, Some(msrvs::BTREE_SET_RETAIN)),
(sym::BTreeMap, Some(msrvs::BTREE_MAP_RETAIN)),
(sym::HashSet, Some(msrvs::HASH_SET_RETAIN)),
(sym::HashMap, Some(msrvs::HASH_MAP_RETAIN)),
(sym::Vec, None),
(sym::VecDeque, None),
];
declare_clippy_lint! {
/// ### What it does
/// Checks for code to be replaced by `.retain()`.
/// ### Why is this bad?
/// `.retain()` is simpler and avoids needless allocation.
/// ### Example
/// ```rust
/// let mut vec = vec![0, 1, 2];
/// vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect();
/// vec = vec.into_iter().filter(|x| x % 2 == 0).collect();
/// ```
/// Use instead:
/// ```rust
/// let mut vec = vec![0, 1, 2];
/// vec.retain(|x| x % 2 == 0);
/// ```
#[clippy::version = "1.63.0"]
pub MANUAL_RETAIN,
perf,
"`retain()` is simpler and the same functionalitys"
}
pub struct ManualRetain {
msrv: Option<RustcVersion>,
}
impl ManualRetain {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
}
}
impl_lint_pass!(ManualRetain => [MANUAL_RETAIN]);
impl<'tcx> LateLintPass<'tcx> for ManualRetain {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if let Some(parent_expr) = get_parent_expr(cx, expr)
&& let Assign(left_expr, collect_expr, _) = &parent_expr.kind
&& let hir::ExprKind::MethodCall(seg, _, _) = &collect_expr.kind
&& seg.args.is_none()
&& let hir::ExprKind::MethodCall(_, [target_expr], _) = &collect_expr.kind
&& let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id)
&& match_def_path(cx, collect_def_id, &paths::CORE_ITER_COLLECT) {
check_into_iter(cx, parent_expr, left_expr, target_expr, self.msrv);
check_iter(cx, parent_expr, left_expr, target_expr, self.msrv);
check_to_owned(cx, parent_expr, left_expr, target_expr, self.msrv);
}
}
extract_msrv_attr!(LateContext);
}
fn check_into_iter(
cx: &LateContext<'_>,
parent_expr: &hir::Expr<'_>,
left_expr: &hir::Expr<'_>,
target_expr: &hir::Expr<'_>,
msrv: Option<RustcVersion>,
) {
if let hir::ExprKind::MethodCall(_, [into_iter_expr, _], _) = &target_expr.kind
&& let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id)
&& match_def_path(cx, filter_def_id, &paths::CORE_ITER_FILTER)
&& let hir::ExprKind::MethodCall(_, [struct_expr], _) = &into_iter_expr.kind
&& let Some(into_iter_def_id) = cx.typeck_results().type_dependent_def_id(into_iter_expr.hir_id)
&& match_def_path(cx, into_iter_def_id, &paths::CORE_ITER_INTO_ITER)
&& match_acceptable_type(cx, left_expr, msrv)
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr) {
suggest(cx, parent_expr, left_expr, target_expr);
}
}
fn check_iter(
cx: &LateContext<'_>,
parent_expr: &hir::Expr<'_>,
left_expr: &hir::Expr<'_>,
target_expr: &hir::Expr<'_>,
msrv: Option<RustcVersion>,
) {
if let hir::ExprKind::MethodCall(_, [filter_expr], _) = &target_expr.kind
&& let Some(copied_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id)
&& (match_def_path(cx, copied_def_id, &paths::CORE_ITER_COPIED)
|| match_def_path(cx, copied_def_id, &paths::CORE_ITER_CLONED))
&& let hir::ExprKind::MethodCall(_, [iter_expr, _], _) = &filter_expr.kind
&& let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(filter_expr.hir_id)
&& match_def_path(cx, filter_def_id, &paths::CORE_ITER_FILTER)
&& let hir::ExprKind::MethodCall(_, [struct_expr], _) = &iter_expr.kind
&& let Some(iter_expr_def_id) = cx.typeck_results().type_dependent_def_id(iter_expr.hir_id)
&& match_acceptable_def_path(cx, iter_expr_def_id)
&& match_acceptable_type(cx, left_expr, msrv)
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr) {
suggest(cx, parent_expr, left_expr, filter_expr);
}
}
fn check_to_owned(
cx: &LateContext<'_>,
parent_expr: &hir::Expr<'_>,
left_expr: &hir::Expr<'_>,
target_expr: &hir::Expr<'_>,
msrv: Option<RustcVersion>,
) {
if meets_msrv(msrv, msrvs::STRING_RETAIN)
&& let hir::ExprKind::MethodCall(_, [filter_expr], _) = &target_expr.kind
&& let Some(to_owned_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id)
&& match_def_path(cx, to_owned_def_id, &paths::TO_OWNED_METHOD)
&& let hir::ExprKind::MethodCall(_, [chars_expr, _], _) = &filter_expr.kind
&& let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(filter_expr.hir_id)
&& match_def_path(cx, filter_def_id, &paths::CORE_ITER_FILTER)
&& let hir::ExprKind::MethodCall(_, [str_expr], _) = &chars_expr.kind
&& let Some(chars_expr_def_id) = cx.typeck_results().type_dependent_def_id(chars_expr.hir_id)
&& match_def_path(cx, chars_expr_def_id, &paths::STR_CHARS)
&& let ty = cx.typeck_results().expr_ty(str_expr).peel_refs()
&& is_type_diagnostic_item(cx, ty, sym::String)
&& SpanlessEq::new(cx).eq_expr(left_expr, str_expr) {
suggest(cx, parent_expr, left_expr, filter_expr);
}
}
fn suggest(cx: &LateContext<'_>, parent_expr: &hir::Expr<'_>, left_expr: &hir::Expr<'_>, filter_expr: &hir::Expr<'_>) {
if let hir::ExprKind::MethodCall(_, [_, closure], _) = filter_expr.kind
&& let hir::ExprKind::Closure{ body, ..} = closure.kind
&& let filter_body = cx.tcx.hir().body(body)
&& let [filter_params] = filter_body.params
&& let Some(sugg) = match filter_params.pat.kind {
hir::PatKind::Binding(_, _, filter_param_ident, None) => {
Some(format!("{}.retain(|{}| {})", snippet(cx, left_expr.span, ".."), filter_param_ident, snippet(cx, filter_body.value.span, "..")))
},
hir::PatKind::Tuple([key_pat, value_pat], _) => {
make_sugg(cx, key_pat, value_pat, left_expr, filter_body)
},
hir::PatKind::Ref(pat, _) => {
match pat.kind {
hir::PatKind::Binding(_, _, filter_param_ident, None) => {
Some(format!("{}.retain(|{}| {})", snippet(cx, left_expr.span, ".."), filter_param_ident, snippet(cx, filter_body.value.span, "..")))
},
_ => None
}
},
_ => None
} {
span_lint_and_sugg(
cx,
MANUAL_RETAIN,
parent_expr.span,
"this expression can be written more simply using `.retain()`",
"consider calling `.retain()` instead",
sugg,
Applicability::MachineApplicable
);
}
}
fn make_sugg(
cx: &LateContext<'_>,
key_pat: &rustc_hir::Pat<'_>,
value_pat: &rustc_hir::Pat<'_>,
left_expr: &hir::Expr<'_>,
filter_body: &hir::Body<'_>,
) -> Option<String> {
match (&key_pat.kind, &value_pat.kind) {
(hir::PatKind::Binding(_, _, key_param_ident, None), hir::PatKind::Binding(_, _, value_param_ident, None)) => {
Some(format!(
"{}.retain(|{}, &mut {}| {})",
snippet(cx, left_expr.span, ".."),
key_param_ident,
value_param_ident,
snippet(cx, filter_body.value.span, "..")
))
},
(hir::PatKind::Binding(_, _, key_param_ident, None), hir::PatKind::Wild) => Some(format!(
"{}.retain(|{}, _| {})",
snippet(cx, left_expr.span, ".."),
key_param_ident,
snippet(cx, filter_body.value.span, "..")
)),
(hir::PatKind::Wild, hir::PatKind::Binding(_, _, value_param_ident, None)) => Some(format!(
"{}.retain(|_, &mut {}| {})",
snippet(cx, left_expr.span, ".."),
value_param_ident,
snippet(cx, filter_body.value.span, "..")
)),
_ => None,
}
}
fn match_acceptable_def_path(cx: &LateContext<'_>, collect_def_id: DefId) -> bool {
ACCEPTABLE_METHODS
.iter()
.any(|&method| match_def_path(cx, collect_def_id, method))
}
fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: Option<RustcVersion>) -> bool {
let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs();
ACCEPTABLE_TYPES.iter().any(|(ty, acceptable_msrv)| {
is_type_diagnostic_item(cx, expr_ty, *ty)
&& acceptable_msrv.map_or(true, |acceptable_msrv| meets_msrv(msrv, acceptable_msrv))
})
}

View file

@ -285,7 +285,7 @@ impl<'a> NormalizedPat<'a> {
// TODO: Handle negative integers. They're currently treated as a wild match.
ExprKind::Lit(lit) => match lit.node {
LitKind::Str(sym, _) => Self::LitStr(sym),
LitKind::ByteStr(ref bytes) => Self::LitBytes(&**bytes),
LitKind::ByteStr(ref bytes) => Self::LitBytes(bytes),
LitKind::Byte(val) => Self::LitInt(val.into()),
LitKind::Char(val) => Self::LitInt(val.into()),
LitKind::Int(val, _) => Self::LitInt(val),

View file

@ -55,7 +55,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
cx,
(ex, expr),
(bind_names, matched_vars),
&*snippet_body,
&snippet_body,
&mut applicability,
Some(span),
);
@ -88,7 +88,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
cx,
(ex, expr),
(bind_names, matched_vars),
&*snippet_body,
&snippet_body,
&mut applicability,
None,
);

View file

@ -118,7 +118,7 @@ fn lint(cx: &LateContext<'_>, case_method: &CaseMethod, bad_case_span: Span, bad
MATCH_STR_CASE_MISMATCH,
bad_case_span,
"this `match` arm has a differing case than its expression",
&*format!("consider changing the case of this arm to respect `{}`", method_str),
&format!("consider changing the case of this arm to respect `{}`", method_str),
format!("\"{}\"", suggestion),
Applicability::MachineApplicable,
);

View file

@ -791,7 +791,7 @@ declare_clippy_lint! {
/// the match block and thus will not unlock.
///
/// ### Example
/// ```rust.ignore
/// ```rust,ignore
/// # use std::sync::Mutex;
///
/// # struct State {}
@ -963,7 +963,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
return;
}
if matches!(source, MatchSource::Normal | MatchSource::ForLoopDesugar) {
significant_drop_in_scrutinee::check(cx, expr, ex, source);
significant_drop_in_scrutinee::check(cx, expr, ex, arms, source);
}
collapsible_match::check_match(cx, arms);

View file

@ -3,16 +3,13 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::needs_ordered_drop;
use clippy_utils::{higher, match_def_path};
use clippy_utils::{is_lang_ctor, is_trait_method, paths};
use clippy_utils::visitors::any_temporaries_need_ordered_drop;
use clippy_utils::{higher, is_lang_ctor, is_trait_method, match_def_path, paths};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, PollPending};
use rustc_hir::{
intravisit::{walk_expr, Visitor},
Arm, Block, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp,
};
use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty};
use rustc_span::sym;
@ -47,79 +44,6 @@ fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
}
}
// Checks if there are any temporaries created in the given expression for which drop order
// matters.
fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
struct V<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
res: bool,
}
impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
match expr.kind {
// Taking the reference of a value leaves a temporary
// e.g. In `&String::new()` the string is a temporary value.
// Remaining fields are temporary values
// e.g. In `(String::new(), 0).1` the string is a temporary value.
ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => {
if !matches!(expr.kind, ExprKind::Path(_)) {
if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) {
self.res = true;
} else {
self.visit_expr(expr);
}
}
},
// the base type is always taken by reference.
// e.g. In `(vec![0])[0]` the vector is a temporary value.
ExprKind::Index(base, index) => {
if !matches!(base.kind, ExprKind::Path(_)) {
if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) {
self.res = true;
} else {
self.visit_expr(base);
}
}
self.visit_expr(index);
},
// Method calls can take self by reference.
// e.g. In `String::new().len()` the string is a temporary value.
ExprKind::MethodCall(_, [self_arg, args @ ..], _) => {
if !matches!(self_arg.kind, ExprKind::Path(_)) {
let self_by_ref = self
.cx
.typeck_results()
.type_dependent_def_id(expr.hir_id)
.map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
if self_by_ref && needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg)) {
self.res = true;
} else {
self.visit_expr(self_arg);
}
}
args.iter().for_each(|arg| self.visit_expr(arg));
},
// Either explicitly drops values, or changes control flow.
ExprKind::DropTemps(_)
| ExprKind::Ret(_)
| ExprKind::Break(..)
| ExprKind::Yield(..)
| ExprKind::Block(Block { expr: None, .. }, _)
| ExprKind::Loop(..) => (),
// Only consider the final expression.
ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr),
_ => walk_expr(self, expr),
}
}
}
let mut v = V { cx, res: false };
v.visit_expr(expr);
v.res
}
fn find_sugg_for_if_let<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
@ -191,7 +115,7 @@ fn find_sugg_for_if_let<'tcx>(
// scrutinee would be, so they have to be considered as well.
// e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
// for the duration if body.
let needs_drop = needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr);
let needs_drop = needs_ordered_drop(cx, check_ty) || any_temporaries_need_ordered_drop(cx, let_expr);
// check that `while_let_on_iterator` lint does not trigger
if_chain! {
@ -362,9 +286,9 @@ fn find_good_method_for_match<'a>(
.qpath_res(path_right, arms[1].pat.hir_id)
.opt_def_id()?;
let body_node_pair = if match_def_path(cx, left_id, expected_left) && match_def_path(cx, right_id, expected_right) {
(&(*arms[0].body).kind, &(*arms[1].body).kind)
(&arms[0].body.kind, &arms[1].body.kind)
} else if match_def_path(cx, right_id, expected_left) && match_def_path(cx, right_id, expected_right) {
(&(*arms[1].body).kind, &(*arms[0].body).kind)
(&arms[1].body.kind, &arms[0].body.kind)
} else {
return None;
};

View file

@ -1,10 +1,10 @@
use crate::FxHashSet;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::get_attr;
use clippy_utils::source::{indent_of, snippet};
use clippy_utils::{get_attr, is_lint_allowed};
use rustc_errors::{Applicability, Diagnostic};
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{Expr, ExprKind, MatchSource};
use rustc_hir::{Arm, Expr, ExprKind, MatchSource};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty::{Ty, TypeAndMut};
@ -16,12 +16,23 @@ pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
scrutinee: &'tcx Expr<'_>,
arms: &'tcx [Arm<'_>],
source: MatchSource,
) {
if is_lint_allowed(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, expr.hir_id) {
return;
}
if let Some((suggestions, message)) = has_significant_drop_in_scrutinee(cx, scrutinee, source) {
for found in suggestions {
span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| {
set_diagnostic(diag, cx, expr, found);
let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None);
diag.span_label(s, "temporary lives until here");
for span in has_significant_drop_in_arms(cx, arms) {
diag.span_label(span, "another value with significant `Drop` created here");
}
diag.note("this might lead to deadlocks or other unexpected behavior");
});
}
}
@ -80,22 +91,77 @@ fn has_significant_drop_in_scrutinee<'tcx, 'a>(
let mut helper = SigDropHelper::new(cx);
helper.find_sig_drop(scrutinee).map(|drops| {
let message = if source == MatchSource::Normal {
"temporary with significant drop in match scrutinee"
"temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression"
} else {
"temporary with significant drop in for loop"
"temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression"
};
(drops, message)
})
}
struct SigDropChecker<'a, 'tcx> {
seen_types: FxHashSet<Ty<'tcx>>,
cx: &'a LateContext<'tcx>,
}
impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
fn new(cx: &'a LateContext<'tcx>) -> SigDropChecker<'a, 'tcx> {
SigDropChecker {
seen_types: FxHashSet::default(),
cx,
}
}
fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> {
self.cx.typeck_results().expr_ty(ex)
}
fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool {
!self.seen_types.insert(ty)
}
fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
if let Some(adt) = ty.ty_adt_def() {
if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 {
return true;
}
}
match ty.kind() {
rustc_middle::ty::Adt(a, b) => {
for f in a.all_fields() {
let ty = f.ty(cx.tcx, b);
if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) {
return true;
}
}
for generic_arg in b.iter() {
if let GenericArgKind::Type(ty) = generic_arg.unpack() {
if self.has_sig_drop_attr(cx, ty) {
return true;
}
}
}
false
},
rustc_middle::ty::Array(ty, _)
| rustc_middle::ty::RawPtr(TypeAndMut { ty, .. })
| rustc_middle::ty::Ref(_, ty, _)
| rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty),
_ => false,
}
}
}
struct SigDropHelper<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
is_chain_end: bool,
seen_types: FxHashSet<Ty<'tcx>>,
has_significant_drop: bool,
current_sig_drop: Option<FoundSigDrop>,
sig_drop_spans: Option<Vec<FoundSigDrop>>,
special_handling_for_binary_op: bool,
sig_drop_checker: SigDropChecker<'a, 'tcx>,
}
#[expect(clippy::enum_variant_names)]
@ -118,11 +184,11 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
SigDropHelper {
cx,
is_chain_end: true,
seen_types: FxHashSet::default(),
has_significant_drop: false,
current_sig_drop: None,
sig_drop_spans: None,
special_handling_for_binary_op: false,
sig_drop_checker: SigDropChecker::new(cx),
}
}
@ -163,7 +229,7 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
if self.current_sig_drop.is_some() {
return;
}
let ty = self.get_type(expr);
let ty = self.sig_drop_checker.get_type(expr);
if ty.is_ref() {
// We checked that the type was ref, so builtin_deref will return Some TypeAndMut,
// but let's avoid any chance of an ICE
@ -187,14 +253,6 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
}
}
fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> {
self.cx.typeck_results().expr_ty(ex)
}
fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool {
!self.seen_types.insert(ty)
}
fn visit_exprs_for_binary_ops(
&mut self,
left: &'tcx Expr<'_>,
@ -214,44 +272,15 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
self.special_handling_for_binary_op = false;
}
fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
if let Some(adt) = ty.ty_adt_def() {
if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 {
return true;
}
}
match ty.kind() {
rustc_middle::ty::Adt(a, b) => {
for f in a.all_fields() {
let ty = f.ty(cx.tcx, b);
if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) {
return true;
}
}
for generic_arg in b.iter() {
if let GenericArgKind::Type(ty) = generic_arg.unpack() {
if self.has_sig_drop_attr(cx, ty) {
return true;
}
}
}
false
},
rustc_middle::ty::Array(ty, _)
| rustc_middle::ty::RawPtr(TypeAndMut { ty, .. })
| rustc_middle::ty::Ref(_, ty, _)
| rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty),
_ => false,
}
}
}
impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
if !self.is_chain_end && self.has_sig_drop_attr(self.cx, self.get_type(ex)) {
if !self.is_chain_end
&& self
.sig_drop_checker
.has_sig_drop_attr(self.cx, self.sig_drop_checker.get_type(ex))
{
self.has_significant_drop = true;
return;
}
@ -330,3 +359,38 @@ impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
}
}
}
struct ArmSigDropHelper<'a, 'tcx> {
sig_drop_checker: SigDropChecker<'a, 'tcx>,
found_sig_drop_spans: FxHashSet<Span>,
}
impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> {
fn new(cx: &'a LateContext<'tcx>) -> ArmSigDropHelper<'a, 'tcx> {
ArmSigDropHelper {
sig_drop_checker: SigDropChecker::new(cx),
found_sig_drop_spans: FxHashSet::<Span>::default(),
}
}
}
fn has_significant_drop_in_arms<'tcx, 'a>(cx: &'a LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> FxHashSet<Span> {
let mut helper = ArmSigDropHelper::new(cx);
for arm in arms {
helper.visit_expr(arm.body);
}
helper.found_sig_drop_spans
}
impl<'a, 'tcx> Visitor<'tcx> for ArmSigDropHelper<'a, 'tcx> {
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
if self
.sig_drop_checker
.has_sig_drop_attr(self.sig_drop_checker.cx, self.sig_drop_checker.get_type(ex))
{
self.found_sig_drop_spans.insert(ex.span);
return;
}
walk_expr(self, ex);
}
}

View file

@ -140,70 +140,45 @@ fn check_opt_like<'a>(
ty: Ty<'a>,
els: Option<&Expr<'_>>,
) {
// list of candidate `Enum`s we know will never get any more members
let candidates = &[
(&paths::COW, "Borrowed"),
(&paths::COW, "Cow::Borrowed"),
(&paths::COW, "Cow::Owned"),
(&paths::COW, "Owned"),
(&paths::OPTION, "None"),
(&paths::RESULT, "Err"),
(&paths::RESULT, "Ok"),
];
// We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive
// match with the second branch, without enum variants in matches.
if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(arms[0].pat, arms[1].pat) {
return;
}
let mut paths_and_types = Vec::new();
if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) {
return;
}
let in_candidate_enum = |path_info: &(String, Ty<'_>)| -> bool {
let (path, ty) = path_info;
for &(ty_path, pat_path) in candidates {
if path == pat_path && match_type(cx, *ty, ty_path) {
return true;
}
}
false
};
if paths_and_types.iter().all(in_candidate_enum) {
// We don't want to lint if the second arm contains an enum which could
// have more variants in the future.
if form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat) {
report_single_pattern(cx, ex, arms, expr, els);
}
}
/// Collects paths and their types from the given patterns. Returns true if the given pattern could
/// be simplified, false otherwise.
fn collect_pat_paths<'a>(acc: &mut Vec<(String, Ty<'a>)>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool {
/// Returns `true` if all of the types in the pattern are enums which we know
/// won't be expanded in the future
fn pat_in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'a>, pat: &Pat<'_>) -> bool {
let mut paths_and_types = Vec::new();
collect_pat_paths(&mut paths_and_types, cx, pat, ty);
paths_and_types.iter().all(|ty| in_candidate_enum(cx, *ty))
}
/// Returns `true` if the given type is an enum we know won't be expanded in the future
fn in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'_>) -> bool {
// list of candidate `Enum`s we know will never get any more members
let candidates = [&paths::COW, &paths::OPTION, &paths::RESULT];
for candidate_ty in candidates {
if match_type(cx, ty, candidate_ty) {
return true;
}
}
false
}
/// Collects types from the given pattern
fn collect_pat_paths<'a>(acc: &mut Vec<Ty<'a>>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) {
match pat.kind {
PatKind::Wild => true,
PatKind::Tuple(inner, _) => inner.iter().all(|p| {
PatKind::Tuple(inner, _) => inner.iter().for_each(|p| {
let p_ty = cx.typeck_results().pat_ty(p);
collect_pat_paths(acc, cx, p, p_ty)
collect_pat_paths(acc, cx, p, p_ty);
}),
PatKind::TupleStruct(ref path, ..) => {
let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
s.print_qpath(path, false);
});
acc.push((path, ty));
true
PatKind::TupleStruct(..) | PatKind::Binding(BindingAnnotation::Unannotated, .., None) | PatKind::Path(_) => {
acc.push(ty);
},
PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => {
acc.push((ident.to_string(), ty));
true
},
PatKind::Path(ref path) => {
let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
s.print_qpath(path, false);
});
acc.push((path, ty));
true
},
_ => false,
_ => {},
}
}
@ -218,7 +193,7 @@ fn contains_only_wilds(pat: &Pat<'_>) -> bool {
/// Returns true if the given patterns forms only exhaustive matches that don't contain enum
/// patterns without a wildcard.
fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool {
fn form_exhaustive_matches<'a>(cx: &LateContext<'a>, ty: Ty<'a>, left: &Pat<'_>, right: &Pat<'_>) -> bool {
match (&left.kind, &right.kind) {
(PatKind::Wild, _) | (_, PatKind::Wild) => true,
(PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => {
@ -264,6 +239,10 @@ fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool {
}
true
},
(PatKind::TupleStruct(..), PatKind::Path(_)) => pat_in_candidate_enum(cx, ty, right),
(PatKind::TupleStruct(..), PatKind::TupleStruct(_, inner, _)) => {
pat_in_candidate_enum(cx, ty, right) && inner.iter().all(contains_only_wilds)
},
_ => false,
}
}

View file

@ -14,14 +14,17 @@ use super::MAP_FLATTEN;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) {
if let Some((caller_ty_name, method_to_use)) = try_get_caller_ty_name_and_method_name(cx, expr, recv, map_arg) {
let mut applicability = Applicability::MachineApplicable;
let closure_snippet = snippet_with_applicability(cx, map_arg.span, "..", &mut applicability);
span_lint_and_sugg(
cx,
MAP_FLATTEN,
expr.span.with_lo(map_span.lo()),
&format!("called `map(..).flatten()` on `{}`", caller_ty_name),
&format!("try replacing `map` with `{}` and remove the `.flatten()`", method_to_use),
&format!(
"try replacing `map` with `{}` and remove the `.flatten()`",
method_to_use
),
format!("{}({})", method_to_use, closure_snippet),
applicability,
);

View file

@ -1,28 +1,21 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet, snippet_opt};
use clippy_utils::ty::{implements_trait, is_copy};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
self as hir, def, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind, Stmt,
StmtKind, TyKind, UnOp,
StmtKind, TyKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::hygiene::DesugaringKind;
use rustc_span::source_map::{ExpnKind, Span};
use rustc_span::symbol::sym;
use clippy_utils::consts::{constant, Constant};
use clippy_utils::sugg::Sugg;
use clippy_utils::{
get_item_name, get_parent_expr, in_constant, is_integer_const, iter_input_pats, last_path_segment,
match_any_def_paths, path_def_id, paths, unsext, SpanlessEq,
};
use clippy_utils::{get_parent_expr, in_constant, iter_input_pats, last_path_segment, SpanlessEq};
declare_clippy_lint! {
/// ### What it does
@ -58,122 +51,6 @@ declare_clippy_lint! {
style,
"an entire binding declared as `ref`, in a function argument or a `let` statement"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for comparisons to NaN.
///
/// ### Why is this bad?
/// NaN does not compare meaningfully to anything not
/// even itself so those comparisons are simply wrong.
///
/// ### Example
/// ```rust
/// # let x = 1.0;
/// if x == f32::NAN { }
/// ```
///
/// Use instead:
/// ```rust
/// # let x = 1.0f32;
/// if x.is_nan() { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub CMP_NAN,
correctness,
"comparisons to `NAN`, which will always return false, probably not intended"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for (in-)equality comparisons on floating-point
/// values (apart from zero), except in functions called `*eq*` (which probably
/// implement equality for a type involving floats).
///
/// ### Why is this bad?
/// Floating point calculations are usually imprecise, so
/// asking if two values are *exactly* equal is asking for trouble. For a good
/// guide on what to do, see [the floating point
/// guide](http://www.floating-point-gui.de/errors/comparison).
///
/// ### Example
/// ```rust
/// let x = 1.2331f64;
/// let y = 1.2332f64;
///
/// if y == 1.23f64 { }
/// if y != x {} // where both are floats
/// ```
///
/// Use instead:
/// ```rust
/// # let x = 1.2331f64;
/// # let y = 1.2332f64;
/// let error_margin = f64::EPSILON; // Use an epsilon for comparison
/// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
/// // let error_margin = std::f64::EPSILON;
/// if (y - 1.23f64).abs() < error_margin { }
/// if (y - x).abs() > error_margin { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub FLOAT_CMP,
pedantic,
"using `==` or `!=` on float values instead of comparing difference with an epsilon"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for conversions to owned values just for the sake
/// of a comparison.
///
/// ### Why is this bad?
/// The comparison can operate on a reference, so creating
/// an owned value effectively throws it away directly afterwards, which is
/// needlessly consuming code and heap space.
///
/// ### Example
/// ```rust
/// # let x = "foo";
/// # let y = String::from("foo");
/// if x.to_owned() == y {}
/// ```
///
/// Use instead:
/// ```rust
/// # let x = "foo";
/// # let y = String::from("foo");
/// if x == y {}
/// ```
#[clippy::version = "pre 1.29.0"]
pub CMP_OWNED,
perf,
"creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for getting the remainder of a division by one or minus
/// one.
///
/// ### Why is this bad?
/// The result for a divisor of one can only ever be zero; for
/// minus one it can cause panic/overflow (if the left operand is the minimal value of
/// the respective integer type) or results in zero. No one will write such code
/// deliberately, unless trying to win an Underhanded Rust Contest. Even for that
/// contest, it's probably a bad idea. Use something more underhanded.
///
/// ### Example
/// ```rust
/// # let x = 1;
/// let a = x % 1;
/// let a = x % -1;
/// ```
#[clippy::version = "pre 1.29.0"]
pub MODULO_ONE,
correctness,
"taking a number modulo +/-1, which can either panic/overflow or always returns 0"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for the use of bindings with a single leading
@ -244,51 +121,11 @@ declare_clippy_lint! {
"using `0 as *{const, mut} T`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for (in-)equality comparisons on floating-point
/// value and constant, except in functions called `*eq*` (which probably
/// implement equality for a type involving floats).
///
/// ### Why is this bad?
/// Floating point calculations are usually imprecise, so
/// asking if two values are *exactly* equal is asking for trouble. For a good
/// guide on what to do, see [the floating point
/// guide](http://www.floating-point-gui.de/errors/comparison).
///
/// ### Example
/// ```rust
/// let x: f64 = 1.0;
/// const ONE: f64 = 1.00;
///
/// if x == ONE { } // where both are floats
/// ```
///
/// Use instead:
/// ```rust
/// # let x: f64 = 1.0;
/// # const ONE: f64 = 1.00;
/// let error_margin = f64::EPSILON; // Use an epsilon for comparison
/// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
/// // let error_margin = std::f64::EPSILON;
/// if (x - ONE).abs() < error_margin { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub FLOAT_CMP_CONST,
restriction,
"using `==` or `!=` on float constants instead of comparing difference with an epsilon"
}
declare_lint_pass!(MiscLints => [
TOPLEVEL_REF_ARG,
CMP_NAN,
FLOAT_CMP,
CMP_OWNED,
MODULO_ONE,
USED_UNDERSCORE_BINDING,
SHORT_CIRCUIT_STATEMENT,
ZERO_PTR,
FLOAT_CMP_CONST
]);
impl<'tcx> LateLintPass<'tcx> for MiscLints {
@ -398,16 +235,9 @@ impl<'tcx> LateLintPass<'tcx> for MiscLints {
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
match expr.kind {
ExprKind::Cast(e, ty) => {
check_cast(cx, expr.span, e, ty);
return;
},
ExprKind::Binary(ref cmp, left, right) => {
check_binary(cx, expr, cmp, left, right);
return;
},
_ => {},
if let ExprKind::Cast(e, ty) = expr.kind {
check_cast(cx, expr.span, e, ty);
return;
}
if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) {
// Don't lint things expanded by #[derive(...)], etc or `await` desugaring
@ -455,236 +285,6 @@ impl<'tcx> LateLintPass<'tcx> for MiscLints {
}
}
fn get_lint_and_message(
is_comparing_constants: bool,
is_comparing_arrays: bool,
) -> (&'static rustc_lint::Lint, &'static str) {
if is_comparing_constants {
(
FLOAT_CMP_CONST,
if is_comparing_arrays {
"strict comparison of `f32` or `f64` constant arrays"
} else {
"strict comparison of `f32` or `f64` constant"
},
)
} else {
(
FLOAT_CMP,
if is_comparing_arrays {
"strict comparison of `f32` or `f64` arrays"
} else {
"strict comparison of `f32` or `f64`"
},
)
}
}
fn check_nan(cx: &LateContext<'_>, expr: &Expr<'_>, cmp_expr: &Expr<'_>) {
if_chain! {
if !in_constant(cx, cmp_expr.hir_id);
if let Some((value, _)) = constant(cx, cx.typeck_results(), expr);
if match value {
Constant::F32(num) => num.is_nan(),
Constant::F64(num) => num.is_nan(),
_ => false,
};
then {
span_lint(
cx,
CMP_NAN,
cmp_expr.span,
"doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead",
);
}
}
}
fn is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
if let Some((_, res)) = constant(cx, cx.typeck_results(), expr) {
res
} else {
false
}
}
fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
match constant(cx, cx.typeck_results(), expr) {
Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(),
Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(),
Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f {
Constant::F32(f) => *f == 0.0 || (*f).is_infinite(),
Constant::F64(f) => *f == 0.0 || (*f).is_infinite(),
_ => false,
}),
_ => false,
}
}
// Return true if `expr` is the result of `signum()` invoked on a float value.
fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
// The negation of a signum is still a signum
if let ExprKind::Unary(UnOp::Neg, child_expr) = expr.kind {
return is_signum(cx, child_expr);
}
if_chain! {
if let ExprKind::MethodCall(method_name, [ref self_arg, ..], _) = expr.kind;
if sym!(signum) == method_name.ident.name;
// Check that the receiver of the signum() is a float (expressions[0] is the receiver of
// the method call)
then {
return is_float(cx, self_arg);
}
}
false
}
fn is_float(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let value = &cx.typeck_results().expr_ty(expr).peel_refs().kind();
if let ty::Array(arr_ty, _) = value {
return matches!(arr_ty.kind(), ty::Float(_));
};
matches!(value, ty::Float(_))
}
fn is_array(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
matches!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _))
}
#[expect(clippy::too_many_lines)]
fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) {
#[derive(Default)]
struct EqImpl {
ty_eq_other: bool,
other_eq_ty: bool,
}
impl EqImpl {
fn is_implemented(&self) -> bool {
self.ty_eq_other || self.other_eq_ty
}
}
fn symmetric_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> Option<EqImpl> {
cx.tcx.lang_items().eq_trait().map(|def_id| EqImpl {
ty_eq_other: implements_trait(cx, ty, def_id, &[other.into()]),
other_eq_ty: implements_trait(cx, other, def_id, &[ty.into()]),
})
}
let typeck = cx.typeck_results();
let (arg, arg_span) = match expr.kind {
ExprKind::MethodCall(.., [arg], _)
if typeck
.type_dependent_def_id(expr.hir_id)
.and_then(|id| cx.tcx.trait_of_item(id))
.map_or(false, |id| {
matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned))
}) =>
{
(arg, arg.span)
},
ExprKind::Call(path, [arg])
if path_def_id(cx, path)
.and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM]))
.map_or(false, |idx| match idx {
0 => true,
1 => !is_copy(cx, typeck.expr_ty(expr)),
_ => false,
}) =>
{
(arg, arg.span)
},
_ => return,
};
let arg_ty = typeck.expr_ty(arg);
let other_ty = typeck.expr_ty(other);
let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default();
let with_deref = arg_ty
.builtin_deref(true)
.and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty))
.unwrap_or_default();
if !with_deref.is_implemented() && !without_deref.is_implemented() {
return;
}
let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _));
let lint_span = if other_gets_derefed {
expr.span.to(other.span)
} else {
expr.span
};
span_lint_and_then(
cx,
CMP_OWNED,
lint_span,
"this creates an owned instance just for comparison",
|diag| {
// This also catches `PartialEq` implementations that call `to_owned`.
if other_gets_derefed {
diag.span_label(lint_span, "try implementing the comparison without allocating");
return;
}
let arg_snip = snippet(cx, arg_span, "..");
let expr_snip;
let eq_impl;
if with_deref.is_implemented() {
expr_snip = format!("*{}", arg_snip);
eq_impl = with_deref;
} else {
expr_snip = arg_snip.to_string();
eq_impl = without_deref;
};
let span;
let hint;
if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) {
span = expr.span;
hint = expr_snip;
} else {
span = expr.span.to(other.span);
let cmp_span = if other.span < expr.span {
other.span.between(expr.span)
} else {
expr.span.between(other.span)
};
if eq_impl.ty_eq_other {
hint = format!(
"{}{}{}",
expr_snip,
snippet(cx, cmp_span, ".."),
snippet(cx, other.span, "..")
);
} else {
hint = format!(
"{}{}{}",
snippet(cx, other.span, ".."),
snippet(cx, cmp_span, ".."),
expr_snip
);
}
}
diag.span_suggestion(
span,
"try",
hint,
Applicability::MachineApplicable, // snippet
);
},
);
}
/// Heuristic to see if an expression is used. Should be compatible with
/// `unused_variables`'s idea
/// of what it means for an expression to be "used".
@ -740,74 +340,3 @@ fn check_cast(cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>)
}
}
}
fn check_binary<'a>(
cx: &LateContext<'a>,
expr: &Expr<'_>,
cmp: &rustc_span::source_map::Spanned<rustc_hir::BinOpKind>,
left: &'a Expr<'_>,
right: &'a Expr<'_>,
) {
let op = cmp.node;
if op.is_comparison() {
check_nan(cx, left, expr);
check_nan(cx, right, expr);
check_to_owned(cx, left, right, true);
check_to_owned(cx, right, left, false);
}
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
if is_allowed(cx, left) || is_allowed(cx, right) {
return;
}
// Allow comparing the results of signum()
if is_signum(cx, left) && is_signum(cx, right) {
return;
}
if let Some(name) = get_item_name(cx, expr) {
let name = name.as_str();
if name == "eq" || name == "ne" || name == "is_nan" || name.starts_with("eq_") || name.ends_with("_eq") {
return;
}
}
let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
let (lint, msg) = get_lint_and_message(
is_named_constant(cx, left) || is_named_constant(cx, right),
is_comparing_arrays,
);
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
let lhs = Sugg::hir(cx, left, "..");
let rhs = Sugg::hir(cx, right, "..");
if !is_comparing_arrays {
diag.span_suggestion(
expr.span,
"consider comparing them within some margin of error",
format!(
"({}).abs() {} error_margin",
lhs - rhs,
if op == BinOpKind::Eq { '<' } else { '>' }
),
Applicability::HasPlaceholders, // snippet
);
}
diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`");
});
} else if op == BinOpKind::Rem {
if is_integer_const(cx, right, 1) {
span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0");
}
if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() {
if is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) {
span_lint(
cx,
MODULO_ONE,
expr.span,
"any number modulo -1 will panic/overflow or result in 0",
);
}
};
}
}

View file

@ -7,7 +7,8 @@
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::span_lint;
use rustc_ast::ast;
use if_chain::if_chain;
use rustc_ast::ast::{self, MetaItem, MetaItemKind};
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::{self, DefIdTree};
@ -57,6 +58,20 @@ impl MissingDoc {
*self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
}
fn has_include(meta: Option<MetaItem>) -> bool {
if_chain! {
if let Some(meta) = meta;
if let MetaItemKind::List(list) = meta.kind;
if let Some(meta) = list.get(0);
if let Some(name) = meta.ident();
then {
name.name == sym::include
} else {
false
}
}
}
fn check_missing_docs_attrs(
&self,
cx: &LateContext<'_>,
@ -80,7 +95,9 @@ impl MissingDoc {
return;
}
let has_doc = attrs.iter().any(|a| a.doc_str().is_some());
let has_doc = attrs
.iter()
.any(|a| a.doc_str().is_some() || Self::has_include(a.meta()));
if !has_doc {
span_lint(
cx,

View file

@ -95,10 +95,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
self.found = true;
return;
},
ExprKind::If(..) => {
self.found = true;
return;
},
ExprKind::Path(_) => {
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
if adj

View file

@ -1,85 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks for uses of bitwise and/or operators between booleans, where performance may be improved by using
/// a lazy and.
///
/// ### Why is this bad?
/// The bitwise operators do not support short-circuiting, so it may hinder code performance.
/// Additionally, boolean logic "masked" as bitwise logic is not caught by lints like `unnecessary_fold`
///
/// ### Known problems
/// This lint evaluates only when the right side is determined to have no side effects. At this time, that
/// determination is quite conservative.
///
/// ### Example
/// ```rust
/// let (x,y) = (true, false);
/// if x & !y {} // where both x and y are booleans
/// ```
/// Use instead:
/// ```rust
/// let (x,y) = (true, false);
/// if x && !y {}
/// ```
#[clippy::version = "1.54.0"]
pub NEEDLESS_BITWISE_BOOL,
pedantic,
"Boolean expressions that use bitwise rather than lazy operators"
}
declare_lint_pass!(NeedlessBitwiseBool => [NEEDLESS_BITWISE_BOOL]);
fn is_bitwise_operation(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let ty = cx.typeck_results().expr_ty(expr);
if_chain! {
if !expr.span.from_expansion();
if let (&ExprKind::Binary(ref op, _, right), &ty::Bool) = (&expr.kind, &ty.kind());
if op.node == BinOpKind::BitAnd || op.node == BinOpKind::BitOr;
if let ExprKind::Call(..) | ExprKind::MethodCall(..) | ExprKind::Binary(..) | ExprKind::Unary(..) = right.kind;
if !right.can_have_side_effects();
then {
return true;
}
}
false
}
fn suggestion_snippet(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
if let ExprKind::Binary(ref op, left, right) = expr.kind {
if let (Some(l_snippet), Some(r_snippet)) = (snippet_opt(cx, left.span), snippet_opt(cx, right.span)) {
let op_snippet = match op.node {
BinOpKind::BitAnd => "&&",
_ => "||",
};
return Some(format!("{} {} {}", l_snippet, op_snippet, r_snippet));
}
}
None
}
impl LateLintPass<'_> for NeedlessBitwiseBool {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if is_bitwise_operation(cx, expr) {
span_lint_and_then(
cx,
NEEDLESS_BITWISE_BOOL,
expr.span,
"use of bitwise operator instead of lazy operator between booleans",
|diag| {
if let Some(sugg) = suggestion_snippet(cx, expr) {
diag.span_suggestion(expr.span, "try", sugg, Applicability::MachineApplicable);
}
},
);
}
}
}

View file

@ -1,7 +1,9 @@
use clippy_utils::consts::{self, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::has_enclosing_paren;
use if_chain::if_chain;
use rustc_ast::util::parser::PREC_PREFIX;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
@ -58,7 +60,12 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) {
then {
let mut applicability = Applicability::MachineApplicable;
let suggestion = format!("-{}", snippet_with_applicability(cx, exp.span, "..", &mut applicability));
let snip = snippet_with_applicability(cx, exp.span, "..", &mut applicability);
let suggestion = if exp.precedence().order() < PREC_PREFIX && !has_enclosing_paren(&snip) {
format!("-({})", snip)
} else {
format!("-{}", snip)
};
span_lint_and_sugg(
cx,
NEG_MULTIPLY,

View file

@ -6,6 +6,7 @@ use std::ptr;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::in_constant;
use clippy_utils::macros::macro_backtrace;
use if_chain::if_chain;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
@ -18,7 +19,7 @@ use rustc_middle::mir::interpret::{ConstValue, ErrorHandled};
use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{InnerSpan, Span, DUMMY_SP};
use rustc_span::{sym, InnerSpan, Span, DUMMY_SP};
use rustc_typeck::hir_ty_to_ty;
// FIXME: this is a correctness problem but there's no suitable
@ -250,8 +251,14 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) {
if let ItemKind::Const(hir_ty, body_id) = it.kind {
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
if is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, ty) {
if !macro_backtrace(it.span).last().map_or(false, |macro_call| {
matches!(
cx.tcx.get_diagnostic_name(macro_call.def_id),
Some(sym::thread_local_macro)
)
}) && is_unfrozen(cx, ty)
&& is_value_unfrozen_poly(cx, body_id, ty)
{
lint(cx, Source::Item { item: it.span });
}
}

View file

@ -159,12 +159,10 @@ impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> {
#[must_use]
fn get_exemptions(interned_name: &str) -> Option<&'static [&'static str]> {
for &list in ALLOWED_TO_BE_SIMILAR {
if allowed_to_be_similar(interned_name, list) {
return Some(list);
}
}
None
ALLOWED_TO_BE_SIMILAR
.iter()
.find(|&&list| allowed_to_be_similar(interned_name, list))
.copied()
}
#[must_use]
@ -328,7 +326,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SimilarNamesLocalVisitor<'a, 'tcx> {
// add the pattern after the expression because the bindings aren't available
// yet in the init
// expression
SimilarNamesNameVisitor(self).visit_pat(&*local.pat);
SimilarNamesNameVisitor(self).visit_pat(&local.pat);
}
fn visit_block(&mut self, blk: &'tcx Block) {
self.single_char_names.push(vec![]);

View file

@ -1,170 +0,0 @@
use clippy_utils::consts::constant_simple;
use clippy_utils::diagnostics::span_lint;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span;
declare_clippy_lint! {
/// ### What it does
/// Checks for integer arithmetic operations which could overflow or panic.
///
/// Specifically, checks for any operators (`+`, `-`, `*`, `<<`, etc) which are capable
/// of overflowing according to the [Rust
/// Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow),
/// or which can panic (`/`, `%`). No bounds analysis or sophisticated reasoning is
/// attempted.
///
/// ### Why is this bad?
/// Integer overflow will trigger a panic in debug builds or will wrap in
/// release mode. Division by zero will cause a panic in either mode. In some applications one
/// wants explicitly checked, wrapping or saturating arithmetic.
///
/// ### Example
/// ```rust
/// # let a = 0;
/// a + 1;
/// ```
#[clippy::version = "pre 1.29.0"]
pub INTEGER_ARITHMETIC,
restriction,
"any integer arithmetic expression which could overflow or panic"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for float arithmetic.
///
/// ### Why is this bad?
/// For some embedded systems or kernel development, it
/// can be useful to rule out floating-point numbers.
///
/// ### Example
/// ```rust
/// # let a = 0.0;
/// a + 1.0;
/// ```
#[clippy::version = "pre 1.29.0"]
pub FLOAT_ARITHMETIC,
restriction,
"any floating-point arithmetic statement"
}
#[derive(Copy, Clone, Default)]
pub struct NumericArithmetic {
expr_span: Option<Span>,
/// This field is used to check whether expressions are constants, such as in enum discriminants
/// and consts
const_span: Option<Span>,
}
impl_lint_pass!(NumericArithmetic => [INTEGER_ARITHMETIC, FLOAT_ARITHMETIC]);
impl<'tcx> LateLintPass<'tcx> for NumericArithmetic {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if self.expr_span.is_some() {
return;
}
if let Some(span) = self.const_span {
if span.contains(expr.span) {
return;
}
}
match &expr.kind {
hir::ExprKind::Binary(op, l, r) | hir::ExprKind::AssignOp(op, l, r) => {
match op.node {
hir::BinOpKind::And
| hir::BinOpKind::Or
| hir::BinOpKind::BitAnd
| hir::BinOpKind::BitOr
| hir::BinOpKind::BitXor
| hir::BinOpKind::Eq
| hir::BinOpKind::Lt
| hir::BinOpKind::Le
| hir::BinOpKind::Ne
| hir::BinOpKind::Ge
| hir::BinOpKind::Gt => return,
_ => (),
}
let (l_ty, r_ty) = (cx.typeck_results().expr_ty(l), cx.typeck_results().expr_ty(r));
if l_ty.peel_refs().is_integral() && r_ty.peel_refs().is_integral() {
match op.node {
hir::BinOpKind::Div | hir::BinOpKind::Rem => match &r.kind {
hir::ExprKind::Lit(_lit) => (),
hir::ExprKind::Unary(hir::UnOp::Neg, expr) => {
if let hir::ExprKind::Lit(lit) = &expr.kind {
if let rustc_ast::ast::LitKind::Int(1, _) = lit.node {
span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
self.expr_span = Some(expr.span);
}
}
},
_ => {
span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
self.expr_span = Some(expr.span);
},
},
_ => {
span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
self.expr_span = Some(expr.span);
},
}
} else if r_ty.peel_refs().is_floating_point() && r_ty.peel_refs().is_floating_point() {
span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
self.expr_span = Some(expr.span);
}
},
hir::ExprKind::Unary(hir::UnOp::Neg, arg) => {
let ty = cx.typeck_results().expr_ty(arg);
if constant_simple(cx, cx.typeck_results(), expr).is_none() {
if ty.is_integral() {
span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
self.expr_span = Some(expr.span);
} else if ty.is_floating_point() {
span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
self.expr_span = Some(expr.span);
}
}
},
_ => (),
}
}
fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if Some(expr.span) == self.expr_span {
self.expr_span = None;
}
}
fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
let body_owner = cx.tcx.hir().body_owner_def_id(body.id());
match cx.tcx.hir().body_owner_kind(body_owner) {
hir::BodyOwnerKind::Static(_) | hir::BodyOwnerKind::Const => {
let body_span = cx.tcx.def_span(body_owner);
if let Some(span) = self.const_span {
if span.contains(body_span) {
return;
}
}
self.const_span = Some(body_span);
},
hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => (),
}
}
fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
let body_owner = cx.tcx.hir().body_owner(body.id());
let body_span = cx.tcx.hir().span(body_owner);
if let Some(span) = self.const_span {
if span.contains(body_span) {
return;
}
}
self.const_span = None;
}
}

View file

@ -1,7 +1,6 @@
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use clippy_utils::comparisons::{normalize_comparison, Rel};
use clippy_utils::consts::{constant, Constant};
@ -10,73 +9,41 @@ use clippy_utils::source::snippet;
use clippy_utils::ty::is_isize_or_usize;
use clippy_utils::{clip, int_bits, unsext};
declare_clippy_lint! {
/// ### What it does
/// Checks for comparisons where one side of the relation is
/// either the minimum or maximum value for its type and warns if it involves a
/// case that is always true or always false. Only integer and boolean types are
/// checked.
///
/// ### Why is this bad?
/// An expression like `min <= x` may misleadingly imply
/// that it is possible for `x` to be less than the minimum. Expressions like
/// `max < x` are probably mistakes.
///
/// ### Known problems
/// For `usize` the size of the current compile target will
/// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such
/// a comparison to detect target pointer width will trigger this lint. One can
/// use `mem::sizeof` and compare its value or conditional compilation
/// attributes
/// like `#[cfg(target_pointer_width = "64")] ..` instead.
///
/// ### Example
/// ```rust
/// let vec: Vec<isize> = Vec::new();
/// if vec.len() <= 0 {}
/// if 100 > i32::MAX {}
/// ```
#[clippy::version = "pre 1.29.0"]
pub ABSURD_EXTREME_COMPARISONS,
correctness,
"a comparison with a maximum or minimum value that is always true or false"
}
use super::ABSURD_EXTREME_COMPARISONS;
declare_lint_pass!(AbsurdExtremeComparisons => [ABSURD_EXTREME_COMPARISONS]);
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
op: BinOpKind,
lhs: &'tcx Expr<'_>,
rhs: &'tcx Expr<'_>,
) {
if let Some((culprit, result)) = detect_absurd_comparison(cx, op, lhs, rhs) {
let msg = "this comparison involving the minimum or maximum element for this \
type contains a case that is always true or always false";
impl<'tcx> LateLintPass<'tcx> for AbsurdExtremeComparisons {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind {
if let Some((culprit, result)) = detect_absurd_comparison(cx, cmp.node, lhs, rhs) {
if !expr.span.from_expansion() {
let msg = "this comparison involving the minimum or maximum element for this \
type contains a case that is always true or always false";
let conclusion = match result {
AbsurdComparisonResult::AlwaysFalse => "this comparison is always false".to_owned(),
AbsurdComparisonResult::AlwaysTrue => "this comparison is always true".to_owned(),
AbsurdComparisonResult::InequalityImpossible => format!(
"the case where the two sides are not equal never occurs, consider using `{} == {}` \
instead",
snippet(cx, lhs.span, "lhs"),
snippet(cx, rhs.span, "rhs")
),
};
let conclusion = match result {
AbsurdComparisonResult::AlwaysFalse => "this comparison is always false".to_owned(),
AbsurdComparisonResult::AlwaysTrue => "this comparison is always true".to_owned(),
AbsurdComparisonResult::InequalityImpossible => format!(
"the case where the two sides are not equal never occurs, consider using `{} == {}` \
instead",
snippet(cx, lhs.span, "lhs"),
snippet(cx, rhs.span, "rhs")
),
};
let help = format!(
"because `{}` is the {} value for this type, {}",
snippet(cx, culprit.expr.span, "x"),
match culprit.which {
ExtremeType::Minimum => "minimum",
ExtremeType::Maximum => "maximum",
},
conclusion
);
let help = format!(
"because `{}` is the {} value for this type, {}",
snippet(cx, culprit.expr.span, "x"),
match culprit.which {
ExtremeType::Minimum => "minimum",
ExtremeType::Maximum => "maximum",
},
conclusion
);
span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help);
}
}
}
span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help);
}
}

View file

@ -0,0 +1,101 @@
use clippy_utils::binop_traits;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::implements_trait;
use clippy_utils::{eq_expr_value, trait_ref_of_method};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_lint::LateContext;
use super::ASSIGN_OP_PATTERN;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'_>,
assignee: &'tcx hir::Expr<'_>,
e: &'tcx hir::Expr<'_>,
) {
if let hir::ExprKind::Binary(op, l, r) = &e.kind {
let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| {
let ty = cx.typeck_results().expr_ty(assignee);
let rty = cx.typeck_results().expr_ty(rhs);
if_chain! {
if let Some((_, lang_item)) = binop_traits(op.node);
if let Ok(trait_id) = cx.tcx.lang_items().require(lang_item);
let parent_fn = cx.tcx.hir().get_parent_item(e.hir_id);
if trait_ref_of_method(cx, parent_fn)
.map_or(true, |t| t.path.res.def_id() != trait_id);
if implements_trait(cx, ty, trait_id, &[rty.into()]);
then {
span_lint_and_then(
cx,
ASSIGN_OP_PATTERN,
expr.span,
"manual implementation of an assign operation",
|diag| {
if let (Some(snip_a), Some(snip_r)) =
(snippet_opt(cx, assignee.span), snippet_opt(cx, rhs.span))
{
diag.span_suggestion(
expr.span,
"replace it with",
format!("{} {}= {}", snip_a, op.node.as_str(), snip_r),
Applicability::MachineApplicable,
);
}
},
);
}
}
};
let mut visitor = ExprVisitor {
assignee,
counter: 0,
cx,
};
walk_expr(&mut visitor, e);
if visitor.counter == 1 {
// a = a op b
if eq_expr_value(cx, assignee, l) {
lint(assignee, r);
}
// a = b commutative_op a
// Limited to primitive type as these ops are know to be commutative
if eq_expr_value(cx, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() {
match op.node {
hir::BinOpKind::Add
| hir::BinOpKind::Mul
| hir::BinOpKind::And
| hir::BinOpKind::Or
| hir::BinOpKind::BitXor
| hir::BinOpKind::BitAnd
| hir::BinOpKind::BitOr => {
lint(assignee, l);
},
_ => {},
}
}
}
}
}
struct ExprVisitor<'a, 'tcx> {
assignee: &'a hir::Expr<'a>,
counter: u8,
cx: &'a LateContext<'tcx>,
}
impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
if eq_expr_value(self.cx, self.assignee, expr) {
self.counter += 1;
}
walk_expr(self, expr);
}
}

View file

@ -1,161 +1,23 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::sugg::Sugg;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use clippy_utils::diagnostics::span_lint;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_lint::LateContext;
use rustc_span::source_map::Span;
declare_clippy_lint! {
/// ### What it does
/// Checks for incompatible bit masks in comparisons.
///
/// The formula for detecting if an expression of the type `_ <bit_op> m
/// <cmp_op> c` (where `<bit_op>` is one of {`&`, `|`} and `<cmp_op>` is one of
/// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following
/// table:
///
/// |Comparison |Bit Op|Example |is always|Formula |
/// |------------|------|-------------|---------|----------------------|
/// |`==` or `!=`| `&` |`x & 2 == 3` |`false` |`c & m != c` |
/// |`<` or `>=`| `&` |`x & 2 < 3` |`true` |`m < c` |
/// |`>` or `<=`| `&` |`x & 1 > 1` |`false` |`m <= c` |
/// |`==` or `!=`| `\|` |`x \| 1 == 0`|`false` |`c \| m != c` |
/// |`<` or `>=`| `\|` |`x \| 1 < 1` |`false` |`m >= c` |
/// |`<=` or `>` | `\|` |`x \| 1 > 0` |`true` |`m > c` |
///
/// ### Why is this bad?
/// If the bits that the comparison cares about are always
/// set to zero or one by the bit mask, the comparison is constant `true` or
/// `false` (depending on mask, compared value, and operators).
///
/// So the code is actively misleading, and the only reason someone would write
/// this intentionally is to win an underhanded Rust contest or create a
/// test-case for this lint.
///
/// ### Example
/// ```rust
/// # let x = 1;
/// if (x & 1 == 2) { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub BAD_BIT_MASK,
correctness,
"expressions of the form `_ & mask == select` that will only ever return `true` or `false`"
}
use super::{BAD_BIT_MASK, INEFFECTIVE_BIT_MASK};
declare_clippy_lint! {
/// ### What it does
/// Checks for bit masks in comparisons which can be removed
/// without changing the outcome. The basic structure can be seen in the
/// following table:
///
/// |Comparison| Bit Op |Example |equals |
/// |----------|----------|------------|-------|
/// |`>` / `<=`|`\|` / `^`|`x \| 2 > 3`|`x > 3`|
/// |`<` / `>=`|`\|` / `^`|`x ^ 1 < 4` |`x < 4`|
///
/// ### Why is this bad?
/// Not equally evil as [`bad_bit_mask`](#bad_bit_mask),
/// but still a bit misleading, because the bit mask is ineffective.
///
/// ### Known problems
/// False negatives: This lint will only match instances
/// where we have figured out the math (which is for a power-of-two compared
/// value). This means things like `x | 1 >= 7` (which would be better written
/// as `x >= 6`) will not be reported (but bit masks like this are fairly
/// uncommon).
///
/// ### Example
/// ```rust
/// # let x = 1;
/// if (x | 1 > 3) { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub INEFFECTIVE_BIT_MASK,
correctness,
"expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for bit masks that can be replaced by a call
/// to `trailing_zeros`
///
/// ### Why is this bad?
/// `x.trailing_zeros() > 4` is much clearer than `x & 15
/// == 0`
///
/// ### Known problems
/// llvm generates better code for `x & 15 == 0` on x86
///
/// ### Example
/// ```rust
/// # let x = 1;
/// if x & 0b1111 == 0 { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub VERBOSE_BIT_MASK,
pedantic,
"expressions where a bit mask is less readable than the corresponding method call"
}
#[derive(Copy, Clone)]
pub struct BitMask {
verbose_bit_mask_threshold: u64,
}
impl BitMask {
#[must_use]
pub fn new(verbose_bit_mask_threshold: u64) -> Self {
Self {
verbose_bit_mask_threshold,
}
}
}
impl_lint_pass!(BitMask => [BAD_BIT_MASK, INEFFECTIVE_BIT_MASK, VERBOSE_BIT_MASK]);
impl<'tcx> LateLintPass<'tcx> for BitMask {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Binary(cmp, left, right) = &e.kind {
if cmp.node.is_comparison() {
if let Some(cmp_opt) = fetch_int_literal(cx, right) {
check_compare(cx, left, cmp.node, cmp_opt, e.span);
} else if let Some(cmp_val) = fetch_int_literal(cx, left) {
check_compare(cx, right, invert_cmp(cmp.node), cmp_val, e.span);
}
}
}
if let ExprKind::Binary(op, left, right) = &e.kind
&& BinOpKind::Eq == op.node
&& let ExprKind::Binary(op1, left1, right1) = &left.kind
&& BinOpKind::BitAnd == op1.node
&& let ExprKind::Lit(lit) = &right1.kind
&& let LitKind::Int(n, _) = lit.node
&& let ExprKind::Lit(lit1) = &right.kind
&& let LitKind::Int(0, _) = lit1.node
&& n.leading_zeros() == n.count_zeros()
&& n > u128::from(self.verbose_bit_mask_threshold)
{
span_lint_and_then(
cx,
VERBOSE_BIT_MASK,
e.span,
"bit mask could be simplified with a call to `trailing_zeros`",
|diag| {
let sugg = Sugg::hir(cx, left1, "...").maybe_par();
diag.span_suggestion(
e.span,
"try",
format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()),
Applicability::MaybeIncorrect,
);
},
);
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
op: BinOpKind,
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
) {
if op.is_comparison() {
if let Some(cmp_opt) = fetch_int_literal(cx, right) {
check_compare(cx, left, op, cmp_opt, e.span);
} else if let Some(cmp_val) = fetch_int_literal(cx, left) {
check_compare(cx, right, invert_cmp(op), cmp_val, e.span);
}
}
}

View file

@ -0,0 +1,30 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::in_constant;
use rustc_hir::{BinOpKind, Expr};
use rustc_lint::LateContext;
use super::CMP_NAN;
pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>) {
if op.is_comparison() && !in_constant(cx, e.hir_id) && (is_nan(cx, lhs) || is_nan(cx, rhs)) {
span_lint(
cx,
CMP_NAN,
e.span,
"doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead",
);
}
}
fn is_nan(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
if let Some((value, _)) = constant(cx, cx.typeck_results(), e) {
match value {
Constant::F32(num) => num.is_nan(),
Constant::F64(num) => num.is_nan(),
_ => false,
}
} else {
false
}
}

View file

@ -0,0 +1,147 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::{match_any_def_paths, path_def_id, paths};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_span::symbol::sym;
use super::CMP_OWNED;
pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>) {
if op.is_comparison() {
check_op(cx, lhs, rhs, true);
check_op(cx, rhs, lhs, false);
}
}
#[derive(Default)]
struct EqImpl {
ty_eq_other: bool,
other_eq_ty: bool,
}
impl EqImpl {
fn is_implemented(&self) -> bool {
self.ty_eq_other || self.other_eq_ty
}
}
fn symmetric_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> Option<EqImpl> {
cx.tcx.lang_items().eq_trait().map(|def_id| EqImpl {
ty_eq_other: implements_trait(cx, ty, def_id, &[other.into()]),
other_eq_ty: implements_trait(cx, other, def_id, &[ty.into()]),
})
}
fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) {
let typeck = cx.typeck_results();
let (arg, arg_span) = match expr.kind {
ExprKind::MethodCall(.., [arg], _)
if typeck
.type_dependent_def_id(expr.hir_id)
.and_then(|id| cx.tcx.trait_of_item(id))
.map_or(false, |id| {
matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned))
}) =>
{
(arg, arg.span)
},
ExprKind::Call(path, [arg])
if path_def_id(cx, path)
.and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM]))
.map_or(false, |idx| match idx {
0 => true,
1 => !is_copy(cx, typeck.expr_ty(expr)),
_ => false,
}) =>
{
(arg, arg.span)
},
_ => return,
};
let arg_ty = typeck.expr_ty(arg);
let other_ty = typeck.expr_ty(other);
let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default();
let with_deref = arg_ty
.builtin_deref(true)
.and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty))
.unwrap_or_default();
if !with_deref.is_implemented() && !without_deref.is_implemented() {
return;
}
let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _));
let lint_span = if other_gets_derefed {
expr.span.to(other.span)
} else {
expr.span
};
span_lint_and_then(
cx,
CMP_OWNED,
lint_span,
"this creates an owned instance just for comparison",
|diag| {
// This also catches `PartialEq` implementations that call `to_owned`.
if other_gets_derefed {
diag.span_label(lint_span, "try implementing the comparison without allocating");
return;
}
let arg_snip = snippet(cx, arg_span, "..");
let expr_snip;
let eq_impl;
if with_deref.is_implemented() {
expr_snip = format!("*{}", arg_snip);
eq_impl = with_deref;
} else {
expr_snip = arg_snip.to_string();
eq_impl = without_deref;
};
let span;
let hint;
if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) {
span = expr.span;
hint = expr_snip;
} else {
span = expr.span.to(other.span);
let cmp_span = if other.span < expr.span {
other.span.between(expr.span)
} else {
expr.span.between(other.span)
};
if eq_impl.ty_eq_other {
hint = format!(
"{}{}{}",
expr_snip,
snippet(cx, cmp_span, ".."),
snippet(cx, other.span, "..")
);
} else {
hint = format!(
"{}{}{}",
snippet(cx, other.span, ".."),
snippet(cx, cmp_span, ".."),
expr_snip
);
}
}
diag.span_suggestion(
span,
"try",
hint,
Applicability::MachineApplicable, // snippet
);
},
);
}

View file

@ -0,0 +1,54 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eq_expr_value;
use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::source_map::Span;
use super::DOUBLE_COMPARISONS;
#[expect(clippy::similar_names)]
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) {
let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) {
(ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => {
(lb.node, llhs, lrhs, rb.node, rlhs, rrhs)
},
_ => return,
};
if !(eq_expr_value(cx, llhs, rlhs) && eq_expr_value(cx, lrhs, rrhs)) {
return;
}
macro_rules! lint_double_comparison {
($op:tt) => {{
let mut applicability = Applicability::MachineApplicable;
let lhs_str = snippet_with_applicability(cx, llhs.span, "", &mut applicability);
let rhs_str = snippet_with_applicability(cx, lrhs.span, "", &mut applicability);
let sugg = format!("{} {} {}", lhs_str, stringify!($op), rhs_str);
span_lint_and_sugg(
cx,
DOUBLE_COMPARISONS,
span,
"this binary expression can be simplified",
"try",
sugg,
applicability,
);
}};
}
match (op, lkind, rkind) {
(BinOpKind::Or, BinOpKind::Eq, BinOpKind::Lt) | (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Eq) => {
lint_double_comparison!(<=);
},
(BinOpKind::Or, BinOpKind::Eq, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Eq) => {
lint_double_comparison!(>=);
},
(BinOpKind::Or, BinOpKind::Lt, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Lt) => {
lint_double_comparison!(!=);
},
(BinOpKind::And, BinOpKind::Le, BinOpKind::Ge) | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => {
lint_double_comparison!(==);
},
_ => (),
};
}

View file

@ -0,0 +1,44 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::sym;
use super::DURATION_SUBSEC;
pub(crate) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
op: BinOpKind,
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
) {
if op == BinOpKind::Div
&& let ExprKind::MethodCall(method_path, [self_arg], _) = left.kind
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_arg).peel_refs(), sym::Duration)
&& let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right)
{
let suggested_fn = match (method_path.ident.as_str(), divisor) {
("subsec_micros", 1_000) | ("subsec_nanos", 1_000_000) => "subsec_millis",
("subsec_nanos", 1_000) => "subsec_micros",
_ => return,
};
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
DURATION_SUBSEC,
expr.span,
&format!("calling `{}()` is more concise than this calculation", suggested_fn),
"try",
format!(
"{}.{}()",
snippet_with_applicability(cx, self_arg.span, "_", &mut applicability),
suggested_fn
),
applicability,
);
}
}

View file

@ -0,0 +1,45 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace};
use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, is_in_test_function};
use rustc_hir::{BinOpKind, Expr};
use rustc_lint::LateContext;
use super::EQ_OP;
pub(crate) fn check_assert<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let Some((macro_call, macro_name))
= first_node_macro_backtrace(cx, e).find_map(|macro_call| {
let name = cx.tcx.item_name(macro_call.def_id);
matches!(name.as_str(), "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne")
.then(|| (macro_call, name))
})
&& let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn)
&& eq_expr_value(cx, lhs, rhs)
&& macro_call.is_local()
&& !is_in_test_function(cx.tcx, e.hir_id)
{
span_lint(
cx,
EQ_OP,
lhs.span.to(rhs.span),
&format!("identical args used in this `{}!` macro call", macro_name),
);
}
}
pub(crate) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
op: BinOpKind,
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
) {
if is_useless_with_eq_exprs(op.into()) && eq_expr_value(cx, left, right) && !is_in_test_function(cx.tcx, e.hir_id) {
span_lint(
cx,
EQ_OP,
e.span,
&format!("equal expressions as operands to `{}`", op.as_str()),
);
}
}

View file

@ -0,0 +1,53 @@
use clippy_utils::consts::{constant_simple, Constant};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::same_type_and_consts;
use rustc_hir::{BinOpKind, Expr};
use rustc_lint::LateContext;
use rustc_middle::ty::TypeckResults;
use super::ERASING_OP;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
op: BinOpKind,
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
) {
let tck = cx.typeck_results();
match op {
BinOpKind::Mul | BinOpKind::BitAnd => {
check_op(cx, tck, left, right, e);
check_op(cx, tck, right, left, e);
},
BinOpKind::Div => check_op(cx, tck, left, right, e),
_ => (),
}
}
fn different_types(tck: &TypeckResults<'_>, input: &Expr<'_>, output: &Expr<'_>) -> bool {
let input_ty = tck.expr_ty(input).peel_refs();
let output_ty = tck.expr_ty(output).peel_refs();
!same_type_and_consts(input_ty, output_ty)
}
fn check_op<'tcx>(
cx: &LateContext<'tcx>,
tck: &TypeckResults<'tcx>,
op: &Expr<'tcx>,
other: &Expr<'tcx>,
parent: &Expr<'tcx>,
) {
if constant_simple(cx, tck, op) == Some(Constant::Int(0)) {
if different_types(tck, other, parent) {
return;
}
span_lint(
cx,
ERASING_OP,
parent.span,
"this operation will always return zero. This is likely not the intended outcome",
);
}
}

View file

@ -0,0 +1,139 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::get_item_name;
use clippy_utils::sugg::Sugg;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::LateContext;
use rustc_middle::ty;
use super::{FLOAT_CMP, FLOAT_CMP_CONST};
pub(crate) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
op: BinOpKind,
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
) {
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
if is_allowed(cx, left) || is_allowed(cx, right) {
return;
}
// Allow comparing the results of signum()
if is_signum(cx, left) && is_signum(cx, right) {
return;
}
if let Some(name) = get_item_name(cx, expr) {
let name = name.as_str();
if name == "eq" || name == "ne" || name == "is_nan" || name.starts_with("eq_") || name.ends_with("_eq") {
return;
}
}
let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
let (lint, msg) = get_lint_and_message(
is_named_constant(cx, left) || is_named_constant(cx, right),
is_comparing_arrays,
);
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
let lhs = Sugg::hir(cx, left, "..");
let rhs = Sugg::hir(cx, right, "..");
if !is_comparing_arrays {
diag.span_suggestion(
expr.span,
"consider comparing them within some margin of error",
format!(
"({}).abs() {} error_margin",
lhs - rhs,
if op == BinOpKind::Eq { '<' } else { '>' }
),
Applicability::HasPlaceholders, // snippet
);
}
diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`");
});
}
}
fn get_lint_and_message(
is_comparing_constants: bool,
is_comparing_arrays: bool,
) -> (&'static rustc_lint::Lint, &'static str) {
if is_comparing_constants {
(
FLOAT_CMP_CONST,
if is_comparing_arrays {
"strict comparison of `f32` or `f64` constant arrays"
} else {
"strict comparison of `f32` or `f64` constant"
},
)
} else {
(
FLOAT_CMP,
if is_comparing_arrays {
"strict comparison of `f32` or `f64` arrays"
} else {
"strict comparison of `f32` or `f64`"
},
)
}
}
fn is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
if let Some((_, res)) = constant(cx, cx.typeck_results(), expr) {
res
} else {
false
}
}
fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
match constant(cx, cx.typeck_results(), expr) {
Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(),
Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(),
Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f {
Constant::F32(f) => *f == 0.0 || (*f).is_infinite(),
Constant::F64(f) => *f == 0.0 || (*f).is_infinite(),
_ => false,
}),
_ => false,
}
}
// Return true if `expr` is the result of `signum()` invoked on a float value.
fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
// The negation of a signum is still a signum
if let ExprKind::Unary(UnOp::Neg, child_expr) = expr.kind {
return is_signum(cx, child_expr);
}
if_chain! {
if let ExprKind::MethodCall(method_name, [ref self_arg, ..], _) = expr.kind;
if sym!(signum) == method_name.ident.name;
// Check that the receiver of the signum() is a float (expressions[0] is the receiver of
// the method call)
then {
return is_float(cx, self_arg);
}
}
false
}
fn is_float(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let value = &cx.typeck_results().expr_ty(expr).peel_refs().kind();
if let ty::Array(arr_ty, _) = value {
return matches!(arr_ty.kind(), ty::Float(_));
};
matches!(value, ty::Float(_))
}
fn is_array(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
matches!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _))
}

View file

@ -0,0 +1,71 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{match_def_path, paths, sugg};
use if_chain::if_chain;
use rustc_ast::util::parser::AssocOp;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::source_map::Spanned;
use super::FLOAT_EQUALITY_WITHOUT_ABS;
pub(crate) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
op: BinOpKind,
lhs: &'tcx Expr<'_>,
rhs: &'tcx Expr<'_>,
) {
let (lhs, rhs) = match op {
BinOpKind::Lt => (lhs, rhs),
BinOpKind::Gt => (rhs, lhs),
_ => return,
};
if_chain! {
// left hand side is a subtraction
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Sub,
..
},
val_l,
val_r,
) = lhs.kind;
// right hand side matches either f32::EPSILON or f64::EPSILON
if let ExprKind::Path(ref epsilon_path) = rhs.kind;
if let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id);
if match_def_path(cx, def_id, &paths::F32_EPSILON) || match_def_path(cx, def_id, &paths::F64_EPSILON);
// values of the subtractions on the left hand side are of the type float
let t_val_l = cx.typeck_results().expr_ty(val_l);
let t_val_r = cx.typeck_results().expr_ty(val_r);
if let ty::Float(_) = t_val_l.kind();
if let ty::Float(_) = t_val_r.kind();
then {
let sug_l = sugg::Sugg::hir(cx, val_l, "..");
let sug_r = sugg::Sugg::hir(cx, val_r, "..");
// format the suggestion
let suggestion = format!("{}.abs()", sugg::make_assoc(AssocOp::Subtract, &sug_l, &sug_r).maybe_par());
// spans the lint
span_lint_and_then(
cx,
FLOAT_EQUALITY_WITHOUT_ABS,
expr.span,
"float equality check without `.abs()`",
| diag | {
diag.span_suggestion(
lhs.span,
"add `.abs()`",
suggestion,
Applicability::MaybeIncorrect,
);
}
);
}
}
}

View file

@ -3,61 +3,40 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{clip, unsext};
use rustc_errors::Applicability;
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_hir::{BinOpKind, Expr, ExprKind, Node};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
declare_clippy_lint! {
/// ### What it does
/// Checks for identity operations, e.g., `x + 0`.
///
/// ### Why is this bad?
/// This code can be removed without changing the
/// meaning. So it just obscures what's going on. Delete it mercilessly.
///
/// ### Example
/// ```rust
/// # let x = 1;
/// x / 1 + 0 * 1 - 0 | 0;
/// ```
#[clippy::version = "pre 1.29.0"]
pub IDENTITY_OP,
complexity,
"using identity operations, e.g., `x + 0` or `y / 1`"
}
use super::IDENTITY_OP;
declare_lint_pass!(IdentityOp => [IDENTITY_OP]);
impl<'tcx> LateLintPass<'tcx> for IdentityOp {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if expr.span.from_expansion() {
return;
}
if let ExprKind::Binary(cmp, left, right) = &expr.kind {
if !is_allowed(cx, *cmp, left, right) {
match cmp.node {
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
check(cx, left, 0, expr.span, right.span, needs_parenthesis(cx, expr, right));
check(cx, right, 0, expr.span, left.span, Parens::Unneeded);
},
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
check(cx, right, 0, expr.span, left.span, Parens::Unneeded);
},
BinOpKind::Mul => {
check(cx, left, 1, expr.span, right.span, needs_parenthesis(cx, expr, right));
check(cx, right, 1, expr.span, left.span, Parens::Unneeded);
},
BinOpKind::Div => check(cx, right, 1, expr.span, left.span, Parens::Unneeded),
BinOpKind::BitAnd => {
check(cx, left, -1, expr.span, right.span, needs_parenthesis(cx, expr, right));
check(cx, right, -1, expr.span, left.span, Parens::Unneeded);
},
BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span),
_ => (),
}
}
pub(crate) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
op: BinOpKind,
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
) {
if !is_allowed(cx, op, left, right) {
match op {
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
check_op(cx, left, 0, expr.span, right.span, needs_parenthesis(cx, expr, right));
check_op(cx, right, 0, expr.span, left.span, Parens::Unneeded);
},
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
check_op(cx, right, 0, expr.span, left.span, Parens::Unneeded);
},
BinOpKind::Mul => {
check_op(cx, left, 1, expr.span, right.span, needs_parenthesis(cx, expr, right));
check_op(cx, right, 1, expr.span, left.span, Parens::Unneeded);
},
BinOpKind::Div => check_op(cx, right, 1, expr.span, left.span, Parens::Unneeded),
BinOpKind::BitAnd => {
check_op(cx, left, -1, expr.span, right.span, needs_parenthesis(cx, expr, right));
check_op(cx, right, -1, expr.span, left.span, Parens::Unneeded);
},
BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span),
_ => (),
}
}
}
@ -108,12 +87,12 @@ fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>)
Parens::Needed
}
fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> bool {
fn is_allowed(cx: &LateContext<'_>, cmp: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> bool {
// This lint applies to integers
!cx.typeck_results().expr_ty(left).peel_refs().is_integral()
|| !cx.typeck_results().expr_ty(right).peel_refs().is_integral()
// `1 << 0` is a common pattern in bit manipulation code
|| (cmp.node == BinOpKind::Shl
|| (cmp == BinOpKind::Shl
&& constant_simple(cx, cx.typeck_results(), right) == Some(Constant::Int(0))
&& constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1)))
}
@ -130,7 +109,7 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span
}
}
fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens) {
fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens) {
if let Some(Constant::Int(v)) = constant_simple(cx, cx.typeck_results(), e).map(Constant::peel_refs) {
let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() {
ty::Int(ity) => unsext(cx.tcx, -1_i128, ity),

View file

@ -0,0 +1,27 @@
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_hir as hir;
use rustc_lint::LateContext;
use super::INTEGER_DIVISION;
pub(crate) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'_>,
op: hir::BinOpKind,
left: &'tcx hir::Expr<'_>,
right: &'tcx hir::Expr<'_>,
) {
if op == hir::BinOpKind::Div
&& cx.typeck_results().expr_ty(left).is_integral()
&& cx.typeck_results().expr_ty(right).is_integral()
{
span_lint_and_help(
cx,
INTEGER_DIVISION,
expr.span,
"integer division",
None,
"division of integers may cause loss of precision. consider using floats",
);
}
}

View file

@ -0,0 +1,84 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::eq_expr_value;
use clippy_utils::source::snippet_opt;
use clippy_utils::sugg;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use super::MISREFACTORED_ASSIGN_OP;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'_>,
op: hir::BinOpKind,
lhs: &'tcx hir::Expr<'_>,
rhs: &'tcx hir::Expr<'_>,
) {
if let hir::ExprKind::Binary(binop, l, r) = &rhs.kind {
if op != binop.node {
return;
}
// lhs op= l op r
if eq_expr_value(cx, lhs, l) {
lint_misrefactored_assign_op(cx, expr, op, rhs, lhs, r);
}
// lhs op= l commutative_op r
if is_commutative(op) && eq_expr_value(cx, lhs, r) {
lint_misrefactored_assign_op(cx, expr, op, rhs, lhs, l);
}
}
}
fn lint_misrefactored_assign_op(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
op: hir::BinOpKind,
rhs: &hir::Expr<'_>,
assignee: &hir::Expr<'_>,
rhs_other: &hir::Expr<'_>,
) {
span_lint_and_then(
cx,
MISREFACTORED_ASSIGN_OP,
expr.span,
"variable appears on both sides of an assignment operation",
|diag| {
if let (Some(snip_a), Some(snip_r)) = (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs_other.span)) {
let a = &sugg::Sugg::hir(cx, assignee, "..");
let r = &sugg::Sugg::hir(cx, rhs, "..");
let long = format!("{} = {}", snip_a, sugg::make_binop(op.into(), a, r));
diag.span_suggestion(
expr.span,
&format!(
"did you mean `{} = {} {} {}` or `{}`? Consider replacing it with",
snip_a,
snip_a,
op.as_str(),
snip_r,
long
),
format!("{} {}= {}", snip_a, op.as_str(), snip_r),
Applicability::MaybeIncorrect,
);
diag.span_suggestion(
expr.span,
"or",
long,
Applicability::MaybeIncorrect, // snippet
);
}
},
);
}
#[must_use]
fn is_commutative(op: hir::BinOpKind) -> bool {
use rustc_hir::BinOpKind::{
Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
};
match op {
Add | Mul | And | Or | BitXor | BitAnd | BitOr | Eq | Ne => true,
Sub | Div | Rem | Shl | Shr | Lt | Le | Ge | Gt => false,
}
}

View file

@ -0,0 +1,849 @@
use rustc_hir::{Body, Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
mod absurd_extreme_comparisons;
mod assign_op_pattern;
mod bit_mask;
mod cmp_nan;
mod cmp_owned;
mod double_comparison;
mod duration_subsec;
mod eq_op;
mod erasing_op;
mod float_cmp;
mod float_equality_without_abs;
mod identity_op;
mod integer_division;
mod misrefactored_assign_op;
mod modulo_arithmetic;
mod modulo_one;
mod needless_bitwise_bool;
mod numeric_arithmetic;
mod op_ref;
mod ptr_eq;
mod self_assignment;
mod verbose_bit_mask;
declare_clippy_lint! {
/// ### What it does
/// Checks for comparisons where one side of the relation is
/// either the minimum or maximum value for its type and warns if it involves a
/// case that is always true or always false. Only integer and boolean types are
/// checked.
///
/// ### Why is this bad?
/// An expression like `min <= x` may misleadingly imply
/// that it is possible for `x` to be less than the minimum. Expressions like
/// `max < x` are probably mistakes.
///
/// ### Known problems
/// For `usize` the size of the current compile target will
/// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such
/// a comparison to detect target pointer width will trigger this lint. One can
/// use `mem::sizeof` and compare its value or conditional compilation
/// attributes
/// like `#[cfg(target_pointer_width = "64")] ..` instead.
///
/// ### Example
/// ```rust
/// let vec: Vec<isize> = Vec::new();
/// if vec.len() <= 0 {}
/// if 100 > i32::MAX {}
/// ```
#[clippy::version = "pre 1.29.0"]
pub ABSURD_EXTREME_COMPARISONS,
correctness,
"a comparison with a maximum or minimum value that is always true or false"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for integer arithmetic operations which could overflow or panic.
///
/// Specifically, checks for any operators (`+`, `-`, `*`, `<<`, etc) which are capable
/// of overflowing according to the [Rust
/// Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow),
/// or which can panic (`/`, `%`). No bounds analysis or sophisticated reasoning is
/// attempted.
///
/// ### Why is this bad?
/// Integer overflow will trigger a panic in debug builds or will wrap in
/// release mode. Division by zero will cause a panic in either mode. In some applications one
/// wants explicitly checked, wrapping or saturating arithmetic.
///
/// ### Example
/// ```rust
/// # let a = 0;
/// a + 1;
/// ```
#[clippy::version = "pre 1.29.0"]
pub INTEGER_ARITHMETIC,
restriction,
"any integer arithmetic expression which could overflow or panic"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for float arithmetic.
///
/// ### Why is this bad?
/// For some embedded systems or kernel development, it
/// can be useful to rule out floating-point numbers.
///
/// ### Example
/// ```rust
/// # let a = 0.0;
/// a + 1.0;
/// ```
#[clippy::version = "pre 1.29.0"]
pub FLOAT_ARITHMETIC,
restriction,
"any floating-point arithmetic statement"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `a = a op b` or `a = b commutative_op a`
/// patterns.
///
/// ### Why is this bad?
/// These can be written as the shorter `a op= b`.
///
/// ### Known problems
/// While forbidden by the spec, `OpAssign` traits may have
/// implementations that differ from the regular `Op` impl.
///
/// ### Example
/// ```rust
/// let mut a = 5;
/// let b = 0;
/// // ...
///
/// a = a + b;
/// ```
///
/// Use instead:
/// ```rust
/// let mut a = 5;
/// let b = 0;
/// // ...
///
/// a += b;
/// ```
#[clippy::version = "pre 1.29.0"]
pub ASSIGN_OP_PATTERN,
style,
"assigning the result of an operation on a variable to that same variable"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `a op= a op b` or `a op= b op a` patterns.
///
/// ### Why is this bad?
/// Most likely these are bugs where one meant to write `a
/// op= b`.
///
/// ### Known problems
/// Clippy cannot know for sure if `a op= a op b` should have
/// been `a = a op a op b` or `a = a op b`/`a op= b`. Therefore, it suggests both.
/// If `a op= a op b` is really the correct behavior it should be
/// written as `a = a op a op b` as it's less confusing.
///
/// ### Example
/// ```rust
/// let mut a = 5;
/// let b = 2;
/// // ...
/// a += a + b;
/// ```
#[clippy::version = "pre 1.29.0"]
pub MISREFACTORED_ASSIGN_OP,
suspicious,
"having a variable on both sides of an assign op"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for incompatible bit masks in comparisons.
///
/// The formula for detecting if an expression of the type `_ <bit_op> m
/// <cmp_op> c` (where `<bit_op>` is one of {`&`, `|`} and `<cmp_op>` is one of
/// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following
/// table:
///
/// |Comparison |Bit Op|Example |is always|Formula |
/// |------------|------|-------------|---------|----------------------|
/// |`==` or `!=`| `&` |`x & 2 == 3` |`false` |`c & m != c` |
/// |`<` or `>=`| `&` |`x & 2 < 3` |`true` |`m < c` |
/// |`>` or `<=`| `&` |`x & 1 > 1` |`false` |`m <= c` |
/// |`==` or `!=`| `\|` |`x \| 1 == 0`|`false` |`c \| m != c` |
/// |`<` or `>=`| `\|` |`x \| 1 < 1` |`false` |`m >= c` |
/// |`<=` or `>` | `\|` |`x \| 1 > 0` |`true` |`m > c` |
///
/// ### Why is this bad?
/// If the bits that the comparison cares about are always
/// set to zero or one by the bit mask, the comparison is constant `true` or
/// `false` (depending on mask, compared value, and operators).
///
/// So the code is actively misleading, and the only reason someone would write
/// this intentionally is to win an underhanded Rust contest or create a
/// test-case for this lint.
///
/// ### Example
/// ```rust
/// # let x = 1;
/// if (x & 1 == 2) { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub BAD_BIT_MASK,
correctness,
"expressions of the form `_ & mask == select` that will only ever return `true` or `false`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for bit masks in comparisons which can be removed
/// without changing the outcome. The basic structure can be seen in the
/// following table:
///
/// |Comparison| Bit Op |Example |equals |
/// |----------|----------|------------|-------|
/// |`>` / `<=`|`\|` / `^`|`x \| 2 > 3`|`x > 3`|
/// |`<` / `>=`|`\|` / `^`|`x ^ 1 < 4` |`x < 4`|
///
/// ### Why is this bad?
/// Not equally evil as [`bad_bit_mask`](#bad_bit_mask),
/// but still a bit misleading, because the bit mask is ineffective.
///
/// ### Known problems
/// False negatives: This lint will only match instances
/// where we have figured out the math (which is for a power-of-two compared
/// value). This means things like `x | 1 >= 7` (which would be better written
/// as `x >= 6`) will not be reported (but bit masks like this are fairly
/// uncommon).
///
/// ### Example
/// ```rust
/// # let x = 1;
/// if (x | 1 > 3) { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub INEFFECTIVE_BIT_MASK,
correctness,
"expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for bit masks that can be replaced by a call
/// to `trailing_zeros`
///
/// ### Why is this bad?
/// `x.trailing_zeros() > 4` is much clearer than `x & 15
/// == 0`
///
/// ### Known problems
/// llvm generates better code for `x & 15 == 0` on x86
///
/// ### Example
/// ```rust
/// # let x = 1;
/// if x & 0b1111 == 0 { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub VERBOSE_BIT_MASK,
pedantic,
"expressions where a bit mask is less readable than the corresponding method call"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for double comparisons that could be simplified to a single expression.
///
///
/// ### Why is this bad?
/// Readability.
///
/// ### Example
/// ```rust
/// # let x = 1;
/// # let y = 2;
/// if x == y || x < y {}
/// ```
///
/// Use instead:
///
/// ```rust
/// # let x = 1;
/// # let y = 2;
/// if x <= y {}
/// ```
#[clippy::version = "pre 1.29.0"]
pub DOUBLE_COMPARISONS,
complexity,
"unnecessary double comparisons that can be simplified"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for calculation of subsecond microseconds or milliseconds
/// from other `Duration` methods.
///
/// ### Why is this bad?
/// It's more concise to call `Duration::subsec_micros()` or
/// `Duration::subsec_millis()` than to calculate them.
///
/// ### Example
/// ```rust
/// # use std::time::Duration;
/// # let duration = Duration::new(5, 0);
/// let micros = duration.subsec_nanos() / 1_000;
/// let millis = duration.subsec_nanos() / 1_000_000;
/// ```
///
/// Use instead:
/// ```rust
/// # use std::time::Duration;
/// # let duration = Duration::new(5, 0);
/// let micros = duration.subsec_micros();
/// let millis = duration.subsec_millis();
/// ```
#[clippy::version = "pre 1.29.0"]
pub DURATION_SUBSEC,
complexity,
"checks for calculation of subsecond microseconds or milliseconds"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for equal operands to comparison, logical and
/// bitwise, difference and division binary operators (`==`, `>`, etc., `&&`,
/// `||`, `&`, `|`, `^`, `-` and `/`).
///
/// ### Why is this bad?
/// This is usually just a typo or a copy and paste error.
///
/// ### Known problems
/// False negatives: We had some false positives regarding
/// calls (notably [racer](https://github.com/phildawes/racer) had one instance
/// of `x.pop() && x.pop()`), so we removed matching any function or method
/// calls. We may introduce a list of known pure functions in the future.
///
/// ### Example
/// ```rust
/// # let x = 1;
/// if x + 1 == x + 1 {}
///
/// // or
///
/// # let a = 3;
/// # let b = 4;
/// assert_eq!(a, a);
/// ```
#[clippy::version = "pre 1.29.0"]
pub EQ_OP,
correctness,
"equal operands on both sides of a comparison or bitwise combination (e.g., `x == x`)"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for arguments to `==` which have their address
/// taken to satisfy a bound
/// and suggests to dereference the other argument instead
///
/// ### Why is this bad?
/// It is more idiomatic to dereference the other argument.
///
/// ### Example
/// ```rust,ignore
/// &x == y
/// ```
///
/// Use instead:
/// ```rust,ignore
/// x == *y
/// ```
#[clippy::version = "pre 1.29.0"]
pub OP_REF,
style,
"taking a reference to satisfy the type constraints on `==`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for erasing operations, e.g., `x * 0`.
///
/// ### Why is this bad?
/// The whole expression can be replaced by zero.
/// This is most likely not the intended outcome and should probably be
/// corrected
///
/// ### Example
/// ```rust
/// let x = 1;
/// 0 / x;
/// 0 * x;
/// x & 0;
/// ```
#[clippy::version = "pre 1.29.0"]
pub ERASING_OP,
correctness,
"using erasing operations, e.g., `x * 0` or `y & 0`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for statements of the form `(a - b) < f32::EPSILON` or
/// `(a - b) < f64::EPSILON`. Notes the missing `.abs()`.
///
/// ### Why is this bad?
/// The code without `.abs()` is more likely to have a bug.
///
/// ### Known problems
/// If the user can ensure that b is larger than a, the `.abs()` is
/// technically unnecessary. However, it will make the code more robust and doesn't have any
/// large performance implications. If the abs call was deliberately left out for performance
/// reasons, it is probably better to state this explicitly in the code, which then can be done
/// with an allow.
///
/// ### Example
/// ```rust
/// pub fn is_roughly_equal(a: f32, b: f32) -> bool {
/// (a - b) < f32::EPSILON
/// }
/// ```
/// Use instead:
/// ```rust
/// pub fn is_roughly_equal(a: f32, b: f32) -> bool {
/// (a - b).abs() < f32::EPSILON
/// }
/// ```
#[clippy::version = "1.48.0"]
pub FLOAT_EQUALITY_WITHOUT_ABS,
suspicious,
"float equality check without `.abs()`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for identity operations, e.g., `x + 0`.
///
/// ### Why is this bad?
/// This code can be removed without changing the
/// meaning. So it just obscures what's going on. Delete it mercilessly.
///
/// ### Example
/// ```rust
/// # let x = 1;
/// x / 1 + 0 * 1 - 0 | 0;
/// ```
#[clippy::version = "pre 1.29.0"]
pub IDENTITY_OP,
complexity,
"using identity operations, e.g., `x + 0` or `y / 1`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for division of integers
///
/// ### Why is this bad?
/// When outside of some very specific algorithms,
/// integer division is very often a mistake because it discards the
/// remainder.
///
/// ### Example
/// ```rust
/// let x = 3 / 2;
/// println!("{}", x);
/// ```
///
/// Use instead:
/// ```rust
/// let x = 3f32 / 2f32;
/// println!("{}", x);
/// ```
#[clippy::version = "1.37.0"]
pub INTEGER_DIVISION,
restriction,
"integer division may cause loss of precision"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for comparisons to NaN.
///
/// ### Why is this bad?
/// NaN does not compare meaningfully to anything not
/// even itself so those comparisons are simply wrong.
///
/// ### Example
/// ```rust
/// # let x = 1.0;
/// if x == f32::NAN { }
/// ```
///
/// Use instead:
/// ```rust
/// # let x = 1.0f32;
/// if x.is_nan() { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub CMP_NAN,
correctness,
"comparisons to `NAN`, which will always return false, probably not intended"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for conversions to owned values just for the sake
/// of a comparison.
///
/// ### Why is this bad?
/// The comparison can operate on a reference, so creating
/// an owned value effectively throws it away directly afterwards, which is
/// needlessly consuming code and heap space.
///
/// ### Example
/// ```rust
/// # let x = "foo";
/// # let y = String::from("foo");
/// if x.to_owned() == y {}
/// ```
///
/// Use instead:
/// ```rust
/// # let x = "foo";
/// # let y = String::from("foo");
/// if x == y {}
/// ```
#[clippy::version = "pre 1.29.0"]
pub CMP_OWNED,
perf,
"creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for (in-)equality comparisons on floating-point
/// values (apart from zero), except in functions called `*eq*` (which probably
/// implement equality for a type involving floats).
///
/// ### Why is this bad?
/// Floating point calculations are usually imprecise, so
/// asking if two values are *exactly* equal is asking for trouble. For a good
/// guide on what to do, see [the floating point
/// guide](http://www.floating-point-gui.de/errors/comparison).
///
/// ### Example
/// ```rust
/// let x = 1.2331f64;
/// let y = 1.2332f64;
///
/// if y == 1.23f64 { }
/// if y != x {} // where both are floats
/// ```
///
/// Use instead:
/// ```rust
/// # let x = 1.2331f64;
/// # let y = 1.2332f64;
/// let error_margin = f64::EPSILON; // Use an epsilon for comparison
/// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
/// // let error_margin = std::f64::EPSILON;
/// if (y - 1.23f64).abs() < error_margin { }
/// if (y - x).abs() > error_margin { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub FLOAT_CMP,
pedantic,
"using `==` or `!=` on float values instead of comparing difference with an epsilon"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for (in-)equality comparisons on floating-point
/// value and constant, except in functions called `*eq*` (which probably
/// implement equality for a type involving floats).
///
/// ### Why is this bad?
/// Floating point calculations are usually imprecise, so
/// asking if two values are *exactly* equal is asking for trouble. For a good
/// guide on what to do, see [the floating point
/// guide](http://www.floating-point-gui.de/errors/comparison).
///
/// ### Example
/// ```rust
/// let x: f64 = 1.0;
/// const ONE: f64 = 1.00;
///
/// if x == ONE { } // where both are floats
/// ```
///
/// Use instead:
/// ```rust
/// # let x: f64 = 1.0;
/// # const ONE: f64 = 1.00;
/// let error_margin = f64::EPSILON; // Use an epsilon for comparison
/// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
/// // let error_margin = std::f64::EPSILON;
/// if (x - ONE).abs() < error_margin { }
/// ```
#[clippy::version = "pre 1.29.0"]
pub FLOAT_CMP_CONST,
restriction,
"using `==` or `!=` on float constants instead of comparing difference with an epsilon"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for getting the remainder of a division by one or minus
/// one.
///
/// ### Why is this bad?
/// The result for a divisor of one can only ever be zero; for
/// minus one it can cause panic/overflow (if the left operand is the minimal value of
/// the respective integer type) or results in zero. No one will write such code
/// deliberately, unless trying to win an Underhanded Rust Contest. Even for that
/// contest, it's probably a bad idea. Use something more underhanded.
///
/// ### Example
/// ```rust
/// # let x = 1;
/// let a = x % 1;
/// let a = x % -1;
/// ```
#[clippy::version = "pre 1.29.0"]
pub MODULO_ONE,
correctness,
"taking a number modulo +/-1, which can either panic/overflow or always returns 0"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for modulo arithmetic.
///
/// ### Why is this bad?
/// The results of modulo (%) operation might differ
/// depending on the language, when negative numbers are involved.
/// If you interop with different languages it might be beneficial
/// to double check all places that use modulo arithmetic.
///
/// For example, in Rust `17 % -3 = 2`, but in Python `17 % -3 = -1`.
///
/// ### Example
/// ```rust
/// let x = -17 % 3;
/// ```
#[clippy::version = "1.42.0"]
pub MODULO_ARITHMETIC,
restriction,
"any modulo arithmetic statement"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for uses of bitwise and/or operators between booleans, where performance may be improved by using
/// a lazy and.
///
/// ### Why is this bad?
/// The bitwise operators do not support short-circuiting, so it may hinder code performance.
/// Additionally, boolean logic "masked" as bitwise logic is not caught by lints like `unnecessary_fold`
///
/// ### Known problems
/// This lint evaluates only when the right side is determined to have no side effects. At this time, that
/// determination is quite conservative.
///
/// ### Example
/// ```rust
/// let (x,y) = (true, false);
/// if x & !y {} // where both x and y are booleans
/// ```
/// Use instead:
/// ```rust
/// let (x,y) = (true, false);
/// if x && !y {}
/// ```
#[clippy::version = "1.54.0"]
pub NEEDLESS_BITWISE_BOOL,
pedantic,
"Boolean expressions that use bitwise rather than lazy operators"
}
declare_clippy_lint! {
/// ### What it does
/// Use `std::ptr::eq` when applicable
///
/// ### Why is this bad?
/// `ptr::eq` can be used to compare `&T` references
/// (which coerce to `*const T` implicitly) by their address rather than
/// comparing the values they point to.
///
/// ### Example
/// ```rust
/// let a = &[1, 2, 3];
/// let b = &[1, 2, 3];
///
/// assert!(a as *const _ as usize == b as *const _ as usize);
/// ```
/// Use instead:
/// ```rust
/// let a = &[1, 2, 3];
/// let b = &[1, 2, 3];
///
/// assert!(std::ptr::eq(a, b));
/// ```
#[clippy::version = "1.49.0"]
pub PTR_EQ,
style,
"use `std::ptr::eq` when comparing raw pointers"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for explicit self-assignments.
///
/// ### Why is this bad?
/// Self-assignments are redundant and unlikely to be
/// intentional.
///
/// ### Known problems
/// If expression contains any deref coercions or
/// indexing operations they are assumed not to have any side effects.
///
/// ### Example
/// ```rust
/// struct Event {
/// x: i32,
/// }
///
/// fn copy_position(a: &mut Event, b: &Event) {
/// a.x = a.x;
/// }
/// ```
///
/// Should be:
/// ```rust
/// struct Event {
/// x: i32,
/// }
///
/// fn copy_position(a: &mut Event, b: &Event) {
/// a.x = b.x;
/// }
/// ```
#[clippy::version = "1.48.0"]
pub SELF_ASSIGNMENT,
correctness,
"explicit self-assignment"
}
pub struct Operators {
arithmetic_context: numeric_arithmetic::Context,
verbose_bit_mask_threshold: u64,
}
impl_lint_pass!(Operators => [
ABSURD_EXTREME_COMPARISONS,
INTEGER_ARITHMETIC,
FLOAT_ARITHMETIC,
ASSIGN_OP_PATTERN,
MISREFACTORED_ASSIGN_OP,
BAD_BIT_MASK,
INEFFECTIVE_BIT_MASK,
VERBOSE_BIT_MASK,
DOUBLE_COMPARISONS,
DURATION_SUBSEC,
EQ_OP,
OP_REF,
ERASING_OP,
FLOAT_EQUALITY_WITHOUT_ABS,
IDENTITY_OP,
INTEGER_DIVISION,
CMP_NAN,
CMP_OWNED,
FLOAT_CMP,
FLOAT_CMP_CONST,
MODULO_ONE,
MODULO_ARITHMETIC,
NEEDLESS_BITWISE_BOOL,
PTR_EQ,
SELF_ASSIGNMENT,
]);
impl Operators {
pub fn new(verbose_bit_mask_threshold: u64) -> Self {
Self {
arithmetic_context: numeric_arithmetic::Context::default(),
verbose_bit_mask_threshold,
}
}
}
impl<'tcx> LateLintPass<'tcx> for Operators {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
eq_op::check_assert(cx, e);
match e.kind {
ExprKind::Binary(op, lhs, rhs) => {
if !e.span.from_expansion() {
absurd_extreme_comparisons::check(cx, e, op.node, lhs, rhs);
if !(macro_with_not_op(lhs) || macro_with_not_op(rhs)) {
eq_op::check(cx, e, op.node, lhs, rhs);
op_ref::check(cx, e, op.node, lhs, rhs);
}
erasing_op::check(cx, e, op.node, lhs, rhs);
identity_op::check(cx, e, op.node, lhs, rhs);
needless_bitwise_bool::check(cx, e, op.node, lhs, rhs);
ptr_eq::check(cx, e, op.node, lhs, rhs);
}
self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
bit_mask::check(cx, e, op.node, lhs, rhs);
verbose_bit_mask::check(cx, e, op.node, lhs, rhs, self.verbose_bit_mask_threshold);
double_comparison::check(cx, op.node, lhs, rhs, e.span);
duration_subsec::check(cx, e, op.node, lhs, rhs);
float_equality_without_abs::check(cx, e, op.node, lhs, rhs);
integer_division::check(cx, e, op.node, lhs, rhs);
cmp_nan::check(cx, e, op.node, lhs, rhs);
cmp_owned::check(cx, op.node, lhs, rhs);
float_cmp::check(cx, e, op.node, lhs, rhs);
modulo_one::check(cx, e, op.node, rhs);
modulo_arithmetic::check(cx, e, op.node, lhs, rhs);
},
ExprKind::AssignOp(op, lhs, rhs) => {
self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
misrefactored_assign_op::check(cx, e, op.node, lhs, rhs);
modulo_arithmetic::check(cx, e, op.node, lhs, rhs);
},
ExprKind::Assign(lhs, rhs, _) => {
assign_op_pattern::check(cx, e, lhs, rhs);
self_assignment::check(cx, e, lhs, rhs);
},
ExprKind::Unary(op, arg) => {
if op == UnOp::Neg {
self.arithmetic_context.check_negate(cx, e, arg);
}
},
_ => (),
}
}
fn check_expr_post(&mut self, _: &LateContext<'_>, e: &Expr<'_>) {
self.arithmetic_context.expr_post(e.hir_id);
}
fn check_body(&mut self, cx: &LateContext<'tcx>, b: &'tcx Body<'_>) {
self.arithmetic_context.enter_body(cx, b);
}
fn check_body_post(&mut self, cx: &LateContext<'tcx>, b: &'tcx Body<'_>) {
self.arithmetic_context.body_post(cx, b);
}
}
fn macro_with_not_op(e: &Expr<'_>) -> bool {
if let ExprKind::Unary(_, e) = e.kind {
e.span.from_expansion()
} else {
false
}
}

View file

@ -2,35 +2,35 @@ use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sext;
use if_chain::if_chain;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_hir::{BinOpKind, Expr};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use std::fmt::Display;
declare_clippy_lint! {
/// ### What it does
/// Checks for modulo arithmetic.
///
/// ### Why is this bad?
/// The results of modulo (%) operation might differ
/// depending on the language, when negative numbers are involved.
/// If you interop with different languages it might be beneficial
/// to double check all places that use modulo arithmetic.
///
/// For example, in Rust `17 % -3 = 2`, but in Python `17 % -3 = -1`.
///
/// ### Example
/// ```rust
/// let x = -17 % 3;
/// ```
#[clippy::version = "1.42.0"]
pub MODULO_ARITHMETIC,
restriction,
"any modulo arithmetic statement"
}
use super::MODULO_ARITHMETIC;
declare_lint_pass!(ModuloArithmetic => [MODULO_ARITHMETIC]);
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
op: BinOpKind,
lhs: &'tcx Expr<'_>,
rhs: &'tcx Expr<'_>,
) {
if op == BinOpKind::Rem {
let lhs_operand = analyze_operand(lhs, cx, e);
let rhs_operand = analyze_operand(rhs, cx, e);
if_chain! {
if let Some(lhs_operand) = lhs_operand;
if let Some(rhs_operand) = rhs_operand;
then {
check_const_operands(cx, e, &lhs_operand, &rhs_operand);
}
else {
check_non_const_operands(cx, e, lhs);
}
}
};
}
struct OperandInfo {
string_representation: Option<String>,
@ -124,27 +124,3 @@ fn check_non_const_operands<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
);
}
}
impl<'tcx> LateLintPass<'tcx> for ModuloArithmetic {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
match &expr.kind {
ExprKind::Binary(op, lhs, rhs) | ExprKind::AssignOp(op, lhs, rhs) => {
if op.node == BinOpKind::Rem {
let lhs_operand = analyze_operand(lhs, cx, expr);
let rhs_operand = analyze_operand(rhs, cx, expr);
if_chain! {
if let Some(lhs_operand) = lhs_operand;
if let Some(rhs_operand) = rhs_operand;
then {
check_const_operands(cx, expr, &lhs_operand, &rhs_operand);
}
else {
check_non_const_operands(cx, expr, lhs);
}
}
};
},
_ => {},
}
}
}

View file

@ -0,0 +1,26 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{is_integer_const, unsext};
use rustc_hir::{BinOpKind, Expr};
use rustc_lint::LateContext;
use rustc_middle::ty;
use super::MODULO_ONE;
pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, right: &Expr<'_>) {
if op == BinOpKind::Rem {
if is_integer_const(cx, right, 1) {
span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0");
}
if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() {
if is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) {
span_lint(
cx,
MODULO_ONE,
expr.span,
"any number modulo -1 will panic/overflow or result in 0",
);
}
};
}
}

View file

@ -0,0 +1,36 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use super::NEEDLESS_BITWISE_BOOL;
pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>) {
let op_str = match op {
BinOpKind::BitAnd => "&&",
BinOpKind::BitOr => "||",
_ => return,
};
if matches!(
rhs.kind,
ExprKind::Call(..) | ExprKind::MethodCall(..) | ExprKind::Binary(..) | ExprKind::Unary(..)
) && cx.typeck_results().expr_ty(e).is_bool()
&& !rhs.can_have_side_effects()
{
span_lint_and_then(
cx,
NEEDLESS_BITWISE_BOOL,
e.span,
"use of bitwise operator instead of lazy operator between booleans",
|diag| {
if let Some(lhs_snip) = snippet_opt(cx, lhs.span)
&& let Some(rhs_snip) = snippet_opt(cx, rhs.span)
{
let sugg = format!("{} {} {}", lhs_snip, op_str, rhs_snip);
diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable);
}
},
);
}
}

View file

@ -0,0 +1,127 @@
use clippy_utils::consts::constant_simple;
use clippy_utils::diagnostics::span_lint;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::source_map::Span;
use super::{FLOAT_ARITHMETIC, INTEGER_ARITHMETIC};
#[derive(Default)]
pub struct Context {
expr_id: Option<hir::HirId>,
/// This field is used to check whether expressions are constants, such as in enum discriminants
/// and consts
const_span: Option<Span>,
}
impl Context {
fn skip_expr(&mut self, e: &hir::Expr<'_>) -> bool {
self.expr_id.is_some() || self.const_span.map_or(false, |span| span.contains(e.span))
}
pub fn check_binary<'tcx>(
&mut self,
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'_>,
op: hir::BinOpKind,
l: &'tcx hir::Expr<'_>,
r: &'tcx hir::Expr<'_>,
) {
if self.skip_expr(expr) {
return;
}
match op {
hir::BinOpKind::And
| hir::BinOpKind::Or
| hir::BinOpKind::BitAnd
| hir::BinOpKind::BitOr
| hir::BinOpKind::BitXor
| hir::BinOpKind::Eq
| hir::BinOpKind::Lt
| hir::BinOpKind::Le
| hir::BinOpKind::Ne
| hir::BinOpKind::Ge
| hir::BinOpKind::Gt => return,
_ => (),
}
let (l_ty, r_ty) = (cx.typeck_results().expr_ty(l), cx.typeck_results().expr_ty(r));
if l_ty.peel_refs().is_integral() && r_ty.peel_refs().is_integral() {
match op {
hir::BinOpKind::Div | hir::BinOpKind::Rem => match &r.kind {
hir::ExprKind::Lit(_lit) => (),
hir::ExprKind::Unary(hir::UnOp::Neg, expr) => {
if let hir::ExprKind::Lit(lit) = &expr.kind {
if let rustc_ast::ast::LitKind::Int(1, _) = lit.node {
span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
self.expr_id = Some(expr.hir_id);
}
}
},
_ => {
span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
self.expr_id = Some(expr.hir_id);
},
},
_ => {
span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
self.expr_id = Some(expr.hir_id);
},
}
} else if r_ty.peel_refs().is_floating_point() && r_ty.peel_refs().is_floating_point() {
span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
self.expr_id = Some(expr.hir_id);
}
}
pub fn check_negate<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) {
if self.skip_expr(expr) {
return;
}
let ty = cx.typeck_results().expr_ty(arg);
if constant_simple(cx, cx.typeck_results(), expr).is_none() {
if ty.is_integral() {
span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
self.expr_id = Some(expr.hir_id);
} else if ty.is_floating_point() {
span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
self.expr_id = Some(expr.hir_id);
}
}
}
pub fn expr_post(&mut self, id: hir::HirId) {
if Some(id) == self.expr_id {
self.expr_id = None;
}
}
pub fn enter_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
let body_owner = cx.tcx.hir().body_owner_def_id(body.id());
match cx.tcx.hir().body_owner_kind(body_owner) {
hir::BodyOwnerKind::Static(_) | hir::BodyOwnerKind::Const => {
let body_span = cx.tcx.def_span(body_owner);
if let Some(span) = self.const_span {
if span.contains(body_span) {
return;
}
}
self.const_span = Some(body_span);
},
hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => (),
}
}
pub fn body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
let body_owner = cx.tcx.hir().body_owner(body.id());
let body_span = cx.tcx.hir().span(body_owner);
if let Some(span) = self.const_span {
if span.contains(body_span) {
return;
}
}
self.const_span = None;
}
}

View file

@ -0,0 +1,218 @@
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::get_enclosing_block;
use clippy_utils::source::snippet;
use clippy_utils::ty::{implements_trait, is_copy};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{def::Res, def_id::DefId, BinOpKind, BorrowKind, Expr, ExprKind, GenericArg, ItemKind, QPath, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use super::OP_REF;
#[expect(clippy::similar_names, clippy::too_many_lines)]
pub(crate) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
op: BinOpKind,
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
) {
let (trait_id, requires_ref) = match op {
BinOpKind::Add => (cx.tcx.lang_items().add_trait(), false),
BinOpKind::Sub => (cx.tcx.lang_items().sub_trait(), false),
BinOpKind::Mul => (cx.tcx.lang_items().mul_trait(), false),
BinOpKind::Div => (cx.tcx.lang_items().div_trait(), false),
BinOpKind::Rem => (cx.tcx.lang_items().rem_trait(), false),
// don't lint short circuiting ops
BinOpKind::And | BinOpKind::Or => return,
BinOpKind::BitXor => (cx.tcx.lang_items().bitxor_trait(), false),
BinOpKind::BitAnd => (cx.tcx.lang_items().bitand_trait(), false),
BinOpKind::BitOr => (cx.tcx.lang_items().bitor_trait(), false),
BinOpKind::Shl => (cx.tcx.lang_items().shl_trait(), false),
BinOpKind::Shr => (cx.tcx.lang_items().shr_trait(), false),
BinOpKind::Ne | BinOpKind::Eq => (cx.tcx.lang_items().eq_trait(), true),
BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ge | BinOpKind::Gt => {
(cx.tcx.lang_items().partial_ord_trait(), true)
},
};
if let Some(trait_id) = trait_id {
match (&left.kind, &right.kind) {
// do not suggest to dereference literals
(&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {},
// &foo == &bar
(&ExprKind::AddrOf(BorrowKind::Ref, _, l), &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
let lty = cx.typeck_results().expr_ty(l);
let rty = cx.typeck_results().expr_ty(r);
let lcpy = is_copy(cx, lty);
let rcpy = is_copy(cx, rty);
if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) {
if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty))
|| (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))
{
return; // Don't lint
}
}
// either operator autorefs or both args are copyable
if (requires_ref || (lcpy && rcpy)) && implements_trait(cx, lty, trait_id, &[rty.into()]) {
span_lint_and_then(
cx,
OP_REF,
e.span,
"needlessly taken reference of both operands",
|diag| {
let lsnip = snippet(cx, l.span, "...").to_string();
let rsnip = snippet(cx, r.span, "...").to_string();
multispan_sugg(
diag,
"use the values directly",
vec![(left.span, lsnip), (right.span, rsnip)],
);
},
);
} else if lcpy
&& !rcpy
&& implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()])
{
span_lint_and_then(
cx,
OP_REF,
e.span,
"needlessly taken reference of left operand",
|diag| {
let lsnip = snippet(cx, l.span, "...").to_string();
diag.span_suggestion(
left.span,
"use the left value directly",
lsnip,
Applicability::MaybeIncorrect, // FIXME #2597
);
},
);
} else if !lcpy
&& rcpy
&& implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()])
{
span_lint_and_then(
cx,
OP_REF,
e.span,
"needlessly taken reference of right operand",
|diag| {
let rsnip = snippet(cx, r.span, "...").to_string();
diag.span_suggestion(
right.span,
"use the right value directly",
rsnip,
Applicability::MaybeIncorrect, // FIXME #2597
);
},
);
}
},
// &foo == bar
(&ExprKind::AddrOf(BorrowKind::Ref, _, l), _) => {
let lty = cx.typeck_results().expr_ty(l);
if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) {
let rty = cx.typeck_results().expr_ty(right);
if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty))
|| (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))
{
return; // Don't lint
}
}
let lcpy = is_copy(cx, lty);
if (requires_ref || lcpy)
&& implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()])
{
span_lint_and_then(
cx,
OP_REF,
e.span,
"needlessly taken reference of left operand",
|diag| {
let lsnip = snippet(cx, l.span, "...").to_string();
diag.span_suggestion(
left.span,
"use the left value directly",
lsnip,
Applicability::MaybeIncorrect, // FIXME #2597
);
},
);
}
},
// foo == &bar
(_, &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
let rty = cx.typeck_results().expr_ty(r);
if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) {
let lty = cx.typeck_results().expr_ty(left);
if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty))
|| (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))
{
return; // Don't lint
}
}
let rcpy = is_copy(cx, rty);
if (requires_ref || rcpy)
&& implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()])
{
span_lint_and_then(cx, OP_REF, e.span, "taken reference of right operand", |diag| {
let rsnip = snippet(cx, r.span, "...").to_string();
diag.span_suggestion(
right.span,
"use the right value directly",
rsnip,
Applicability::MaybeIncorrect, // FIXME #2597
);
});
}
},
_ => {},
}
}
}
fn in_impl<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
bin_op: DefId,
) -> Option<(&'tcx rustc_hir::Ty<'tcx>, &'tcx rustc_hir::Ty<'tcx>)> {
if_chain! {
if let Some(block) = get_enclosing_block(cx, e.hir_id);
if let Some(impl_def_id) = cx.tcx.impl_of_method(block.hir_id.owner.to_def_id());
let item = cx.tcx.hir().expect_item(impl_def_id.expect_local());
if let ItemKind::Impl(item) = &item.kind;
if let Some(of_trait) = &item.of_trait;
if let Some(seg) = of_trait.path.segments.last();
if let Some(Res::Def(_, trait_id)) = seg.res;
if trait_id == bin_op;
if let Some(generic_args) = seg.args;
if let Some(GenericArg::Type(other_ty)) = generic_args.args.last();
then {
Some((item.self_ty, other_ty))
}
else {
None
}
}
}
fn are_equal<'tcx>(cx: &LateContext<'tcx>, middle_ty: Ty<'_>, hir_ty: &rustc_hir::Ty<'_>) -> bool {
if_chain! {
if let ty::Adt(adt_def, _) = middle_ty.kind();
if let Some(local_did) = adt_def.did().as_local();
let item = cx.tcx.hir().expect_item(local_did);
let middle_ty_id = item.def_id.to_def_id();
if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind;
if let Res::Def(_, hir_ty_id) = path.res;
then {
hir_ty_id == middle_ty_id
}
else {
false
}
}
}

View file

@ -0,0 +1,65 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use super::PTR_EQ;
static LINT_MSG: &str = "use `std::ptr::eq` when comparing raw pointers";
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
op: BinOpKind,
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
) {
if BinOpKind::Eq == op {
let (left, right) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) {
(Some(lhs), Some(rhs)) => (lhs, rhs),
_ => (left, right),
};
if_chain! {
if let Some(left_var) = expr_as_cast_to_raw_pointer(cx, left);
if let Some(right_var) = expr_as_cast_to_raw_pointer(cx, right);
if let Some(left_snip) = snippet_opt(cx, left_var.span);
if let Some(right_snip) = snippet_opt(cx, right_var.span);
then {
span_lint_and_sugg(
cx,
PTR_EQ,
expr.span,
LINT_MSG,
"try",
format!("std::ptr::eq({}, {})", left_snip, right_snip),
Applicability::MachineApplicable,
);
}
}
}
}
// If the given expression is a cast to a usize, return the lhs of the cast
// E.g., `foo as *const _ as usize` returns `foo as *const _`.
fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize {
if let ExprKind::Cast(expr, _) = cast_expr.kind {
return Some(expr);
}
}
None
}
// If the given expression is a cast to a `*const` pointer, return the lhs of the cast
// E.g., `foo as *const _` returns `foo`.
fn expr_as_cast_to_raw_pointer<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if cx.typeck_results().expr_ty(cast_expr).is_unsafe_ptr() {
if let ExprKind::Cast(expr, _) = cast_expr.kind {
return Some(expr);
}
}
None
}

View file

@ -0,0 +1,20 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::eq_expr_value;
use clippy_utils::source::snippet;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use super::SELF_ASSIGNMENT;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>) {
if eq_expr_value(cx, lhs, rhs) {
let lhs = snippet(cx, lhs.span, "<lhs>");
let rhs = snippet(cx, rhs.span, "<rhs>");
span_lint(
cx,
SELF_ASSIGNMENT,
e.span,
&format!("self-assignment of `{}` to `{}`", rhs, lhs),
);
}
}

View file

@ -0,0 +1,44 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::Sugg;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use super::VERBOSE_BIT_MASK;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
op: BinOpKind,
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
threshold: u64,
) {
if BinOpKind::Eq == op
&& let ExprKind::Binary(op1, left1, right1) = &left.kind
&& BinOpKind::BitAnd == op1.node
&& let ExprKind::Lit(lit) = &right1.kind
&& let LitKind::Int(n, _) = lit.node
&& let ExprKind::Lit(lit1) = &right.kind
&& let LitKind::Int(0, _) = lit1.node
&& n.leading_zeros() == n.count_zeros()
&& n > u128::from(threshold)
{
span_lint_and_then(
cx,
VERBOSE_BIT_MASK,
e.span,
"bit mask could be simplified with a call to `trailing_zeros`",
|diag| {
let sugg = Sugg::hir(cx, left1, "...").maybe_par();
diag.span_suggestion(
e.span,
"try",
format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()),
Applicability::MaybeIncorrect,
);
},
);
}
}

View file

@ -3,17 +3,20 @@ use std::iter;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_copy;
use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy};
use clippy_utils::{is_self, is_self_ty};
use core::ops::ControlFlow;
use if_chain::if_chain;
use rustc_ast::attr;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_middle::ty::adjustment::{Adjust, PointerCast};
use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::{self, RegionKind};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::LocalDefId;
use rustc_span::{sym, Span};
@ -141,50 +144,76 @@ impl<'tcx> PassByRefOrValue {
}
let fn_sig = cx.tcx.fn_sig(def_id);
let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);
let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id));
for (index, (input, &ty)) in iter::zip(decl.inputs, fn_sig.inputs()).enumerate() {
// Gather all the lifetimes found in the output type which may affect whether
// `TRIVIALLY_COPY_PASS_BY_REF` should be linted.
let mut output_regions = FxHashSet::default();
for_each_top_level_late_bound_region(fn_sig.skip_binder().output(), |region| -> ControlFlow<!> {
output_regions.insert(region);
ControlFlow::Continue(())
});
for (index, (input, ty)) in iter::zip(
decl.inputs,
fn_sig.skip_binder().inputs().iter().map(|&ty| fn_sig.rebind(ty)),
)
.enumerate()
{
// All spans generated from a proc-macro invocation are the same...
match span {
Some(s) if s == input.span => return,
Some(s) if s == input.span => continue,
_ => (),
}
match ty.kind() {
ty::Ref(input_lt, ty, Mutability::Not) => {
// Use lifetimes to determine if we're returning a reference to the
// argument. In that case we can't switch to pass-by-value as the
// argument will not live long enough.
let output_lts = match *fn_sig.output().kind() {
ty::Ref(output_lt, _, _) => vec![output_lt],
ty::Adt(_, substs) => substs.regions().collect(),
_ => vec![],
};
match *ty.skip_binder().kind() {
ty::Ref(lt, ty, Mutability::Not) => {
match lt.kind() {
RegionKind::ReLateBound(index, region)
if index.as_u32() == 0 && output_regions.contains(&region) =>
{
continue;
},
// Early bound regions on functions are either from the containing item, are bounded by another
// lifetime, or are used as a bound for a type or lifetime.
RegionKind::ReEarlyBound(..) => continue,
_ => (),
}
if_chain! {
if !output_lts.contains(input_lt);
if is_copy(cx, *ty);
if let Some(size) = cx.layout_of(*ty).ok().map(|l| l.size.bytes());
if size <= self.ref_min_size;
if let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind;
then {
let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
"self".into()
} else {
snippet(cx, decl_ty.span, "_").into()
};
span_lint_and_sugg(
cx,
TRIVIALLY_COPY_PASS_BY_REF,
input.span,
&format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
"consider passing by value instead",
value_type,
Applicability::Unspecified,
);
let ty = cx.tcx.erase_late_bound_regions(fn_sig.rebind(ty));
if is_copy(cx, ty)
&& let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes())
&& size <= self.ref_min_size
&& let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind
{
if let Some(typeck) = cx.maybe_typeck_results() {
// Don't lint if an unsafe pointer is created.
// TODO: Limit the check only to unsafe pointers to the argument (or part of the argument)
// which escape the current function.
if typeck.node_types().iter().any(|(_, &ty)| ty.is_unsafe_ptr())
|| typeck
.adjustments()
.iter()
.flat_map(|(_, a)| a)
.any(|a| matches!(a.kind, Adjust::Pointer(PointerCast::UnsafeFnPointer)))
{
continue;
}
}
let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
"self".into()
} else {
snippet(cx, decl_ty.span, "_").into()
};
span_lint_and_sugg(
cx,
TRIVIALLY_COPY_PASS_BY_REF,
input.span,
&format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
"consider passing by value instead",
value_type,
Applicability::Unspecified,
);
}
},
@ -196,6 +225,7 @@ impl<'tcx> PassByRefOrValue {
_ => continue,
}
}
let ty = cx.tcx.erase_late_bound_regions(ty);
if_chain! {
if is_copy(cx, ty);

View file

@ -1,6 +1,6 @@
//! Checks for usage of `&Vec[_]` and `&String`.
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::expr_sig;
use clippy_utils::visitors::contains_unsafe_block;
@ -166,15 +166,14 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
)
.filter(|arg| arg.mutability() == Mutability::Not)
{
span_lint_and_sugg(
cx,
PTR_ARG,
arg.span,
&arg.build_msg(),
"change this to",
format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)),
Applicability::Unspecified,
);
span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, &arg.build_msg(), |diag| {
diag.span_suggestion(
arg.span,
"change this to",
format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)),
Applicability::Unspecified,
);
});
}
}
}
@ -221,7 +220,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
let results = check_ptr_arg_usage(cx, body, &lint_args);
for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
span_lint_and_then(cx, PTR_ARG, args.span, &args.build_msg(), |diag| {
span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, &args.build_msg(), |diag| {
diag.multipart_suggestion(
"change this to",
iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx))))
@ -315,6 +314,7 @@ struct PtrArgReplacement {
struct PtrArg<'tcx> {
idx: usize,
emission_id: hir::HirId,
span: Span,
ty_did: DefId,
ty_name: Symbol,
@ -419,10 +419,8 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
if let [.., name] = path.segments;
if cx.tcx.item_name(adt.did()) == name.ident.name;
if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id);
if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id));
then {
let emission_id = params.get(i).map_or(hir_ty.hir_id, |param| param.hir_id);
let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) {
Some(sym::Vec) => (
[("clone", ".to_owned()")].as_slice(),
@ -455,14 +453,20 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
})
.and_then(|arg| snippet_opt(cx, arg.span))
.unwrap_or_else(|| substs.type_at(1).to_string());
span_lint_and_sugg(
span_lint_hir_and_then(
cx,
PTR_ARG,
emission_id,
hir_ty.span,
"using a reference to `Cow` is not recommended",
"change this to",
format!("&{}{}", mutability.prefix_str(), ty_name),
Applicability::Unspecified,
|diag| {
diag.span_suggestion(
hir_ty.span,
"change this to",
format!("&{}{}", mutability.prefix_str(), ty_name),
Applicability::Unspecified,
);
}
);
return None;
},
@ -470,6 +474,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
};
return Some(PtrArg {
idx: i,
emission_id,
span: hir_ty.span,
ty_did: adt.did(),
ty_name: name.ident.name,
@ -574,14 +579,13 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args:
Some((Node::Expr(e), child_id)) => match e.kind {
ExprKind::Call(f, expr_args) => {
let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
if expr_sig(self.cx, f)
.map(|sig| sig.input(i).skip_binder().peel_refs())
.map_or(true, |ty| match *ty.kind() {
if expr_sig(self.cx, f).and_then(|sig| sig.input(i)).map_or(true, |ty| {
match *ty.skip_binder().peel_refs().kind() {
ty::Param(_) => true,
ty::Adt(def, _) => def.did() == args.ty_did,
_ => false,
})
{
}
}) {
// Passed to a function taking the non-dereferenced type.
set_skip_flag();
}

View file

@ -1,97 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Use `std::ptr::eq` when applicable
///
/// ### Why is this bad?
/// `ptr::eq` can be used to compare `&T` references
/// (which coerce to `*const T` implicitly) by their address rather than
/// comparing the values they point to.
///
/// ### Example
/// ```rust
/// let a = &[1, 2, 3];
/// let b = &[1, 2, 3];
///
/// assert!(a as *const _ as usize == b as *const _ as usize);
/// ```
/// Use instead:
/// ```rust
/// let a = &[1, 2, 3];
/// let b = &[1, 2, 3];
///
/// assert!(std::ptr::eq(a, b));
/// ```
#[clippy::version = "1.49.0"]
pub PTR_EQ,
style,
"use `std::ptr::eq` when comparing raw pointers"
}
declare_lint_pass!(PtrEq => [PTR_EQ]);
static LINT_MSG: &str = "use `std::ptr::eq` when comparing raw pointers";
impl<'tcx> LateLintPass<'tcx> for PtrEq {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if expr.span.from_expansion() {
return;
}
if let ExprKind::Binary(ref op, left, right) = expr.kind {
if BinOpKind::Eq == op.node {
let (left, right) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) {
(Some(lhs), Some(rhs)) => (lhs, rhs),
_ => (left, right),
};
if_chain! {
if let Some(left_var) = expr_as_cast_to_raw_pointer(cx, left);
if let Some(right_var) = expr_as_cast_to_raw_pointer(cx, right);
if let Some(left_snip) = snippet_opt(cx, left_var.span);
if let Some(right_snip) = snippet_opt(cx, right_var.span);
then {
span_lint_and_sugg(
cx,
PTR_EQ,
expr.span,
LINT_MSG,
"try",
format!("std::ptr::eq({}, {})", left_snip, right_snip),
Applicability::MachineApplicable,
);
}
}
}
}
}
}
// If the given expression is a cast to a usize, return the lhs of the cast
// E.g., `foo as *const _ as usize` returns `foo as *const _`.
fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize {
if let ExprKind::Cast(expr, _) = cast_expr.kind {
return Some(expr);
}
}
None
}
// If the given expression is a cast to a `*const` pointer, return the lhs of the cast
// E.g., `foo as *const _` returns `foo`.
fn expr_as_cast_to_raw_pointer<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if cx.typeck_results().expr_ty(cast_expr).is_unsafe_ptr() {
if let ExprKind::Cast(expr, _) = cast_expr.kind {
return Some(expr);
}
}
None
}

View file

@ -396,7 +396,7 @@ fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args:
// `.iter()` and `.len()` called on same `Path`
if let ExprKind::Path(QPath::Resolved(_, iter_path)) = iter_args[0].kind;
if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments);
if SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments);
then {
span_lint(cx,
RANGE_ZIP_WITH_LEN,

View file

@ -87,7 +87,7 @@ impl RedundantStaticLifetimes {
_ => {},
}
}
self.visit_type(&*borrow_type.ty, cx, reason);
self.visit_type(&borrow_type.ty, cx, reason);
},
_ => {},
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::{snippet_opt, snippet_with_context};
use clippy_utils::{fn_def_id, path_to_local_id};
use if_chain::if_chain;
@ -94,9 +94,10 @@ impl<'tcx> LateLintPass<'tcx> for Return {
if !in_external_macro(cx.sess(), retexpr.span);
if !local.span.from_expansion();
then {
span_lint_and_then(
span_lint_hir_and_then(
cx,
LET_AND_RETURN,
retexpr.hir_id,
retexpr.span,
"returning the result of a `let` binding from a block",
|err| {
@ -185,6 +186,7 @@ fn check_final_expr<'tcx>(
if !borrows {
emit_return_lint(
cx,
inner.map_or(expr.hir_id, |inner| inner.hir_id),
span.expect("`else return` is not possible"),
inner.as_ref().map(|i| i.span),
replacement,
@ -220,50 +222,81 @@ fn check_final_expr<'tcx>(
}
}
fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option<Span>, replacement: RetReplacement) {
fn emit_return_lint(
cx: &LateContext<'_>,
emission_place: HirId,
ret_span: Span,
inner_span: Option<Span>,
replacement: RetReplacement,
) {
if ret_span.from_expansion() {
return;
}
match inner_span {
Some(inner_span) => {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
let (snippet, _) = snippet_with_context(cx, inner_span, ret_span.ctxt(), "..", &mut applicability);
diag.span_suggestion(ret_span, "remove `return`", snippet, applicability);
});
span_lint_hir_and_then(
cx,
NEEDLESS_RETURN,
emission_place,
ret_span,
"unneeded `return` statement",
|diag| {
let (snippet, _) = snippet_with_context(cx, inner_span, ret_span.ctxt(), "..", &mut applicability);
diag.span_suggestion(ret_span, "remove `return`", snippet, applicability);
},
);
},
None => match replacement {
RetReplacement::Empty => {
span_lint_and_sugg(
span_lint_hir_and_then(
cx,
NEEDLESS_RETURN,
emission_place,
ret_span,
"unneeded `return` statement",
"remove `return`",
String::new(),
Applicability::MachineApplicable,
|diag| {
diag.span_suggestion(
ret_span,
"remove `return`",
String::new(),
Applicability::MachineApplicable,
);
},
);
},
RetReplacement::Block => {
span_lint_and_sugg(
span_lint_hir_and_then(
cx,
NEEDLESS_RETURN,
emission_place,
ret_span,
"unneeded `return` statement",
"replace `return` with an empty block",
"{}".to_string(),
Applicability::MachineApplicable,
|diag| {
diag.span_suggestion(
ret_span,
"replace `return` with an empty block",
"{}".to_string(),
Applicability::MachineApplicable,
);
},
);
},
RetReplacement::Unit => {
span_lint_and_sugg(
span_lint_hir_and_then(
cx,
NEEDLESS_RETURN,
emission_place,
ret_span,
"unneeded `return` statement",
"replace `return` with a unit value",
"()".to_string(),
Applicability::MachineApplicable,
|diag| {
diag.span_suggestion(
ret_span,
"replace `return` with a unit value",
"()".to_string(),
Applicability::MachineApplicable,
);
},
);
},
},

View file

@ -55,11 +55,11 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
if matches!(cx.tcx.def_kind(id.def_id), DefKind::Impl)
&& let item = cx.tcx.hir().item(id)
&& let ItemKind::Impl(Impl {
items,
of_trait,
self_ty,
..
}) = &item.kind
items,
of_trait,
self_ty,
..
}) = &item.kind
&& let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind
{
if !map.contains_key(res) {

View file

@ -1,56 +0,0 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::eq_expr_value;
use clippy_utils::source::snippet;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks for explicit self-assignments.
///
/// ### Why is this bad?
/// Self-assignments are redundant and unlikely to be
/// intentional.
///
/// ### Known problems
/// If expression contains any deref coercions or
/// indexing operations they are assumed not to have any side effects.
///
/// ### Example
/// ```rust
/// struct Event {
/// id: usize,
/// x: i32,
/// y: i32,
/// }
///
/// fn copy_position(a: &mut Event, b: &Event) {
/// a.x = b.x;
/// a.y = a.y;
/// }
/// ```
#[clippy::version = "1.48.0"]
pub SELF_ASSIGNMENT,
correctness,
"explicit self-assignment"
}
declare_lint_pass!(SelfAssignment => [SELF_ASSIGNMENT]);
impl<'tcx> LateLintPass<'tcx> for SelfAssignment {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Assign(lhs, rhs, _) = &expr.kind {
if eq_expr_value(cx, lhs, rhs) {
let lhs = snippet(cx, lhs.span, "<lhs>");
let rhs = snippet(cx, rhs.span, "<rhs>");
span_lint(
cx,
SELF_ASSIGNMENT,
expr.span,
&format!("self-assignment of `{}` to `{}`", rhs, lhs),
);
}
}
}
}

View file

@ -26,6 +26,9 @@ declare_clippy_lint! {
/// let mut vec1 = Vec::with_capacity(len);
/// vec1.resize(len, 0);
///
/// let mut vec1 = Vec::with_capacity(len);
/// vec1.resize(vec1.capacity(), 0);
///
/// let mut vec2 = Vec::with_capacity(len);
/// vec2.extend(repeat(0).take(len));
/// ```
@ -211,23 +214,20 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
/// Checks if the given expression is resizing a vector with 0
fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) {
if_chain! {
if self.initialization_found;
if let ExprKind::MethodCall(path, [self_arg, len_arg, fill_arg], _) = expr.kind;
if path_to_local_id(self_arg, self.vec_alloc.local_id);
if path.ident.name == sym!(resize);
if self.initialization_found
&& let ExprKind::MethodCall(path, [self_arg, len_arg, fill_arg], _) = expr.kind
&& path_to_local_id(self_arg, self.vec_alloc.local_id)
&& path.ident.name == sym!(resize)
// Check that is filled with 0
if let ExprKind::Lit(ref lit) = fill_arg.kind;
if let LitKind::Int(0, _) = lit.node;
// Check that len expression is equals to `with_capacity` expression
if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr);
then {
self.slow_expression = Some(InitializationType::Resize(expr));
&& let ExprKind::Lit(ref lit) = fill_arg.kind
&& let LitKind::Int(0, _) = lit.node {
// Check that len expression is equals to `with_capacity` expression
if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) {
self.slow_expression = Some(InitializationType::Resize(expr));
} else if let ExprKind::MethodCall(path, _, _) = len_arg.kind && path.ident.as_str() == "capacity" {
self.slow_expression = Some(InitializationType::Resize(expr));
}
}
}
}
/// Returns `true` if give expression is `repeat(0).take(...)`
@ -240,12 +240,15 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
if let Some(repeat_expr) = take_args.get(0);
if self.is_repeat_zero(repeat_expr);
// Check that len expression is equals to `with_capacity` expression
if let Some(len_arg) = take_args.get(1);
if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr);
then {
return true;
// Check that len expression is equals to `with_capacity` expression
if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) {
return true;
} else if let ExprKind::MethodCall(path, _, _) = len_arg.kind && path.ident.as_str() == "capacity" {
return true;
}
}
}

View file

@ -60,6 +60,12 @@ declare_clippy_lint! {
/// let x = "Hello".to_owned();
/// x + ", World";
/// ```
///
/// Use instead:
/// ```rust
/// let mut x = "Hello".to_owned();
/// x.push_str(", World");
/// ```
#[clippy::version = "pre 1.29.0"]
pub STRING_ADD,
restriction,

View file

@ -35,7 +35,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.38.0"]
pub TYPE_REPETITION_IN_BOUNDS,
pedantic,
nursery,
"Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
}
@ -65,7 +65,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.47.0"]
pub TRAIT_DUPLICATION_IN_BOUNDS,
pedantic,
nursery,
"Check if the same trait bounds are specified twice during a function declaration"
}

View file

@ -16,9 +16,10 @@ mod wrong_transmute;
use clippy_utils::in_constant;
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind};
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::sym;
declare_clippy_lint! {
@ -385,7 +386,10 @@ declare_clippy_lint! {
"transmute to or from a type with an undefined representation"
}
declare_lint_pass!(Transmute => [
pub struct Transmute {
msrv: Option<RustcVersion>,
}
impl_lint_pass!(Transmute => [
CROSSPOINTER_TRANSMUTE,
TRANSMUTE_PTR_TO_REF,
TRANSMUTE_PTR_TO_PTR,
@ -401,13 +405,18 @@ declare_lint_pass!(Transmute => [
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
TRANSMUTE_UNDEFINED_REPR,
]);
impl Transmute {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
}
}
impl<'tcx> LateLintPass<'tcx> for Transmute {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Call(path_expr, [arg]) = e.kind;
if let ExprKind::Path(ref qpath) = path_expr.kind;
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id();
if let ExprKind::Path(QPath::Resolved(None, path)) = path_expr.kind;
if let Some(def_id) = path.res.opt_def_id();
if cx.tcx.is_diagnostic_item(sym::transmute, def_id);
then {
// Avoid suggesting non-const operations in const contexts:
@ -427,7 +436,7 @@ 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_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv)
| transmute_int_to_char::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg)
@ -446,4 +455,6 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
}
}
}
extract_msrv_attr!(LateContext);
}

View file

@ -1,11 +1,12 @@
use super::utils::get_type_snippet;
use super::TRANSMUTE_PTR_TO_REF;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{meets_msrv, msrvs, sugg};
use rustc_errors::Applicability;
use rustc_hir::{Expr, Mutability, QPath};
use rustc_hir::{self as hir, Expr, GenericArg, Mutability, Path, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_middle::ty::{self, Ty, TypeFoldable};
use rustc_semver::RustcVersion;
/// Checks for `transmute_ptr_to_ref` lint.
/// Returns `true` if it's triggered, otherwise returns `false`.
@ -15,7 +16,8 @@ pub(super) fn check<'tcx>(
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
qpath: &'tcx QPath<'_>,
path: &'tcx Path<'_>,
msrv: Option<RustcVersion>,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::RawPtr(from_ptr_ty), ty::Ref(_, to_ref_ty, mutbl)) => {
@ -34,19 +36,34 @@ pub(super) fn check<'tcx>(
} else {
("&*", "*const")
};
let mut app = Applicability::MachineApplicable;
let arg = if from_ptr_ty.ty == *to_ref_ty {
arg
let sugg = if let Some(ty) = get_explicit_type(path) {
let ty_snip = snippet_with_applicability(cx, ty.span, "..", &mut app);
if meets_msrv(msrv, msrvs::POINTER_CAST) {
format!("{}{}.cast::<{}>()", deref, arg.maybe_par(), ty_snip)
} else if from_ptr_ty.has_erased_regions() {
sugg::make_unop(deref, arg.as_ty(format!("{} () as {} {}", cast, cast, ty_snip)))
.to_string()
} else {
sugg::make_unop(deref, arg.as_ty(format!("{} {}", cast, ty_snip))).to_string()
}
} else if from_ptr_ty.ty == *to_ref_ty {
if from_ptr_ty.has_erased_regions() {
if meets_msrv(msrv, msrvs::POINTER_CAST) {
format!("{}{}.cast::<{}>()", deref, arg.maybe_par(), to_ref_ty)
} else {
sugg::make_unop(deref, arg.as_ty(format!("{} () as {} {}", cast, cast, to_ref_ty)))
.to_string()
}
} else {
sugg::make_unop(deref, arg).to_string()
}
} else {
arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, *to_ref_ty)))
sugg::make_unop(deref, arg.as_ty(format!("{} {}", cast, to_ref_ty))).to_string()
};
diag.span_suggestion(
e.span,
"try",
sugg::make_unop(deref, arg).to_string(),
Applicability::Unspecified,
);
diag.span_suggestion(e.span, "try", sugg, app);
},
);
true
@ -54,3 +71,14 @@ pub(super) fn check<'tcx>(
_ => false,
}
}
/// Gets the type `Bar` in `…::transmute<Foo, &Bar>`.
fn get_explicit_type<'tcx>(path: &'tcx Path<'tcx>) -> Option<&'tcx hir::Ty<'tcx>> {
if let GenericArg::Type(ty) = path.segments.last()?.args?.args.get(1)?
&& let TyKind::Rptr(_, ty) = &ty.kind
{
Some(ty.ty)
} else {
None
}
}

View file

@ -1,35 +1,9 @@
use clippy_utils::last_path_segment;
use clippy_utils::source::snippet;
use if_chain::if_chain;
use rustc_hir::{Expr, GenericArg, QPath, TyKind};
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{cast::CastKind, Ty};
use rustc_span::DUMMY_SP;
use rustc_typeck::check::{cast::CastCheck, FnCtxt, Inherited};
/// Gets the snippet of `Bar` in `…::transmute<Foo, &Bar>`. If that snippet is
/// not available , use
/// the type's `ToString` implementation. In weird cases it could lead to types
/// with invalid `'_`
/// lifetime, but it should be rare.
pub(super) fn get_type_snippet(cx: &LateContext<'_>, path: &QPath<'_>, to_ref_ty: Ty<'_>) -> String {
let seg = last_path_segment(path);
if_chain! {
if let Some(params) = seg.args;
if !params.parenthesized;
if let Some(to_ty) = params.args.iter().filter_map(|arg| match arg {
GenericArg::Type(ty) => Some(ty),
_ => None,
}).nth(1);
if let TyKind::Rptr(_, ref to_ty) = to_ty.kind;
then {
return snippet(cx, to_ty.ty.span, &to_ref_ty.to_string()).to_string();
}
}
to_ref_ty.to_string()
}
// check if the component types of the transmuted collection and the result have different ABI,
// size or alignment
pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool {

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