Merge commit '0cb0f7636851f9fcc57085cf80197a2ef6db098f' into clippyup

This commit is contained in:
Philipp Krones
2022-06-30 10:50:09 +02:00
parent ee37029afa
commit 09f5df5087
243 changed files with 9046 additions and 3487 deletions

View File

@@ -21,7 +21,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v1.4.4 uses: actions/setup-node@v1.4.4
with: with:
node-version: '12.x' node-version: '14.x'
- name: Install remark - name: Install remark
run: npm install remark-cli remark-lint remark-lint-maximum-line-length remark-preset-lint-recommended remark-gfm 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 [`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 [`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 [`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_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_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 [`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_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 [`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 [`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_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_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 [`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_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_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_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_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_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 [`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_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_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_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_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_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 [`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "clippy" name = "clippy"
version = "0.1.63" version = "0.1.64"
description = "A bunch of helpful lints to avoid common pitfalls in Rust" description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy" repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md" readme = "README.md"
@@ -31,6 +31,7 @@ termize = "0.1"
compiletest_rs = { version = "0.8", features = ["tmp"] } compiletest_rs = { version = "0.8", features = ["tmp"] }
tester = "0.9" tester = "0.9"
regex = "1.5" regex = "1.5"
toml = "0.5"
# This is used by the `collect-metadata` alias. # This is used by the `collect-metadata` alias.
filetime = "0.2" filetime = "0.2"

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. 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). 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. 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" 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. The MSRV can also be specified as an inner attribute, like below.
```rust ```rust

View File

@@ -6,7 +6,7 @@
A collection of lints to catch common mistakes and improve your A collection of lints to catch common mistakes and improve your
[Rust](https://github.com/rust-lang/rust) code. [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 Lints are divided into categories, each with a default [lint
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how

View File

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

View File

@@ -5,6 +5,7 @@
use clap::{Arg, ArgAction, ArgMatches, Command, PossibleValue}; use clap::{Arg, ArgAction, ArgMatches, Command, PossibleValue};
use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints}; use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints};
use indoc::indoc; use indoc::indoc;
fn main() { fn main() {
let matches = get_clap_config(); let matches = get_clap_config();
@@ -85,6 +86,11 @@ fn main() {
let uplift = matches.contains_id("uplift"); let uplift = matches.contains_id("uplift");
update_lints::rename(old_name, new_name, 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") .long("uplift")
.help("This lint will be uplifted into rustc"), .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() .get_matches()
} }

View File

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

View File

@@ -1,16 +1,17 @@
use crate::clippy_project_root;
use aho_corasick::AhoCorasickBuilder; use aho_corasick::AhoCorasickBuilder;
use core::fmt::Write as _; use indoc::writedoc;
use itertools::Itertools; use itertools::Itertools;
use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs; use std::fmt::Write;
use std::io::{self, Read as _, Seek as _, Write as _}; use std::fs::{self, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write as _};
use std::ops::Range;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use walkdir::{DirEntry, WalkDir}; use walkdir::{DirEntry, WalkDir};
use crate::clippy_project_root;
const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ 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\ // Use that command to update this file and do not edit by hand.\n\
// Manual edits will be overwritten.\n\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"); 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 /// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there
/// were no replacements. /// were no replacements.
fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<String> { fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<String> {
@@ -393,16 +588,18 @@ struct Lint {
group: String, group: String,
desc: String, desc: String,
module: String, module: String,
declaration_range: Range<usize>,
} }
impl Lint { impl Lint {
#[must_use] #[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 { Self {
name: name.to_lowercase(), name: name.to_lowercase(),
group: group.into(), group: group.into(),
desc: remove_line_splices(desc), desc: remove_line_splices(desc),
module: module.into(), module: module.into(),
declaration_range,
} }
} }
@@ -433,12 +630,14 @@ impl Lint {
struct DeprecatedLint { struct DeprecatedLint {
name: String, name: String,
reason: String, reason: String,
declaration_range: Range<usize>,
} }
impl DeprecatedLint { impl DeprecatedLint {
fn new(name: &str, reason: &str) -> Self { fn new(name: &str, reason: &str, declaration_range: Range<usize>) -> Self {
Self { Self {
name: name.to_lowercase(), name: name.to_lowercase(),
reason: remove_line_splices(reason), 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 { macro_rules! match_tokens {
($iter:ident, $($token:ident $({$($fields:tt)*})? $(($capture:ident))?)*) => { ($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 _x
} else { } else {
continue; 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. /// Parse a source file looking for `declare_clippy_lint` macro invocations.
fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) { fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
let mut offset = 0usize; let mut offset = 0usize;
let mut iter = tokenize(contents).map(|t| { let mut iter = tokenize(contents).map(|t| {
let range = offset..offset + t.len; let range = offset..offset + t.len;
offset = range.end; 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 let mut iter = iter
.by_ref() .by_ref()
.filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. })); .filter(|t| !matches!(t.token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
// matches `!{` // matches `!{`
match_tokens!(iter, Bang OpenBrace); match_tokens!(iter, Bang OpenBrace);
match iter.next() { match iter.next() {
// #[clippy::version = "version"] pub // #[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); match_tokens!(iter, OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Ident);
}, },
// pub // pub
Some((TokenKind::Ident, _)) => (), Some(LintDeclSearchResult {
token_kind: TokenKind::Ident,
..
}) => (),
_ => continue, _ => continue,
} }
let (name, group, desc) = match_tokens!( let (name, group, desc) = match_tokens!(
iter, iter,
// LINT_NAME // LINT_NAME
Ident(name) Comma Ident(name) Comma
// group, // group,
Ident(group) Comma Ident(group) Comma
// "description" } // "description"
Literal{..}(desc) CloseBrace 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 mut iter = tokenize(contents).map(|t| {
let range = offset..offset + t.len; let range = offset..offset + t.len;
offset = range.end; offset = range.end;
(t.kind, &contents[range])
LintDeclSearchResult {
token_kind: t.kind,
content: &contents[range.clone()],
range,
}
});
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 { .. })
}); });
while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_deprecated_lint") {
let mut iter = iter
.by_ref()
.filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
let (name, reason) = match_tokens!( let (name, reason) = match_tokens!(
iter, iter,
// !{ // !{
@@ -680,10 +927,16 @@ fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) {
Ident Ident(name) Comma Ident Ident(name) Comma
// "description" // "description"
Literal{kind: LiteralKind::Str{..},..}(reason) 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 mut iter = tokenize(line).map(|t| {
let range = offset..offset + t.len; let range = offset..offset + t.len;
offset = range.end; offset = range.end;
(t.kind, &line[range])
LintDeclSearchResult {
token_kind: t.kind,
content: &line[range.clone()],
range,
}
}); });
let (old_name, new_name) = match_tokens!( let (old_name, new_name) = match_tokens!(
iter, iter,
// ("old_name", // ("old_name",
@@ -844,10 +1103,25 @@ mod tests {
"#; "#;
let mut result = Vec::new(); let mut result = Vec::new();
parse_contents(CONTENTS, "module_name", &mut result); parse_contents(CONTENTS, "module_name", &mut result);
for r in &mut result {
r.declaration_range = Range::default();
}
let expected = vec![ let expected = vec![
Lint::new("ptr_arg", "style", "\"really long text\"", "module_name"), Lint::new(
Lint::new("doc_markdown", "pedantic", "\"single line\"", "module_name"), "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); assert_eq!(expected, result);
} }
@@ -865,10 +1139,14 @@ mod tests {
let mut result = Vec::new(); let mut result = Vec::new();
parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result); parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result);
for r in &mut result {
r.declaration_range = Range::default();
}
let expected = vec![DeprecatedLint::new( let expected = vec![DeprecatedLint::new(
"should_assert_eq", "should_assert_eq",
"\"`assert!()` will be more flexible with RFC 2011\"", "\"`assert!()` will be more flexible with RFC 2011\"",
Range::default(),
)]; )];
assert_eq!(expected, result); assert_eq!(expected, result);
} }
@@ -876,15 +1154,34 @@ mod tests {
#[test] #[test]
fn test_usable_lints() { fn test_usable_lints() {
let lints = vec![ let lints = vec![
Lint::new("should_assert_eq2", "Not Deprecated", "\"abc\"", "module_name"), Lint::new(
Lint::new("should_assert_eq2", "internal", "\"abc\"", "module_name"), "should_assert_eq2",
Lint::new("should_assert_eq2", "internal_style", "\"abc\"", "module_name"), "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( let expected = vec![Lint::new(
"should_assert_eq2", "should_assert_eq2",
"Not Deprecated", "Not Deprecated",
"\"abc\"", "\"abc\"",
"module_name", "module_name",
Range::default(),
)]; )];
assert_eq!(expected, Lint::usable_lints(&lints)); assert_eq!(expected, Lint::usable_lints(&lints));
} }
@@ -892,21 +1189,33 @@ mod tests {
#[test] #[test]
fn test_by_lint_group() { fn test_by_lint_group() {
let lints = vec![ let lints = vec![
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"), Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()),
Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name"), Lint::new(
Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"), "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(); let mut expected: HashMap<String, Vec<Lint>> = HashMap::new();
expected.insert( expected.insert(
"group1".to_string(), "group1".to_string(),
vec![ vec![
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"), Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()),
Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"), Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()),
], ],
); );
expected.insert( expected.insert(
"group2".to_string(), "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())); assert_eq!(expected, Lint::by_lint_group(lints.into_iter()));
} }
@@ -914,8 +1223,12 @@ mod tests {
#[test] #[test]
fn test_gen_deprecated() { fn test_gen_deprecated() {
let lints = vec![ let lints = vec![
DeprecatedLint::new("should_assert_eq", "\"has been superseded by should_assert_eq2\""), DeprecatedLint::new(
DeprecatedLint::new("another_deprecated", "\"will be removed\""), "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() let expected = GENERATED_FILE_COMMENT.to_string()
@@ -940,9 +1253,9 @@ mod tests {
#[test] #[test]
fn test_gen_lint_group_list() { fn test_gen_lint_group_list() {
let lints = vec![ let lints = vec![
Lint::new("abc", "group1", "\"abc\"", "module_name"), Lint::new("abc", "group1", "\"abc\"", "module_name", Range::default()),
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"), Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()),
Lint::new("internal", "internal_style", "\"abc\"", "module_name"), Lint::new("internal", "internal_style", "\"abc\"", "module_name", Range::default()),
]; ];
let expected = GENERATED_FILE_COMMENT.to_string() let expected = GENERATED_FILE_COMMENT.to_string()
+ &[ + &[

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "clippy_lints" name = "clippy_lints"
version = "0.1.63" version = "0.1.64"
description = "A bunch of helpful lints to avoid common pitfalls in Rust" description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy" repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md" readme = "README.md"
@@ -10,7 +10,6 @@ edition = "2021"
[dependencies] [dependencies]
cargo_metadata = "0.14" cargo_metadata = "0.14"
clippy_dev = { path = "../clippy_dev", optional = true }
clippy_utils = { path = "../clippy_utils" } clippy_utils = { path = "../clippy_utils" }
if_chain = "1.0" if_chain = "1.0"
itertools = "0.10.1" itertools = "0.10.1"
@@ -32,7 +31,7 @@ url = { version = "2.2", features = ["serde"] }
[features] [features]
deny-warnings = ["clippy_utils/deny-warnings"] deny-warnings = ["clippy_utils/deny-warnings"]
# build clippy with internal lints enabled, off by default # 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] [package.metadata.rust-analyzer]
# This crate uses #[feature(rustc_private)] # 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 /// Checks for `extern crate` and `use` items annotated with
/// lint attributes. /// lint attributes.
/// ///
/// This lint permits `#[allow(unused_imports)]`, `#[allow(deprecated)]`, /// This lint permits lint attributes for lints emitted on the items themself.
/// `#[allow(unreachable_pub)]`, `#[allow(clippy::wildcard_imports)]` and /// For `use` items these lints are:
/// `#[allow(clippy::enum_glob_use)]` on `use` items and `#[allow(unused_imports)]` on /// * deprecated
/// `extern crate` items with a `#[macro_use]` attribute. /// * 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? /// ### Why is this bad?
/// Lint attributes have no effect on crate imports. Most /// 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| { || extract_clippy_lint(lint).map_or(false, |s| {
matches!( matches!(
s.as_str(), 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::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{eq_expr_value, get_trait_def_id, paths}; use clippy_utils::{eq_expr_value, get_trait_def_id, paths};
@@ -394,9 +394,10 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
continue 'simplified; continue 'simplified;
} }
if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 { if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
span_lint_and_then( span_lint_hir_and_then(
self.cx, self.cx,
LOGIC_BUG, LOGIC_BUG,
e.hir_id,
e.span, e.span,
"this boolean expression contains a logic bug", "this boolean expression contains a logic bug",
|diag| { |diag| {
@@ -429,9 +430,10 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
} }
} }
let nonminimal_bool_lint = |suggestions: Vec<_>| { let nonminimal_bool_lint = |suggestions: Vec<_>| {
span_lint_and_then( span_lint_hir_and_then(
self.cx, self.cx,
NONMINIMAL_BOOL, NONMINIMAL_BOOL,
e.hir_id,
e.span, e.span,
"this boolean expression can be simplified", "this boolean expression can be simplified",
|diag| { |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 // NOTE: Entries should be created with `cargo dev deprecate`
// tests/ui/deprecated.rs
/// This struct fakes the `Lint` declaration that is usually created by `declare_lint!`. This /// 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 /// enables the simple extraction of the metadata without changing the current deprecation
/// declaration. /// declaration.
pub struct ClippyDeprecatedLint; pub struct ClippyDeprecatedLint {
#[allow(dead_code)]
pub desc: &'static str,
}
#[macro_export]
macro_rules! declare_deprecated_lint { macro_rules! declare_deprecated_lint {
{ $(#[$attr:meta])* pub $name: ident, $_reason: expr} => { { $(#[$attr:meta])* pub $name: ident, $reason: literal} => {
$(#[$attr])* $(#[$attr])*
#[allow(dead_code)] #[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::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::peel_mid_ty_refs; use clippy_utils::ty::{expr_sig, peel_mid_ty_refs, variant_of_res};
use clippy_utils::{get_parent_expr, get_parent_node, is_lint_allowed, path_to_local}; 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_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_ty, Visitor};
use rustc_hir::{ use rustc_hir::{
BindingAnnotation, Body, BodyId, BorrowKind, Destination, Expr, ExprKind, HirId, MatchSource, Mutability, Node, self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, GenericArg, HirId, ImplItem,
Pat, PatKind, UnOp, 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_lint::{LateContext, LateLintPass};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; 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_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! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@@ -104,10 +108,34 @@ declare_clippy_lint! {
"`ref` binding to a reference" "`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 => [ impl_lint_pass!(Dereferencing => [
EXPLICIT_DEREF_METHODS, EXPLICIT_DEREF_METHODS,
NEEDLESS_BORROW, NEEDLESS_BORROW,
REF_BINDING_TO_REFERENCE, REF_BINDING_TO_REFERENCE,
EXPLICIT_AUTO_DEREF,
]); ]);
#[derive(Default)] #[derive(Default)]
@@ -136,6 +164,12 @@ struct StateData {
/// Span of the top level expression /// Span of the top level expression
span: Span, span: Span,
hir_id: HirId, hir_id: HirId,
position: Position,
}
struct DerefedBorrow {
count: usize,
msg: &'static str,
} }
enum State { enum State {
@@ -147,11 +181,19 @@ enum State {
/// The required mutability /// The required mutability
target_mut: Mutability, target_mut: Mutability,
}, },
DerefedBorrow { DerefedBorrow(DerefedBorrow),
count: usize, ExplicitDeref {
required_precedence: i8, // Span and id of the top-level deref expression if the parent expression is a borrow.
msg: &'static str, 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 // A reference operation considered by this lint pass
@@ -207,13 +249,28 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
match (self.state.take(), kind) { match (self.state.take(), kind) {
(None, kind) => { (None, kind) => {
let parent = get_parent_node(cx.tcx, expr.hir_id);
let expr_ty = typeck.expr_ty(expr); let expr_ty = typeck.expr_ty(expr);
let (position, adjustments) = walk_parents(cx, expr);
match kind { 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) RefOp::Method(target_mut)
if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id) 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(( self.state = Some((
State::DerefMethod { State::DerefMethod {
@@ -228,12 +285,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
StateData { StateData {
span: expr.span, span: expr.span,
hir_id: expr.hir_id, hir_id: expr.hir_id,
position
}, },
)); ));
}, },
RefOp::AddrOf => { RefOp::AddrOf => {
// Find the number of times the borrow is auto-derefed. // 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 mut deref_count = 0usize;
let next_adjust = loop { let next_adjust = loop {
match iter.next() { 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"; "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 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) let (required_refs, msg) = if position.can_auto_borrow() {
{ (1, if deref_count == 1 { borrow_msg } else { deref_msg })
(1, PREC_POSTFIX, if deref_count == 1 { borrow_msg } else { deref_msg })
} else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) = } else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) =
next_adjust.map(|a| &a.kind) next_adjust.map(|a| &a.kind)
{ {
if matches!(mutability, AutoBorrowMutability::Mut { .. }) if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !position.is_reborrow_stable()
&& !is_auto_reborrow_position(parent)
{ {
(3, 0, deref_msg) (3, deref_msg)
} else { } else {
(2, 0, deref_msg) (2, deref_msg)
} }
} else { } else {
(2, 0, deref_msg) (2, deref_msg)
}; };
if deref_count >= required_refs { if deref_count >= required_refs {
self.state = Some(( self.state = Some((
State::DerefedBorrow { State::DerefedBorrow(DerefedBorrow {
// One of the required refs is for the current borrow expression, the remaining ones // 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. // can't be removed without breaking the code. See earlier comment.
count: deref_count - required_refs, count: deref_count - required_refs,
required_precedence,
msg, msg,
}, }),
StateData { span: expr.span, hir_id: expr.hir_id, position },
));
} else if position.is_deref_stable() {
self.state = Some((
State::Borrow,
StateData { StateData {
span: expr.span, span: expr.span,
hir_id: expr.hir_id, hir_id: expr.hir_id,
position
}, },
)); ));
} }
}, },
_ => (), RefOp::Method(..) => (),
} }
}, },
( (
@@ -334,25 +395,89 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
data, data,
)); ));
}, },
( (Some((State::DerefedBorrow(state), data)), RefOp::AddrOf) if state.count != 0 => {
Some((
State::DerefedBorrow {
count,
required_precedence,
msg,
},
data,
)),
RefOp::AddrOf,
) if count != 0 => {
self.state = Some(( self.state = Some((
State::DerefedBorrow { State::DerefedBorrow(DerefedBorrow {
count: count - 1, count: state.count - 1,
required_precedence, ..state
msg, }),
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, data,
)); ));
} else {
self.state = Some((
State::ExplicitDeref {
deref_span_id: Some((expr.span, expr.hir_id)),
},
data,
));
}
},
(
Some((
State::Reborrow {
deref_span,
deref_hir_id,
},
data,
)),
RefOp::Deref,
) => {
self.state = Some((
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), (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 /// The position of an expression relative to it's parent.
// deref operator. #[derive(Clone, Copy)]
fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId, child_span: Span) -> bool { enum Position {
let parent = match parent { MethodReceiver,
Some(Node::Expr(e)) if e.span.ctxt() == child_span.ctxt() => e, /// The method is defined on a reference type. e.g. `impl Foo for &T`
_ => return true, MethodReceiverRefImpl,
}; Callee,
match parent.kind { FieldAccess(Symbol),
// Leave deref calls in the middle of a method chain. Postfix,
// e.g. x.deref().foo() Deref,
ExprKind::MethodCall(_, [self_arg, ..], _) if self_arg.hir_id == child_id => false, /// 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),
}
impl Position {
fn is_deref_stable(self) -> bool {
matches!(self, Self::DerefStable(_))
}
// Leave deref calls resulting in a called function fn is_reborrow_stable(self) -> bool {
// e.g. (x.deref())() matches!(self, Self::DerefStable(_) | Self::ReborrowStable(_))
ExprKind::Call(func_expr, _) if func_expr.hir_id == child_id => false, }
// Makes an ugly suggestion fn can_auto_borrow(self) -> bool {
// e.g. *x.deref() => *&*x matches!(self, Self::MethodReceiver | Self::FieldAccess(_) | Self::Callee)
ExprKind::Unary(UnOp::Deref, _) }
// Postfix expressions would require parens
| ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
| ExprKind::Field(..)
| ExprKind::Index(..)
| ExprKind::Err => false,
ExprKind::Box(..) fn lint_explicit_deref(self) -> bool {
| ExprKind::ConstBlock(..) matches!(self, Self::Other(_) | Self::DerefStable(_) | Self::ReborrowStable(_))
| ExprKind::Array(_) }
| ExprKind::Call(..)
| ExprKind::MethodCall(..) fn precedence(self) -> i8 {
| ExprKind::Tup(..) match self {
| ExprKind::Binary(..) Self::MethodReceiver
| ExprKind::Unary(..) | Self::MethodReceiverRefImpl
| ExprKind::Lit(..) | Self::Callee
| ExprKind::Cast(..) | Self::FieldAccess(_)
| ExprKind::Type(..) | Self::Postfix => PREC_POSTFIX,
| ExprKind::DropTemps(..) Self::Deref => PREC_PREFIX,
| ExprKind::If(..) Self::DerefStable(p) | Self::ReborrowStable(p) | Self::Other(p) => p,
| 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,
} }
} }
/// Checks if the given expression is in a position which can be auto-reborrowed. /// Walks up the parent expressions attempting to determine both how stable the auto-deref result
/// Note: This is only correct assuming auto-deref is already occurring. /// is, and which adjustments will be applied to it. Note this will not consider auto-borrow
fn is_auto_reborrow_position(parent: Option<Node<'_>>) -> bool { /// 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 { match parent {
Some(Node::Expr(parent)) => matches!(parent.kind, ExprKind::MethodCall(..) | ExprKind::Call(..)), Node::Local(Local { ty: Some(ty), span, .. }) if span.ctxt() == ctxt => {
Some(Node::Local(_)) => true, 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, _ => 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 if the given expression is a position which can auto-borrow. // Checks whether a type is inferred at some point.
fn is_auto_borrow_position(parent: Option<Node<'_>>, child_id: HirId) -> bool { // e.g. `_`, `Box<_>`, `[_]`
if let Some(Node::Expr(parent)) = parent { fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
match parent.kind { struct V(bool);
// ExprKind::MethodCall(_, [self_arg, ..], _) => self_arg.hir_id == child_id, impl Visitor<'_> for V {
ExprKind::Field(..) => true, fn visit_ty(&mut self, ty: &hir::Ty<'_>) {
ExprKind::Call(f, _) => f.hir_id == child_id, if self.0
_ => false, || 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 { } else {
false false
} }
} }
/// Adjustments are sometimes made in the parent block rather than the expression itself. #[expect(clippy::needless_pass_by_value, clippy::too_many_lines)]
fn find_adjustments<'tcx>( fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData) {
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) {
match state { match state {
State::DerefMethod { State::DerefMethod {
ty_changed_count, ty_changed_count,
@@ -647,15 +1003,14 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: S
app, app,
); );
}, },
State::DerefedBorrow { State::DerefedBorrow(state) => {
required_precedence,
msg,
..
} => {
let mut app = Applicability::MachineApplicable; let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0; 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, msg, |diag| { span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| {
let sugg = if required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) { let sugg = if !snip_is_macro
&& expr.precedence().order() < data.position.precedence()
&& !has_enclosing_paren(&snip)
{
format!("({})", snip) format!("({})", snip)
} else { } else {
snip.into() 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); 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) .map(|e| *e.0)
.collect(); .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, (true, true) => return,
(false, _) => ("pre", pre.join("")), (false, _) => ("pre", pre.join("")),
(true, false) => { (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] #[must_use]
fn to_camel_case(item_name: &str) -> String { fn to_camel_case(item_name: &str) -> String {
let mut s = String::new(); 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 clippy_utils::ty::contains_ty;
use rustc_hir::intravisit; use rustc_hir::intravisit;
use rustc_hir::{self, AssocItemKind, Body, FnDecl, HirId, HirIdSet, Impl, ItemKind, Node}; use rustc_hir::{self, AssocItemKind, Body, FnDecl, HirId, HirIdSet, Impl, ItemKind, Node};
@@ -118,9 +118,10 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
}); });
for node in v.set { for node in v.set {
span_lint( span_lint_hir(
cx, cx,
BOXED_LOCAL, BOXED_LOCAL,
node,
cx.tcx.hir().span(node), cx.tcx.hir().span(node),
"local variable doesn't need to be boxed here", "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::{ use clippy_utils::{
diagnostics::span_lint_and_sugg, diagnostics::span_lint_hir_and_then,
get_async_fn_body, is_async_fn, get_async_fn_body, is_async_fn,
source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}, source::{snippet_with_applicability, snippet_with_context, walk_span_to_context},
visitors::expr_visitor_no_bodies, visitors::expr_visitor_no_bodies,
@@ -43,32 +43,39 @@ declare_clippy_lint! {
declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]); 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 mut app = Applicability::MachineApplicable;
let snip = snippet_with_applicability(cx, span, "..", &mut app); let snip = snippet_with_applicability(cx, span, "..", &mut app);
span_lint_and_sugg( span_lint_hir_and_then(
cx, cx,
IMPLICIT_RETURN, IMPLICIT_RETURN,
emission_place,
span, span,
"missing `return` statement", "missing `return` statement",
"add `return` as shown", |diag| {
format!("return {}", snip), diag.span_suggestion(span, "add `return` as shown", format!("return {}", snip), app);
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 mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0; let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0;
span_lint_and_sugg( span_lint_hir_and_then(
cx, cx,
IMPLICIT_RETURN, IMPLICIT_RETURN,
emission_place,
break_span, break_span,
"missing `return` statement", "missing `return` statement",
|diag| {
diag.span_suggestion(
break_span,
"change `break` to `return` as shown", "change `break` to `return` as shown",
format!("return {}", snip), format!("return {}", snip),
app, app,
); );
},
);
} }
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
@@ -152,7 +159,7 @@ fn lint_implicit_returns(
// At this point sub_expr can be `None` in async functions which either diverge, or return // At this point sub_expr can be `None` in async functions which either diverge, or return
// the unit type. // the unit type.
if let Some(sub_expr) = sub_expr { 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 { } else {
// the break expression is from a macro call, add a return to the loop // 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 { if add_return {
#[expect(clippy::option_if_let_else)] #[expect(clippy::option_if_let_else)]
if let Some(span) = call_site_span { if let Some(span) = call_site_span {
lint_return(cx, span); lint_return(cx, expr.hir_id, span);
LintLocation::Parent LintLocation::Parent
} else { } else {
lint_return(cx, expr.span); lint_return(cx, expr.hir_id, expr.span);
LintLocation::Inner LintLocation::Inner
} }
} else { } else {
@@ -198,10 +205,10 @@ fn lint_implicit_returns(
{ {
#[expect(clippy::option_if_let_else)] #[expect(clippy::option_if_let_else)]
if let Some(span) = call_site_span { if let Some(span) = call_site_span {
lint_return(cx, span); lint_return(cx, expr.hir_id, span);
LintLocation::Parent LintLocation::Parent
} else { } else {
lint_return(cx, expr.span); lint_return(cx, expr.hir_id, expr.span);
LintLocation::Inner 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: /// Use instead:
/// ```rust.ignore /// ```rust,ignore
/// pub static a = [0u32; 1_000_000]; /// pub static a = [0u32; 1_000_000];
/// ``` /// ```
#[clippy::version = "1.44.0"] #[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]); 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::MUTEX_GUARD,
&paths::RWLOCK_READ_GUARD, &paths::RWLOCK_READ_GUARD,
&paths::RWLOCK_WRITE_GUARD, &paths::RWLOCK_WRITE_GUARD,
&paths::PARKING_LOT_RAWMUTEX, &paths::PARKING_LOT_MUTEX_GUARD,
&paths::PARKING_LOT_RAWRWLOCK, &paths::PARKING_LOT_RWLOCK_READ_GUARD,
&paths::PARKING_LOT_RWLOCK_WRITE_GUARD,
]; ];
impl<'tcx> LateLintPass<'tcx> for LetUnderscore { impl<'tcx> LateLintPass<'tcx> for LetUnderscore {

View File

@@ -3,12 +3,9 @@
// Manual edits will be overwritten. // Manual edits will be overwritten.
store.register_group(true, "clippy::all", Some("clippy_all"), vec![ 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(almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE),
LintId::of(approx_const::APPROX_CONSTANT), LintId::of(approx_const::APPROX_CONSTANT),
LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS), 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(async_yields_async::ASYNC_YIELDS_ASYNC),
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS), LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
LintId::of(attrs::DEPRECATED_CFG_ATTR), 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_INVALID_TYPE),
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), 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(blacklisted_name::BLACKLISTED_NAME),
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS), LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON), 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(copies::IF_SAME_THEN_ELSE),
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF), LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT), 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(dereference::NEEDLESS_BORROW),
LintId::of(derivable_impls::DERIVABLE_IMPLS), LintId::of(derivable_impls::DERIVABLE_IMPLS),
LintId::of(derive::DERIVE_HASH_XOR_EQ), 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(disallowed_types::DISALLOWED_TYPES),
LintId::of(doc::MISSING_SAFETY_DOC), LintId::of(doc::MISSING_SAFETY_DOC),
LintId::of(doc::NEEDLESS_DOCTEST_MAIN), LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
LintId::of(double_comparison::DOUBLE_COMPARISONS),
LintId::of(double_parens::DOUBLE_PARENS), LintId::of(double_parens::DOUBLE_PARENS),
LintId::of(drop_forget_ref::DROP_COPY), LintId::of(drop_forget_ref::DROP_COPY),
LintId::of(drop_forget_ref::DROP_NON_DROP), 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::FORGET_REF),
LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS), LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS),
LintId::of(duplicate_mod::DUPLICATE_MOD), LintId::of(duplicate_mod::DUPLICATE_MOD),
LintId::of(duration_subsec::DURATION_SUBSEC),
LintId::of(entry::MAP_ENTRY), LintId::of(entry::MAP_ENTRY),
LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT), LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
LintId::of(enum_variants::ENUM_VARIANT_NAMES), LintId::of(enum_variants::ENUM_VARIANT_NAMES),
LintId::of(enum_variants::MODULE_INCEPTION), 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(escape::BOXED_LOCAL),
LintId::of(eta_reduction::REDUNDANT_CLOSURE), LintId::of(eta_reduction::REDUNDANT_CLOSURE),
LintId::of(explicit_write::EXPLICIT_WRITE), LintId::of(explicit_write::EXPLICIT_WRITE),
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
LintId::of(float_literal::EXCESSIVE_PRECISION), LintId::of(float_literal::EXCESSIVE_PRECISION),
LintId::of(format::USELESS_FORMAT), LintId::of(format::USELESS_FORMAT),
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS), 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::RESULT_UNIT_ERR),
LintId::of(functions::TOO_MANY_ARGUMENTS), LintId::of(functions::TOO_MANY_ARGUMENTS),
LintId::of(get_first::GET_FIRST), LintId::of(get_first::GET_FIRST),
LintId::of(identity_op::IDENTITY_OP),
LintId::of(if_let_mutex::IF_LET_MUTEX), LintId::of(if_let_mutex::IF_LET_MUTEX),
LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING), LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING),
LintId::of(infinite_iter::INFINITE_ITER), 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_KV_MAP),
LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES), LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
LintId::of(loops::ITER_NEXT_LOOP), LintId::of(loops::ITER_NEXT_LOOP),
LintId::of(loops::MANUAL_FIND),
LintId::of(loops::MANUAL_FLATTEN), LintId::of(loops::MANUAL_FLATTEN),
LintId::of(loops::MANUAL_MEMCPY), LintId::of(loops::MANUAL_MEMCPY),
LintId::of(loops::MISSING_SPIN_LOOP), 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_async_fn::MANUAL_ASYNC_FN),
LintId::of(manual_bits::MANUAL_BITS), LintId::of(manual_bits::MANUAL_BITS),
LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE), 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(manual_strip::MANUAL_STRIP),
LintId::of(map_clone::MAP_CLONE), LintId::of(map_clone::MAP_CLONE),
LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN), 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::WRONG_SELF_CONVENTION),
LintId::of(methods::ZST_OFFSET), LintId::of(methods::ZST_OFFSET),
LintId::of(minmax::MIN_MAX), 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::SHORT_CIRCUIT_STATEMENT),
LintId::of(misc::TOPLEVEL_REF_ARG), LintId::of(misc::TOPLEVEL_REF_ARG),
LintId::of(misc::ZERO_PTR), 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(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
LintId::of(octal_escapes::OCTAL_ESCAPES), LintId::of(octal_escapes::OCTAL_ESCAPES),
LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), 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(option_env_unwrap::OPTION_ENV_UNWRAP),
LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL), 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::INVALID_NULL_PTR_USAGE),
LintId::of(ptr::MUT_FROM_REF), LintId::of(ptr::MUT_FROM_REF),
LintId::of(ptr::PTR_ARG), LintId::of(ptr::PTR_ARG),
LintId::of(ptr_eq::PTR_EQ),
LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST), LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST),
LintId::of(question_mark::QUESTION_MARK), LintId::of(question_mark::QUESTION_MARK),
LintId::of(ranges::MANUAL_RANGE_CONTAINS), 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(repeat_once::REPEAT_ONCE),
LintId::of(returns::LET_AND_RETURN), LintId::of(returns::LET_AND_RETURN),
LintId::of(returns::NEEDLESS_RETURN), LintId::of(returns::NEEDLESS_RETURN),
LintId::of(self_assignment::SELF_ASSIGNMENT),
LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS), LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS),
LintId::of(serde_api::SERDE_API_MISUSE), LintId::of(serde_api::SERDE_API_MISUSE),
LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), 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(bytes_count_to_len::BYTES_COUNT_TO_LEN),
LintId::of(casts::CHAR_LIT_AS_U8), LintId::of(casts::CHAR_LIT_AS_U8),
LintId::of(casts::UNNECESSARY_CAST), LintId::of(casts::UNNECESSARY_CAST),
LintId::of(dereference::EXPLICIT_AUTO_DEREF),
LintId::of(derivable_impls::DERIVABLE_IMPLS), LintId::of(derivable_impls::DERIVABLE_IMPLS),
LintId::of(double_comparison::DOUBLE_COMPARISONS),
LintId::of(double_parens::DOUBLE_PARENS), LintId::of(double_parens::DOUBLE_PARENS),
LintId::of(duration_subsec::DURATION_SUBSEC),
LintId::of(explicit_write::EXPLICIT_WRITE), LintId::of(explicit_write::EXPLICIT_WRITE),
LintId::of(format::USELESS_FORMAT), LintId::of(format::USELESS_FORMAT),
LintId::of(functions::TOO_MANY_ARGUMENTS), LintId::of(functions::TOO_MANY_ARGUMENTS),
LintId::of(identity_op::IDENTITY_OP),
LintId::of(int_plus_one::INT_PLUS_ONE), LintId::of(int_plus_one::INT_PLUS_ONE),
LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES), LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
LintId::of(lifetimes::NEEDLESS_LIFETIMES), LintId::of(lifetimes::NEEDLESS_LIFETIMES),
LintId::of(loops::EXPLICIT_COUNTER_LOOP), LintId::of(loops::EXPLICIT_COUNTER_LOOP),
LintId::of(loops::MANUAL_FIND),
LintId::of(loops::MANUAL_FLATTEN), LintId::of(loops::MANUAL_FLATTEN),
LintId::of(loops::SINGLE_ELEMENT_LOOP), LintId::of(loops::SINGLE_ELEMENT_LOOP),
LintId::of(loops::WHILE_LET_LOOP), LintId::of(loops::WHILE_LET_LOOP),
LintId::of(manual_rem_euclid::MANUAL_REM_EUCLID),
LintId::of(manual_strip::MANUAL_STRIP), LintId::of(manual_strip::MANUAL_STRIP),
LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN), LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
LintId::of(map_unit_fn::RESULT_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(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
LintId::of(no_effect::NO_EFFECT), LintId::of(no_effect::NO_EFFECT),
LintId::of(no_effect::UNNECESSARY_OPERATION), 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(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL), LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
LintId::of(precedence::PRECEDENCE), LintId::of(precedence::PRECEDENCE),

View File

@@ -3,14 +3,11 @@
// Manual edits will be overwritten. // Manual edits will be overwritten.
store.register_group(true, "clippy::correctness", Some("clippy_correctness"), vec![ 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(approx_const::APPROX_CONSTANT),
LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC), LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC),
LintId::of(attrs::DEPRECATED_SEMVER), LintId::of(attrs::DEPRECATED_SEMVER),
LintId::of(attrs::MISMATCHED_TARGET_OS), LintId::of(attrs::MISMATCHED_TARGET_OS),
LintId::of(attrs::USELESS_ATTRIBUTE), 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(booleans::LOGIC_BUG),
LintId::of(casts::CAST_REF_TO_MUT), LintId::of(casts::CAST_REF_TO_MUT),
LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES), 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::FORGET_REF),
LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS), LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS),
LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT), 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(format_impl::RECURSIVE_FORMAT_IMPL),
LintId::of(formatting::POSSIBLE_MISSING_COMMA), LintId::of(formatting::POSSIBLE_MISSING_COMMA),
LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF), 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::UNINIT_ASSUMED_INIT),
LintId::of(methods::ZST_OFFSET), LintId::of(methods::ZST_OFFSET),
LintId::of(minmax::MIN_MAX), 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(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), 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(option_env_unwrap::OPTION_ENV_UNWRAP),
LintId::of(ptr::INVALID_NULL_PTR_USAGE), LintId::of(ptr::INVALID_NULL_PTR_USAGE),
LintId::of(ptr::MUT_FROM_REF), LintId::of(ptr::MUT_FROM_REF),
LintId::of(ranges::REVERSED_EMPTY_RANGES), LintId::of(ranges::REVERSED_EMPTY_RANGES),
LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC), LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC),
LintId::of(regex::INVALID_REGEX), LintId::of(regex::INVALID_REGEX),
LintId::of(self_assignment::SELF_ASSIGNMENT),
LintId::of(serde_api::SERDE_API_MISUSE), LintId::of(serde_api::SERDE_API_MISUSE),
LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT), LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
LintId::of(swap::ALMOST_SWAPPED), 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::CLIPPY_LINTS_INTERNAL),
LintId::of(utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS), LintId::of(utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
LintId::of(utils::internal_lints::COMPILER_LINT_FUNCTIONS), 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::DEFAULT_LINT),
LintId::of(utils::internal_lints::IF_CHAIN_STYLE), LintId::of(utils::internal_lints::IF_CHAIN_STYLE),
LintId::of(utils::internal_lints::INTERNING_DEFINED_SYMBOL), LintId::of(utils::internal_lints::INTERNING_DEFINED_SYMBOL),

View File

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

View File

@@ -4,7 +4,6 @@
store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
LintId::of(attrs::INLINE_ALWAYS), LintId::of(attrs::INLINE_ALWAYS),
LintId::of(bit_mask::VERBOSE_BIT_MASK),
LintId::of(borrow_as_ptr::BORROW_AS_PTR), LintId::of(borrow_as_ptr::BORROW_AS_PTR),
LintId::of(bytecount::NAIVE_BYTECOUNT), LintId::of(bytecount::NAIVE_BYTECOUNT),
LintId::of(case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS), 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::INEFFICIENT_TO_STRING),
LintId::of(methods::MAP_UNWRAP_OR), LintId::of(methods::MAP_UNWRAP_OR),
LintId::of(methods::UNNECESSARY_JOIN), LintId::of(methods::UNNECESSARY_JOIN),
LintId::of(misc::FLOAT_CMP),
LintId::of(misc::USED_UNDERSCORE_BINDING), LintId::of(misc::USED_UNDERSCORE_BINDING),
LintId::of(mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER), LintId::of(mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER),
LintId::of(mut_mut::MUT_MUT), LintId::of(mut_mut::MUT_MUT),
LintId::of(needless_bitwise_bool::NEEDLESS_BITWISE_BOOL),
LintId::of(needless_continue::NEEDLESS_CONTINUE), LintId::of(needless_continue::NEEDLESS_CONTINUE),
LintId::of(needless_for_each::NEEDLESS_FOR_EACH), LintId::of(needless_for_each::NEEDLESS_FOR_EACH),
LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE), LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE),
LintId::of(no_effect::NO_EFFECT_UNDERSCORE_BINDING), LintId::of(no_effect::NO_EFFECT_UNDERSCORE_BINDING),
LintId::of(non_expressive_names::MANY_SINGLE_CHAR_NAMES), LintId::of(non_expressive_names::MANY_SINGLE_CHAR_NAMES),
LintId::of(non_expressive_names::SIMILAR_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::LARGE_TYPES_PASSED_BY_VALUE),
LintId::of(pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF), LintId::of(pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF),
LintId::of(ranges::RANGE_MINUS_ONE), LintId::of(ranges::RANGE_MINUS_ONE),

View File

@@ -13,6 +13,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
LintId::of(loops::MANUAL_MEMCPY), LintId::of(loops::MANUAL_MEMCPY),
LintId::of(loops::MISSING_SPIN_LOOP), LintId::of(loops::MISSING_SPIN_LOOP),
LintId::of(loops::NEEDLESS_COLLECT), LintId::of(loops::NEEDLESS_COLLECT),
LintId::of(manual_retain::MANUAL_RETAIN),
LintId::of(methods::EXPECT_FUN_CALL), LintId::of(methods::EXPECT_FUN_CALL),
LintId::of(methods::EXTEND_WITH_DRAIN), LintId::of(methods::EXTEND_WITH_DRAIN),
LintId::of(methods::ITER_NTH), 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::OR_FUN_CALL),
LintId::of(methods::SINGLE_CHAR_PATTERN), LintId::of(methods::SINGLE_CHAR_PATTERN),
LintId::of(methods::UNNECESSARY_TO_OWNED), LintId::of(methods::UNNECESSARY_TO_OWNED),
LintId::of(misc::CMP_OWNED), LintId::of(operators::CMP_OWNED),
LintId::of(redundant_clone::REDUNDANT_CLONE), LintId::of(redundant_clone::REDUNDANT_CLONE),
LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
LintId::of(types::BOX_COLLECTION), 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(implicit_return::IMPLICIT_RETURN),
LintId::of(indexing_slicing::INDEXING_SLICING), LintId::of(indexing_slicing::INDEXING_SLICING),
LintId::of(inherent_impl::MULTIPLE_INHERENT_IMPL), LintId::of(inherent_impl::MULTIPLE_INHERENT_IMPL),
LintId::of(integer_division::INTEGER_DIVISION),
LintId::of(large_include_file::LARGE_INCLUDE_FILE), LintId::of(large_include_file::LARGE_INCLUDE_FILE),
LintId::of(let_underscore::LET_UNDERSCORE_MUST_USE), LintId::of(let_underscore::LET_UNDERSCORE_MUST_USE),
LintId::of(literal_representation::DECIMAL_LITERAL_REPRESENTATION), 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::FILETYPE_IS_FILE),
LintId::of(methods::GET_UNWRAP), LintId::of(methods::GET_UNWRAP),
LintId::of(methods::UNWRAP_USED), LintId::of(methods::UNWRAP_USED),
LintId::of(misc::FLOAT_CMP_CONST),
LintId::of(misc_early::SEPARATED_LITERAL_SUFFIX), LintId::of(misc_early::SEPARATED_LITERAL_SUFFIX),
LintId::of(misc_early::UNNEEDED_FIELD_PATTERN), LintId::of(misc_early::UNNEEDED_FIELD_PATTERN),
LintId::of(misc_early::UNSEPARATED_LITERAL_SUFFIX), 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(mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION),
LintId::of(module_style::MOD_MODULE_FILES), LintId::of(module_style::MOD_MODULE_FILES),
LintId::of(module_style::SELF_NAMED_MODULE_FILES), LintId::of(module_style::SELF_NAMED_MODULE_FILES),
LintId::of(modulo_arithmetic::MODULO_ARITHMETIC), LintId::of(operators::FLOAT_ARITHMETIC),
LintId::of(numeric_arithmetic::FLOAT_ARITHMETIC), LintId::of(operators::FLOAT_CMP_CONST),
LintId::of(numeric_arithmetic::INTEGER_ARITHMETIC), 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_in_result_fn::PANIC_IN_RESULT_FN),
LintId::of(panic_unimplemented::PANIC), LintId::of(panic_unimplemented::PANIC),
LintId::of(panic_unimplemented::TODO), LintId::of(panic_unimplemented::TODO),

View File

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

View File

@@ -4,7 +4,6 @@
store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec![ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec![
LintId::of(almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE), 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(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE), LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE),
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), 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::DROP_NON_DROP),
LintId::of(drop_forget_ref::FORGET_NON_DROP), LintId::of(drop_forget_ref::FORGET_NON_DROP),
LintId::of(duplicate_mod::DUPLICATE_MOD), 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(format_impl::PRINT_IN_FORMAT_IMPL),
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
LintId::of(formatting::SUSPICIOUS_ELSE_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(methods::SUSPICIOUS_MAP),
LintId::of(mut_key::MUTABLE_KEY_TYPE), LintId::of(mut_key::MUTABLE_KEY_TYPE),
LintId::of(octal_escapes::OCTAL_ESCAPES), 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(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT),
LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),

View File

@@ -7,6 +7,7 @@
#![feature(let_chains)] #![feature(let_chains)]
#![feature(let_else)] #![feature(let_else)]
#![feature(lint_reasons)] #![feature(lint_reasons)]
#![feature(never_type)]
#![feature(once_cell)] #![feature(once_cell)]
#![feature(rustc_private)] #![feature(rustc_private)]
#![feature(stmt_expr_attributes)] #![feature(stmt_expr_attributes)]
@@ -52,6 +53,7 @@ extern crate clippy_utils;
use clippy_utils::parse_msrv; use clippy_utils::parse_msrv;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_lint::LintId; use rustc_lint::LintId;
use rustc_semver::RustcVersion;
use rustc_session::Session; use rustc_session::Session;
/// Macro used to declare a Clippy lint. /// Macro used to declare a Clippy lint.
@@ -159,25 +161,22 @@ macro_rules! declare_clippy_lint {
} }
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
mod deprecated_lints; pub mod deprecated_lints;
#[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))] #[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))]
mod utils; mod utils;
mod renamed_lints; mod renamed_lints;
// begin lints modules, do not remove this comment, its used in `update_lints` // begin lints modules, do not remove this comment, its used in `update_lints`
mod absurd_extreme_comparisons;
mod almost_complete_letter_range; mod almost_complete_letter_range;
mod approx_const; mod approx_const;
mod as_conversions; mod as_conversions;
mod as_underscore; mod as_underscore;
mod asm_syntax; mod asm_syntax;
mod assertions_on_constants; mod assertions_on_constants;
mod assign_ops;
mod async_yields_async; mod async_yields_async;
mod attrs; mod attrs;
mod await_holding_invalid; mod await_holding_invalid;
mod bit_mask;
mod blacklisted_name; mod blacklisted_name;
mod blocks_in_if_conditions; mod blocks_in_if_conditions;
mod bool_assert_comparison; mod bool_assert_comparison;
@@ -199,6 +198,7 @@ mod crate_in_macro_def;
mod create_dir; mod create_dir;
mod dbg_macro; mod dbg_macro;
mod default; mod default;
mod default_instead_of_iter_empty;
mod default_numeric_fallback; mod default_numeric_fallback;
mod default_union_representation; mod default_union_representation;
mod dereference; mod dereference;
@@ -209,11 +209,9 @@ mod disallowed_script_idents;
mod disallowed_types; mod disallowed_types;
mod doc; mod doc;
mod doc_link_with_quotes; mod doc_link_with_quotes;
mod double_comparison;
mod double_parens; mod double_parens;
mod drop_forget_ref; mod drop_forget_ref;
mod duplicate_mod; mod duplicate_mod;
mod duration_subsec;
mod else_if_without_else; mod else_if_without_else;
mod empty_drop; mod empty_drop;
mod empty_enum; mod empty_enum;
@@ -221,9 +219,7 @@ mod empty_structs_with_brackets;
mod entry; mod entry;
mod enum_clike; mod enum_clike;
mod enum_variants; mod enum_variants;
mod eq_op;
mod equatable_if_let; mod equatable_if_let;
mod erasing_op;
mod escape; mod escape;
mod eta_reduction; mod eta_reduction;
mod excessive_bools; mod excessive_bools;
@@ -231,7 +227,6 @@ mod exhaustive_items;
mod exit; mod exit;
mod explicit_write; mod explicit_write;
mod fallible_impl_from; mod fallible_impl_from;
mod float_equality_without_abs;
mod float_literal; mod float_literal;
mod floating_point_arithmetic; mod floating_point_arithmetic;
mod format; mod format;
@@ -244,7 +239,6 @@ mod from_str_radix_10;
mod functions; mod functions;
mod future_not_send; mod future_not_send;
mod get_first; mod get_first;
mod identity_op;
mod if_let_mutex; mod if_let_mutex;
mod if_not_else; mod if_not_else;
mod if_then_some_else_none; mod if_then_some_else_none;
@@ -260,7 +254,6 @@ mod inherent_to_string;
mod init_numbered_fields; mod init_numbered_fields;
mod inline_fn_without_body; mod inline_fn_without_body;
mod int_plus_one; mod int_plus_one;
mod integer_division;
mod invalid_upcast_comparisons; mod invalid_upcast_comparisons;
mod items_after_statements; mod items_after_statements;
mod iter_not_returning_iterator; mod iter_not_returning_iterator;
@@ -281,6 +274,8 @@ mod manual_async_fn;
mod manual_bits; mod manual_bits;
mod manual_non_exhaustive; mod manual_non_exhaustive;
mod manual_ok_or; mod manual_ok_or;
mod manual_rem_euclid;
mod manual_retain;
mod manual_strip; mod manual_strip;
mod map_clone; mod map_clone;
mod map_err_ignore; mod map_err_ignore;
@@ -300,7 +295,6 @@ mod missing_enforced_import_rename;
mod missing_inline; mod missing_inline;
mod mixed_read_write_in_expression; mod mixed_read_write_in_expression;
mod module_style; mod module_style;
mod modulo_arithmetic;
mod mut_key; mod mut_key;
mod mut_mut; mod mut_mut;
mod mut_mutex_lock; mod mut_mutex_lock;
@@ -308,7 +302,6 @@ mod mut_reference;
mod mutable_debug_assertion; mod mutable_debug_assertion;
mod mutex_atomic; mod mutex_atomic;
mod needless_arbitrary_self_type; mod needless_arbitrary_self_type;
mod needless_bitwise_bool;
mod needless_bool; mod needless_bool;
mod needless_borrowed_ref; mod needless_borrowed_ref;
mod needless_continue; mod needless_continue;
@@ -327,10 +320,10 @@ mod non_expressive_names;
mod non_octal_unix_permissions; mod non_octal_unix_permissions;
mod non_send_fields_in_send_ty; mod non_send_fields_in_send_ty;
mod nonstandard_macro_braces; mod nonstandard_macro_braces;
mod numeric_arithmetic;
mod octal_escapes; mod octal_escapes;
mod only_used_in_recursion; mod only_used_in_recursion;
mod open_options; mod open_options;
mod operators;
mod option_env_unwrap; mod option_env_unwrap;
mod option_if_let_else; mod option_if_let_else;
mod overflow_check_conditional; mod overflow_check_conditional;
@@ -342,7 +335,6 @@ mod path_buf_push_overwrite;
mod pattern_type_mismatch; mod pattern_type_mismatch;
mod precedence; mod precedence;
mod ptr; mod ptr;
mod ptr_eq;
mod ptr_offset_with_cast; mod ptr_offset_with_cast;
mod pub_use; mod pub_use;
mod question_mark; mod question_mark;
@@ -363,7 +355,6 @@ mod repeat_once;
mod return_self_not_must_use; mod return_self_not_must_use;
mod returns; mod returns;
mod same_name_method; mod same_name_method;
mod self_assignment;
mod self_named_constructors; mod self_named_constructors;
mod semicolon_if_nothing_returned; mod semicolon_if_nothing_returned;
mod serde_api; 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 })); 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)] #[doc(hidden)]
pub fn read_conf(sess: &Session) -> Conf { pub fn read_conf(sess: &Session) -> Conf {
let file_name = match utils::conf::lookup_conf_file() { 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); 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 // all conf errors are non-fatal, we just use the default conf in case of error
for error in errors { for error in errors {
sess.struct_err(&format!( sess.err(&format!(
"error reading Clippy's configuration file `{}`: {}", "error reading Clippy's configuration file `{}`: {}",
file_name.display(), file_name.display(),
format_error(error) format_error(error)
)) ));
.emit();
} }
conf 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(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(enum_clike::UnportableVariant));
store.register_late_pass(|| Box::new(float_literal::FloatLiteral)); 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::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::NeedlessBool));
store.register_late_pass(|| Box::new(needless_bool::BoolComparison)); 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(needless_for_each::NeedlessForEach));
store.register_late_pass(|| Box::new(misc::MiscLints)); store.register_late_pass(|| Box::new(misc::MiscLints));
store.register_late_pass(|| Box::new(eta_reduction::EtaReduction)); 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_mut::MutMut));
store.register_late_pass(|| Box::new(mut_reference::UnnecessaryMutPassed)); store.register_late_pass(|| Box::new(mut_reference::UnnecessaryMutPassed));
store.register_late_pass(|| Box::new(len_zero::LenZero)); 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_late_pass(|| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports)); store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
let msrv = conf.msrv.as_ref().and_then(|s| { let msrv = read_msrv(conf, sess);
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 avoid_breaking_exported_api = conf.avoid_breaking_exported_api; let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
let allow_expect_in_tests = conf.allow_expect_in_tests; let allow_expect_in_tests = conf.allow_expect_in_tests;
let allow_unwrap_in_tests = conf.allow_unwrap_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(borrow_deref_ref::BorrowDerefRef));
store.register_late_pass(|| Box::new(no_effect::NoEffect)); store.register_late_pass(|| Box::new(no_effect::NoEffect));
store.register_late_pass(|| Box::new(temporary_assignment::TemporaryAssignment)); 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; let cognitive_complexity_threshold = conf.cognitive_complexity_threshold;
store.register_late_pass(move || { store.register_late_pass(move || {
Box::new(cognitive_complexity::CognitiveComplexity::new( 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(derivable_impls::DerivableImpls));
store.register_late_pass(|| Box::new(drop_forget_ref::DropForgetRef)); 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(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(invalid_upcast_comparisons::InvalidUpcastComparisons));
store.register_late_pass(|| Box::new(regex::Regex)); store.register_late_pass(|| Box::new(regex::Regex));
store.register_late_pass(|| Box::new(copies::CopyAndPaste)); 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(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(neg_multiply::NegMultiply));
store.register_late_pass(|| Box::new(mem_forget::MemForget)); 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(let_if_seq::LetIfSeq));
store.register_late_pass(|| Box::new(mixed_read_write_in_expression::EvalOrderDependence)); store.register_late_pass(|| Box::new(mixed_read_write_in_expression::EvalOrderDependence));
store.register_late_pass(|| Box::new(missing_doc::MissingDoc::new())); 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(useless_conversion::UselessConversion::default()));
store.register_late_pass(|| Box::new(implicit_hasher::ImplicitHasher)); 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(fallible_impl_from::FallibleImplFrom));
store.register_late_pass(|| Box::new(double_comparison::DoubleComparisons));
store.register_late_pass(|| Box::new(question_mark::QuestionMark)); store.register_late_pass(|| Box::new(question_mark::QuestionMark));
store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings)); store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings));
store.register_late_pass(|| Box::new(suspicious_trait_impl::SuspiciousImpl)); 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(inherent_impl::MultipleInherentImpl));
store.register_late_pass(|| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd)); 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(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(indexing_slicing::IndexingSlicing));
store.register_late_pass(|| Box::new(non_copy_const::NonCopyConst)); store.register_late_pass(|| Box::new(non_copy_const::NonCopyConst));
store.register_late_pass(|| Box::new(ptr_offset_with_cast::PtrOffsetWithCast)); 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(assertions_on_constants::AssertionsOnConstants));
store.register_late_pass(|| Box::new(transmuting_null::TransmutingNull)); 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(path_buf_push_overwrite::PathBufPushOverwrite));
store.register_late_pass(|| Box::new(integer_division::IntegerDivision));
store.register_late_pass(|| Box::new(inherent_to_string::InherentToString)); store.register_late_pass(|| Box::new(inherent_to_string::InherentToString));
let max_trait_bounds = conf.max_trait_bounds; 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(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(comparison_chain::ComparisonChain));
store.register_late_pass(|| Box::new(mut_key::MutableKeyType)); 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(reference::DerefAddrOf));
store.register_early_pass(|| Box::new(double_parens::DoubleParens)); store.register_early_pass(|| Box::new(double_parens::DoubleParens));
store.register_late_pass(|| Box::new(format_impl::FormatImpl::new())); 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(stable_sort_primitive::StableSortPrimitive));
store.register_late_pass(|| Box::new(repeat_once::RepeatOnce)); 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(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(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(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
store.register_late_pass(|| Box::new(async_yields_async::AsyncYieldsAsync)); store.register_late_pass(|| Box::new(async_yields_async::AsyncYieldsAsync));
let disallowed_methods = conf.disallowed_methods.clone(); 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(mismatching_type_param_order::TypeParamMismatch));
store.register_late_pass(|| Box::new(as_underscore::AsUnderscore)); 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(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` // add lints here, do not remove this comment, it's used in `new_lint`
} }

View File

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

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_kv_map;
mod for_loops_over_fallibles; mod for_loops_over_fallibles;
mod iter_next_loop; mod iter_next_loop;
mod manual_find;
mod manual_flatten; mod manual_flatten;
mod manual_memcpy; mod manual_memcpy;
mod missing_spin_loop; mod missing_spin_loop;
@@ -346,7 +347,14 @@ declare_clippy_lint! {
/// ///
/// ### Example /// ### Example
/// ```ignore /// ```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" "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 => [ declare_lint_pass!(Loops => [
MANUAL_MEMCPY, MANUAL_MEMCPY,
MANUAL_FLATTEN, MANUAL_FLATTEN,
@@ -622,6 +661,7 @@ declare_lint_pass!(Loops => [
SAME_ITEM_PUSH, SAME_ITEM_PUSH,
SINGLE_ELEMENT_LOOP, SINGLE_ELEMENT_LOOP,
MISSING_SPIN_LOOP, MISSING_SPIN_LOOP,
MANUAL_FIND,
]); ]);
impl<'tcx> LateLintPass<'tcx> for Loops { impl<'tcx> LateLintPass<'tcx> for Loops {
@@ -696,6 +736,7 @@ fn check_for_loop<'tcx>(
single_element_loop::check(cx, pat, arg, body, expr); single_element_loop::check(cx, pat, arg, body, expr);
same_item_push::check(cx, pat, arg, body, expr); same_item_push::check(cx, pat, arg, body, expr);
manual_flatten::check(cx, pat, arg, body, span); 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<'_>) { 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::diagnostics::span_lint_and_sugg;
use clippy_utils::higher; use clippy_utils::higher;
use clippy_utils::source::snippet_with_applicability; 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_errors::Applicability;
use rustc_hir::{Block, Expr, ExprKind, MatchSource, Pat, StmtKind}; use rustc_hir::{Block, Expr, ExprKind, Local, MatchSource, Pat, StmtKind};
use rustc_lint::{LateContext, LintContext}; use rustc_lint::LateContext;
use rustc_middle::lint::in_external_macro;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { 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 (init, has_trailing_exprs) = match (loop_block.stmts, loop_block.expr) {
let inner_stmt_expr = extract_expr_from_first_stmt(loop_block); ([stmt, stmts @ ..], expr) => {
// or extract the first expression (if any) from the block if let StmtKind::Local(&Local { init: Some(e), .. }) | StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind {
if let Some(inner) = inner_stmt_expr.or_else(|| extract_first_expr(loop_block)) { (e, !stmts.is_empty() || expr.is_some())
if let Some(higher::IfLet { } else {
let_pat, return;
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);
} }
}
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 /// Returns `true` if expr contains a single break expression without a label or eub-expression.
/// and fn is_simple_break_expr(e: &Expr<'_>) -> bool {
/// passed expression. The expression may be within a block. matches!(peel_blocks(e).kind, ExprKind::Break(dest, None) if dest.label.is_none())
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, /// Removes any blocks containing only a single expression.
ExprKind::Block(b, _) => extract_first_expr(b).map_or(false, is_simple_break_expr), fn peel_blocks<'tcx>(e: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
_ => false, 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<'_>, expr: &'tcx Expr<'_>,
let_pat: &'tcx Pat<'_>, let_pat: &'tcx Pat<'_>,
let_expr: &'tcx Expr<'_>, 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; 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 clippy_utils::source::snippet;
use hir::def::{DefKind, Res}; use hir::def::{DefKind, Res};
use if_chain::if_chain; use if_chain::if_chain;
@@ -50,8 +50,9 @@ impl MacroRefData {
#[derive(Default)] #[derive(Default)]
#[expect(clippy::module_name_repetitions)] #[expect(clippy::module_name_repetitions)]
pub struct MacroUseImports { pub struct MacroUseImports {
/// the actual import path used and the span of the attribute above it. /// the actual import path used and the span of the attribute above it. The value is
imports: Vec<(String, Span)>, /// 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. /// the span of the macro reference, kept to ensure only one reference is used per macro call.
collected: FxHashSet<Span>, collected: FxHashSet<Span>,
mac_refs: Vec<MacroRefData>, mac_refs: Vec<MacroRefData>,
@@ -90,7 +91,8 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
if_chain! { if_chain! {
if cx.sess().opts.edition >= Edition::Edition2018; if cx.sess().opts.edition >= Edition::Edition2018;
if let hir::ItemKind::Use(path, _kind) = &item.kind; 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 Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use));
if let Res::Def(DefKind::Mod, id) = path.res; if let Res::Def(DefKind::Mod, id) = path.res;
if !id.is_local(); if !id.is_local();
@@ -99,7 +101,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res { if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
let span = mac_attr.span; let span = mac_attr.span;
let def_path = cx.tcx.def_path_str(mac_id); 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 { } else {
@@ -137,7 +139,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
fn check_crate_post(&mut self, cx: &LateContext<'_>) { fn check_crate_post(&mut self, cx: &LateContext<'_>) {
let mut used = FxHashMap::default(); let mut used = FxHashMap::default();
let mut check_dup = vec![]; 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)); let found_idx = self.mac_refs.iter().position(|mac| import.ends_with(&mac.name));
if let Some(idx) = found_idx { if let Some(idx) = found_idx {
@@ -150,7 +152,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
[] | [_] => return, [] | [_] => return,
[root, item] => { [root, item] => {
if !check_dup.contains(&(*item).to_string()) { 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) .or_insert_with(Vec::new)
.push((*item).to_string()); .push((*item).to_string());
check_dup.push((*item).to_string()); check_dup.push((*item).to_string());
@@ -168,13 +170,13 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
used.entry(((*root).to_string(), span)) used.entry(((*root).to_string(), span, hir_id))
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push(filtered.join("::")); .push(filtered.join("::"));
check_dup.extend(filtered); check_dup.extend(filtered);
} else { } else {
let rest = rest.to_vec(); let rest = rest.to_vec();
used.entry(((*root).to_string(), span)) used.entry(((*root).to_string(), span, hir_id))
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push(rest.join("::")); .push(rest.join("::"));
check_dup.extend(rest.iter().map(ToString::to_string)); check_dup.extend(rest.iter().map(ToString::to_string));
@@ -185,28 +187,34 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
} }
let mut suggestions = vec![]; let mut suggestions = vec![];
for ((root, span), path) in used { for ((root, span, hir_id), path) in used {
if path.len() == 1 { if path.len() == 1 {
suggestions.push((span, format!("{}::{}", root, path[0]))); suggestions.push((span, format!("{}::{}", root, path[0]), hir_id));
} else { } 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 // 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. // such as `std::prelude::v1::foo` or some other macro that expands to an import.
if self.mac_refs.is_empty() { if self.mac_refs.is_empty() {
for (span, import) in suggestions { for (span, import, hir_id) in suggestions {
let help = format!("use {};", import); let help = format!("use {};", import);
span_lint_and_sugg( span_lint_hir_and_then(
cx, cx,
MACRO_USE_IMPORTS, MACRO_USE_IMPORTS,
*hir_id,
*span, *span,
"`macro_use` attributes are no longer needed in the Rust 2018 edition", "`macro_use` attributes are no longer needed in the Rust 2018 edition",
|diag| {
diag.span_suggestion(
*span,
"remove the attribute and import the macro directly, try", "remove the attribute and import the macro directly, try",
help, help,
Applicability::MaybeIncorrect, 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::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_ast::ast::{self, VisibilityKind};
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@@ -190,12 +190,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
!self !self
.constructed_enum_variants .constructed_enum_variants
.contains(&(enum_id.to_def_id(), variant_id.to_def_id())) .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, cx,
MANUAL_NON_EXHAUSTIVE, MANUAL_NON_EXHAUSTIVE,
hir_id,
enum_span, enum_span,
"this seems like a manual implementation of the non-exhaustive pattern", "this seems like a manual implementation of the non-exhaustive pattern",
|diag| { |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. // TODO: Handle negative integers. They're currently treated as a wild match.
ExprKind::Lit(lit) => match lit.node { ExprKind::Lit(lit) => match lit.node {
LitKind::Str(sym, _) => Self::LitStr(sym), 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::Byte(val) => Self::LitInt(val.into()),
LitKind::Char(val) => Self::LitInt(val.into()), LitKind::Char(val) => Self::LitInt(val.into()),
LitKind::Int(val, _) => Self::LitInt(val), 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, cx,
(ex, expr), (ex, expr),
(bind_names, matched_vars), (bind_names, matched_vars),
&*snippet_body, &snippet_body,
&mut applicability, &mut applicability,
Some(span), Some(span),
); );
@@ -88,7 +88,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
cx, cx,
(ex, expr), (ex, expr),
(bind_names, matched_vars), (bind_names, matched_vars),
&*snippet_body, &snippet_body,
&mut applicability, &mut applicability,
None, None,
); );

View File

@@ -118,7 +118,7 @@ fn lint(cx: &LateContext<'_>, case_method: &CaseMethod, bad_case_span: Span, bad
MATCH_STR_CASE_MISMATCH, MATCH_STR_CASE_MISMATCH,
bad_case_span, bad_case_span,
"this `match` arm has a differing case than its expression", "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), format!("\"{}\"", suggestion),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );

View File

@@ -791,7 +791,7 @@ declare_clippy_lint! {
/// the match block and thus will not unlock. /// the match block and thus will not unlock.
/// ///
/// ### Example /// ### Example
/// ```rust.ignore /// ```rust,ignore
/// # use std::sync::Mutex; /// # use std::sync::Mutex;
/// ///
/// # struct State {} /// # struct State {}
@@ -963,7 +963,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
return; return;
} }
if matches!(source, MatchSource::Normal | MatchSource::ForLoopDesugar) { 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); 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::source::snippet;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::ty::needs_ordered_drop; use clippy_utils::ty::needs_ordered_drop;
use clippy_utils::{higher, match_def_path}; use clippy_utils::visitors::any_temporaries_need_ordered_drop;
use clippy_utils::{is_lang_ctor, is_trait_method, paths}; use clippy_utils::{higher, is_lang_ctor, is_trait_method, match_def_path, paths};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::ast::LitKind; use rustc_ast::ast::LitKind;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, PollPending}; use rustc_hir::LangItem::{OptionNone, PollPending};
use rustc_hir::{ use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp};
intravisit::{walk_expr, Visitor},
Arm, Block, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp,
};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty}; use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty};
use rustc_span::sym; 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>( fn find_sugg_for_if_let<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>, 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. // 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 // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
// for the duration if body. // 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 // check that `while_let_on_iterator` lint does not trigger
if_chain! { if_chain! {
@@ -362,9 +286,9 @@ fn find_good_method_for_match<'a>(
.qpath_res(path_right, arms[1].pat.hir_id) .qpath_res(path_right, arms[1].pat.hir_id)
.opt_def_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) { 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) { } 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 { } else {
return None; return None;
}; };

View File

@@ -1,10 +1,10 @@
use crate::FxHashSet; use crate::FxHashSet;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::get_attr;
use clippy_utils::source::{indent_of, snippet}; use clippy_utils::source::{indent_of, snippet};
use clippy_utils::{get_attr, is_lint_allowed};
use rustc_errors::{Applicability, Diagnostic}; use rustc_errors::{Applicability, Diagnostic};
use rustc_hir::intravisit::{walk_expr, Visitor}; 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_lint::{LateContext, LintContext};
use rustc_middle::ty::subst::GenericArgKind; use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty::{Ty, TypeAndMut}; use rustc_middle::ty::{Ty, TypeAndMut};
@@ -16,12 +16,23 @@ pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>, expr: &'tcx Expr<'tcx>,
scrutinee: &'tcx Expr<'_>, scrutinee: &'tcx Expr<'_>,
arms: &'tcx [Arm<'_>],
source: MatchSource, 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) { if let Some((suggestions, message)) = has_significant_drop_in_scrutinee(cx, scrutinee, source) {
for found in suggestions { for found in suggestions {
span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| { span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| {
set_diagnostic(diag, cx, expr, found); 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); let mut helper = SigDropHelper::new(cx);
helper.find_sig_drop(scrutinee).map(|drops| { helper.find_sig_drop(scrutinee).map(|drops| {
let message = if source == MatchSource::Normal { 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 { } 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) (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> { struct SigDropHelper<'a, 'tcx> {
cx: &'a LateContext<'tcx>, cx: &'a LateContext<'tcx>,
is_chain_end: bool, is_chain_end: bool,
seen_types: FxHashSet<Ty<'tcx>>,
has_significant_drop: bool, has_significant_drop: bool,
current_sig_drop: Option<FoundSigDrop>, current_sig_drop: Option<FoundSigDrop>,
sig_drop_spans: Option<Vec<FoundSigDrop>>, sig_drop_spans: Option<Vec<FoundSigDrop>>,
special_handling_for_binary_op: bool, special_handling_for_binary_op: bool,
sig_drop_checker: SigDropChecker<'a, 'tcx>,
} }
#[expect(clippy::enum_variant_names)] #[expect(clippy::enum_variant_names)]
@@ -118,11 +184,11 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
SigDropHelper { SigDropHelper {
cx, cx,
is_chain_end: true, is_chain_end: true,
seen_types: FxHashSet::default(),
has_significant_drop: false, has_significant_drop: false,
current_sig_drop: None, current_sig_drop: None,
sig_drop_spans: None, sig_drop_spans: None,
special_handling_for_binary_op: false, 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() { if self.current_sig_drop.is_some() {
return; return;
} }
let ty = self.get_type(expr); let ty = self.sig_drop_checker.get_type(expr);
if ty.is_ref() { if ty.is_ref() {
// We checked that the type was ref, so builtin_deref will return Some TypeAndMut, // We checked that the type was ref, so builtin_deref will return Some TypeAndMut,
// but let's avoid any chance of an ICE // 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( fn visit_exprs_for_binary_ops(
&mut self, &mut self,
left: &'tcx Expr<'_>, left: &'tcx Expr<'_>,
@@ -214,44 +272,15 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
self.special_handling_for_binary_op = false; 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> { impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { 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; self.has_significant_drop = true;
return; 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>, ty: Ty<'a>,
els: Option<&Expr<'_>>, els: Option<&Expr<'_>>,
) { ) {
// list of candidate `Enum`s we know will never get any more members // We don't want to lint if the second arm contains an enum which could
let candidates = &[ // have more variants in the future.
(&paths::COW, "Borrowed"), if form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat) {
(&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) {
report_single_pattern(cx, ex, arms, expr, els); report_single_pattern(cx, ex, arms, expr, els);
} }
} }
/// Collects paths and their types from the given patterns. Returns true if the given pattern could /// Returns `true` if all of the types in the pattern are enums which we know
/// be simplified, false otherwise. /// won't be expanded in the future
fn collect_pat_paths<'a>(acc: &mut Vec<(String, Ty<'a>)>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool { 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 { match pat.kind {
PatKind::Wild => true, PatKind::Tuple(inner, _) => inner.iter().for_each(|p| {
PatKind::Tuple(inner, _) => inner.iter().all(|p| {
let p_ty = cx.typeck_results().pat_ty(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, ..) => { PatKind::TupleStruct(..) | PatKind::Binding(BindingAnnotation::Unannotated, .., None) | PatKind::Path(_) => {
let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { acc.push(ty);
s.print_qpath(path, false);
});
acc.push((path, ty));
true
}, },
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 /// Returns true if the given patterns forms only exhaustive matches that don't contain enum
/// patterns without a wildcard. /// 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) { match (&left.kind, &right.kind) {
(PatKind::Wild, _) | (_, PatKind::Wild) => true, (PatKind::Wild, _) | (_, PatKind::Wild) => true,
(PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => { (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 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, _ => false,
} }
} }

View File

@@ -21,7 +21,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_
MAP_FLATTEN, MAP_FLATTEN,
expr.span.with_lo(map_span.lo()), expr.span.with_lo(map_span.lo()),
&format!("called `map(..).flatten()` on `{}`", caller_ty_name), &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), format!("{}({})", method_to_use, closure_snippet),
applicability, 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::source::{snippet, snippet_opt};
use clippy_utils::ty::{implements_trait, is_copy};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::ast::LitKind; use rustc_ast::ast::LitKind;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::FnKind;
use rustc_hir::{ use rustc_hir::{
self as hir, def, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind, Stmt, 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_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::hygiene::DesugaringKind; use rustc_span::hygiene::DesugaringKind;
use rustc_span::source_map::{ExpnKind, Span}; 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::sugg::Sugg;
use clippy_utils::{ use clippy_utils::{get_parent_expr, in_constant, iter_input_pats, last_path_segment, SpanlessEq};
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,
};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@@ -58,122 +51,6 @@ declare_clippy_lint! {
style, style,
"an entire binding declared as `ref`, in a function argument or a `let` statement" "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! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for the use of bindings with a single leading /// Checks for the use of bindings with a single leading
@@ -244,51 +121,11 @@ declare_clippy_lint! {
"using `0 as *{const, mut} T`" "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 => [ declare_lint_pass!(MiscLints => [
TOPLEVEL_REF_ARG, TOPLEVEL_REF_ARG,
CMP_NAN,
FLOAT_CMP,
CMP_OWNED,
MODULO_ONE,
USED_UNDERSCORE_BINDING, USED_UNDERSCORE_BINDING,
SHORT_CIRCUIT_STATEMENT, SHORT_CIRCUIT_STATEMENT,
ZERO_PTR, ZERO_PTR,
FLOAT_CMP_CONST
]); ]);
impl<'tcx> LateLintPass<'tcx> for MiscLints { 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<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
match expr.kind { if let ExprKind::Cast(e, ty) = expr.kind {
ExprKind::Cast(e, ty) => {
check_cast(cx, expr.span, e, ty); check_cast(cx, expr.span, e, ty);
return; return;
},
ExprKind::Binary(ref cmp, left, right) => {
check_binary(cx, expr, cmp, left, right);
return;
},
_ => {},
} }
if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) { if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) {
// Don't lint things expanded by #[derive(...)], etc or `await` desugaring // 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 /// Heuristic to see if an expression is used. Should be compatible with
/// `unused_variables`'s idea /// `unused_variables`'s idea
/// of what it means for an expression to be "used". /// 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

@@ -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::consts::{self, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::has_enclosing_paren;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::util::parser::PREC_PREFIX;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@@ -58,7 +60,12 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) {
then { then {
let mut applicability = Applicability::MachineApplicable; 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( span_lint_and_sugg(
cx, cx,
NEG_MULTIPLY, NEG_MULTIPLY,

View File

@@ -6,6 +6,7 @@ use std::ptr;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::in_constant; use clippy_utils::in_constant;
use clippy_utils::macros::macro_backtrace;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId; 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::adjustment::Adjust;
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint}; 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; use rustc_typeck::hir_ty_to_ty;
// FIXME: this is a correctness problem but there's no suitable // 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<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) {
if let ItemKind::Const(hir_ty, body_id) = it.kind { if let ItemKind::Const(hir_ty, body_id) = it.kind {
let ty = hir_ty_to_ty(cx.tcx, hir_ty); let ty = hir_ty_to_ty(cx.tcx, hir_ty);
if !macro_backtrace(it.span).last().map_or(false, |macro_call| {
if is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, ty) { 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 }); 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] #[must_use]
fn get_exemptions(interned_name: &str) -> Option<&'static [&'static str]> { fn get_exemptions(interned_name: &str) -> Option<&'static [&'static str]> {
for &list in ALLOWED_TO_BE_SIMILAR { ALLOWED_TO_BE_SIMILAR
if allowed_to_be_similar(interned_name, list) { .iter()
return Some(list); .find(|&&list| allowed_to_be_similar(interned_name, list))
} .copied()
}
None
} }
#[must_use] #[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 // add the pattern after the expression because the bindings aren't available
// yet in the init // yet in the init
// expression // expression
SimilarNamesNameVisitor(self).visit_pat(&*local.pat); SimilarNamesNameVisitor(self).visit_pat(&local.pat);
} }
fn visit_block(&mut self, blk: &'tcx Block) { fn visit_block(&mut self, blk: &'tcx Block) {
self.single_char_names.push(vec![]); 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_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::LateContext;
use rustc_middle::ty; use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use clippy_utils::comparisons::{normalize_comparison, Rel}; use clippy_utils::comparisons::{normalize_comparison, Rel};
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{constant, Constant};
@@ -10,45 +9,16 @@ use clippy_utils::source::snippet;
use clippy_utils::ty::is_isize_or_usize; use clippy_utils::ty::is_isize_or_usize;
use clippy_utils::{clip, int_bits, unsext}; use clippy_utils::{clip, int_bits, unsext};
declare_clippy_lint! { use super::ABSURD_EXTREME_COMPARISONS;
/// ### 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_lint_pass!(AbsurdExtremeComparisons => [ABSURD_EXTREME_COMPARISONS]); pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
impl<'tcx> LateLintPass<'tcx> for AbsurdExtremeComparisons { expr: &'tcx Expr<'_>,
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { op: BinOpKind,
if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind { lhs: &'tcx Expr<'_>,
if let Some((culprit, result)) = detect_absurd_comparison(cx, cmp.node, lhs, rhs) { rhs: &'tcx Expr<'_>,
if !expr.span.from_expansion() { ) {
if let Some((culprit, result)) = detect_absurd_comparison(cx, op, lhs, rhs) {
let msg = "this comparison involving the minimum or maximum element for this \ let msg = "this comparison involving the minimum or maximum element for this \
type contains a case that is always true or always false"; type contains a case that is always true or always false";
@@ -75,9 +45,6 @@ impl<'tcx> LateLintPass<'tcx> for AbsurdExtremeComparisons {
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);
} }
}
}
}
} }
enum ExtremeType { enum ExtremeType {

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::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::diagnostics::span_lint;
use clippy_utils::sugg::Sugg;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::LateContext;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span; use rustc_span::source_map::Span;
declare_clippy_lint! { use super::{BAD_BIT_MASK, INEFFECTIVE_BIT_MASK};
/// ### 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! { pub(super) fn check<'tcx>(
/// ### What it does cx: &LateContext<'tcx>,
/// Checks for bit masks in comparisons which can be removed e: &'tcx Expr<'_>,
/// without changing the outcome. The basic structure can be seen in the op: BinOpKind,
/// following table: left: &'tcx Expr<'_>,
/// right: &'tcx Expr<'_>,
/// |Comparison| Bit Op |Example |equals | ) {
/// |----------|----------|------------|-------| if op.is_comparison() {
/// |`>` / `<=`|`\|` / `^`|`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) { if let Some(cmp_opt) = fetch_int_literal(cx, right) {
check_compare(cx, left, cmp.node, cmp_opt, e.span); check_compare(cx, left, op, cmp_opt, e.span);
} else if let Some(cmp_val) = fetch_int_literal(cx, left) { } else if let Some(cmp_val) = fetch_int_literal(cx, left) {
check_compare(cx, right, invert_cmp(cmp.node), cmp_val, e.span); check_compare(cx, right, invert_cmp(op), 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,
);
},
);
} }
} }
} }

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,63 +3,42 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{clip, unsext}; use clippy_utils::{clip, unsext};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, Node}; use rustc_hir::{BinOpKind, Expr, ExprKind, Node};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::LateContext;
use rustc_middle::ty; use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span; use rustc_span::source_map::Span;
declare_clippy_lint! { use super::IDENTITY_OP;
/// ### 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_lint_pass!(IdentityOp => [IDENTITY_OP]); pub(crate) fn check<'tcx>(
cx: &LateContext<'tcx>,
impl<'tcx> LateLintPass<'tcx> for IdentityOp { expr: &'tcx Expr<'_>,
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { op: BinOpKind,
if expr.span.from_expansion() { left: &'tcx Expr<'_>,
return; right: &'tcx Expr<'_>,
} ) {
if let ExprKind::Binary(cmp, left, right) = &expr.kind { if !is_allowed(cx, op, left, right) {
if !is_allowed(cx, *cmp, left, right) { match op {
match cmp.node {
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => { BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
check(cx, left, 0, expr.span, right.span, needs_parenthesis(cx, expr, right)); check_op(cx, left, 0, expr.span, right.span, needs_parenthesis(cx, expr, right));
check(cx, right, 0, expr.span, left.span, Parens::Unneeded); check_op(cx, right, 0, expr.span, left.span, Parens::Unneeded);
}, },
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => { BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
check(cx, right, 0, expr.span, left.span, Parens::Unneeded); check_op(cx, right, 0, expr.span, left.span, Parens::Unneeded);
}, },
BinOpKind::Mul => { BinOpKind::Mul => {
check(cx, left, 1, expr.span, right.span, needs_parenthesis(cx, expr, right)); check_op(cx, left, 1, expr.span, right.span, needs_parenthesis(cx, expr, right));
check(cx, right, 1, expr.span, left.span, Parens::Unneeded); check_op(cx, right, 1, expr.span, left.span, Parens::Unneeded);
}, },
BinOpKind::Div => check(cx, right, 1, expr.span, left.span, Parens::Unneeded), BinOpKind::Div => check_op(cx, right, 1, expr.span, left.span, Parens::Unneeded),
BinOpKind::BitAnd => { BinOpKind::BitAnd => {
check(cx, left, -1, expr.span, right.span, needs_parenthesis(cx, expr, right)); check_op(cx, left, -1, expr.span, right.span, needs_parenthesis(cx, expr, right));
check(cx, right, -1, expr.span, left.span, Parens::Unneeded); check_op(cx, right, -1, expr.span, left.span, Parens::Unneeded);
}, },
BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span), BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span),
_ => (), _ => (),
} }
} }
}
}
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@@ -108,12 +87,12 @@ fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>)
Parens::Needed 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 // This lint applies to integers
!cx.typeck_results().expr_ty(left).peel_refs().is_integral() !cx.typeck_results().expr_ty(left).peel_refs().is_integral()
|| !cx.typeck_results().expr_ty(right).peel_refs().is_integral() || !cx.typeck_results().expr_ty(right).peel_refs().is_integral()
// `1 << 0` is a common pattern in bit manipulation code // `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(), right) == Some(Constant::Int(0))
&& constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1))) && 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) { 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() { let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() {
ty::Int(ity) => unsext(cx.tcx, -1_i128, ity), 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::diagnostics::span_lint_and_then;
use clippy_utils::sext; use clippy_utils::sext;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_hir::{BinOpKind, Expr};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use std::fmt::Display; use std::fmt::Display;
declare_clippy_lint! { use super::MODULO_ARITHMETIC;
/// ### 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_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 { struct OperandInfo {
string_representation: Option<String>, 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::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet; 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 clippy_utils::{is_self, is_self_ty};
use core::ops::ControlFlow;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::attr; use rustc_ast::attr;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::FnKind;
use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind}; use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind};
use rustc_lint::{LateContext, LateLintPass}; 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::layout::LayoutOf;
use rustc_middle::ty::{self, RegionKind};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::LocalDefId; use rustc_span::def_id::LocalDefId;
use rustc_span::{sym, Span}; use rustc_span::{sym, Span};
@@ -141,35 +144,62 @@ impl<'tcx> PassByRefOrValue {
} }
let fn_sig = cx.tcx.fn_sig(def_id); 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)); 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... // All spans generated from a proc-macro invocation are the same...
match span { match span {
Some(s) if s == input.span => return, Some(s) if s == input.span => continue,
_ => (), _ => (),
} }
match ty.kind() { match *ty.skip_binder().kind() {
ty::Ref(input_lt, ty, Mutability::Not) => { ty::Ref(lt, ty, Mutability::Not) => {
// Use lifetimes to determine if we're returning a reference to the match lt.kind() {
// argument. In that case we can't switch to pass-by-value as the RegionKind::ReLateBound(index, region)
// argument will not live long enough. if index.as_u32() == 0 && output_regions.contains(&region) =>
let output_lts = match *fn_sig.output().kind() { {
ty::Ref(output_lt, _, _) => vec![output_lt], continue;
ty::Adt(_, substs) => substs.regions().collect(), },
_ => vec![], // 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! { let ty = cx.tcx.erase_late_bound_regions(fn_sig.rebind(ty));
if !output_lts.contains(input_lt); if is_copy(cx, ty)
if is_copy(cx, *ty); && let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes())
if let Some(size) = cx.layout_of(*ty).ok().map(|l| l.size.bytes()); && size <= self.ref_min_size
if size <= self.ref_min_size; && let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind
if let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind; {
then { 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) { let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
"self".into() "self".into()
} else { } else {
@@ -185,7 +215,6 @@ impl<'tcx> PassByRefOrValue {
Applicability::Unspecified, Applicability::Unspecified,
); );
} }
}
}, },
ty::Adt(_, _) | ty::Array(_, _) | ty::Tuple(_) => { ty::Adt(_, _) | ty::Array(_, _) | ty::Tuple(_) => {
@@ -196,6 +225,7 @@ impl<'tcx> PassByRefOrValue {
_ => continue, _ => continue,
} }
} }
let ty = cx.tcx.erase_late_bound_regions(ty);
if_chain! { if_chain! {
if is_copy(cx, ty); if is_copy(cx, ty);

View File

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

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

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); /// let mut vec1 = Vec::with_capacity(len);
/// vec1.resize(len, 0); /// vec1.resize(len, 0);
/// ///
/// let mut vec1 = Vec::with_capacity(len);
/// vec1.resize(vec1.capacity(), 0);
///
/// let mut vec2 = Vec::with_capacity(len); /// let mut vec2 = Vec::with_capacity(len);
/// vec2.extend(repeat(0).take(len)); /// vec2.extend(repeat(0).take(len));
/// ``` /// ```
@@ -211,20 +214,17 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
/// Checks if the given expression is resizing a vector with 0 /// Checks if the given expression is resizing a vector with 0
fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) { fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) {
if_chain! { if self.initialization_found
if self.initialization_found; && let ExprKind::MethodCall(path, [self_arg, len_arg, fill_arg], _) = expr.kind
if let ExprKind::MethodCall(path, [self_arg, len_arg, fill_arg], _) = expr.kind; && path_to_local_id(self_arg, self.vec_alloc.local_id)
if path_to_local_id(self_arg, self.vec_alloc.local_id); && path.ident.name == sym!(resize)
if path.ident.name == sym!(resize);
// Check that is filled with 0 // Check that is filled with 0
if let ExprKind::Lit(ref lit) = fill_arg.kind; && let ExprKind::Lit(ref lit) = fill_arg.kind
if let LitKind::Int(0, _) = lit.node; && let LitKind::Int(0, _) = lit.node {
// Check that len expression is equals to `with_capacity` expression // Check that len expression is equals to `with_capacity` expression
if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr); if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) {
self.slow_expression = Some(InitializationType::Resize(expr));
then { } else if let ExprKind::MethodCall(path, _, _) = len_arg.kind && path.ident.as_str() == "capacity" {
self.slow_expression = Some(InitializationType::Resize(expr)); self.slow_expression = Some(InitializationType::Resize(expr));
} }
} }
@@ -240,12 +240,15 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
if let Some(repeat_expr) = take_args.get(0); if let Some(repeat_expr) = take_args.get(0);
if self.is_repeat_zero(repeat_expr); 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 let Some(len_arg) = take_args.get(1);
if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr);
then { then {
// 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; 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(); /// let x = "Hello".to_owned();
/// x + ", World"; /// x + ", World";
/// ``` /// ```
///
/// Use instead:
/// ```rust
/// let mut x = "Hello".to_owned();
/// x.push_str(", World");
/// ```
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub STRING_ADD, pub STRING_ADD,
restriction, restriction,

View File

@@ -16,9 +16,10 @@ mod wrong_transmute;
use clippy_utils::in_constant; use clippy_utils::in_constant;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass}; 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; use rustc_span::symbol::sym;
declare_clippy_lint! { declare_clippy_lint! {
@@ -385,7 +386,10 @@ declare_clippy_lint! {
"transmute to or from a type with an undefined representation" "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, CROSSPOINTER_TRANSMUTE,
TRANSMUTE_PTR_TO_REF, TRANSMUTE_PTR_TO_REF,
TRANSMUTE_PTR_TO_PTR, TRANSMUTE_PTR_TO_PTR,
@@ -401,13 +405,18 @@ declare_lint_pass!(Transmute => [
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
TRANSMUTE_UNDEFINED_REPR, TRANSMUTE_UNDEFINED_REPR,
]); ]);
impl Transmute {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
}
}
impl<'tcx> LateLintPass<'tcx> for Transmute { impl<'tcx> LateLintPass<'tcx> for Transmute {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::Call(path_expr, [arg]) = e.kind; if let ExprKind::Call(path_expr, [arg]) = e.kind;
if let ExprKind::Path(ref qpath) = path_expr.kind; if let ExprKind::Path(QPath::Resolved(None, path)) = path_expr.kind;
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id(); if let Some(def_id) = path.res.opt_def_id();
if cx.tcx.is_diagnostic_item(sym::transmute, def_id); if cx.tcx.is_diagnostic_item(sym::transmute, def_id);
then { then {
// Avoid suggesting non-const operations in const contexts: // 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) let linted = wrong_transmute::check(cx, e, from_ty, to_ty)
| crosspointer_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_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_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg) | transmute_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 super::TRANSMUTE_PTR_TO_REF;
use clippy_utils::diagnostics::span_lint_and_then; 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_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_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. /// Checks for `transmute_ptr_to_ref` lint.
/// Returns `true` if it's triggered, otherwise returns `false`. /// Returns `true` if it's triggered, otherwise returns `false`.
@@ -15,7 +16,8 @@ pub(super) fn check<'tcx>(
from_ty: Ty<'tcx>, from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>, to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>, arg: &'tcx Expr<'_>,
qpath: &'tcx QPath<'_>, path: &'tcx Path<'_>,
msrv: Option<RustcVersion>,
) -> bool { ) -> bool {
match (&from_ty.kind(), &to_ty.kind()) { match (&from_ty.kind(), &to_ty.kind()) {
(ty::RawPtr(from_ptr_ty), ty::Ref(_, to_ref_ty, mutbl)) => { (ty::RawPtr(from_ptr_ty), ty::Ref(_, to_ref_ty, mutbl)) => {
@@ -34,19 +36,34 @@ pub(super) fn check<'tcx>(
} else { } else {
("&*", "*const") ("&*", "*const")
}; };
let mut app = Applicability::MachineApplicable;
let arg = if from_ptr_ty.ty == *to_ref_ty { let sugg = if let Some(ty) = get_explicit_type(path) {
arg 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 { } else {
arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, *to_ref_ty))) 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 {
sugg::make_unop(deref, arg.as_ty(format!("{} {}", cast, to_ref_ty))).to_string()
}; };
diag.span_suggestion( diag.span_suggestion(e.span, "try", sugg, app);
e.span,
"try",
sugg::make_unop(deref, arg).to_string(),
Applicability::Unspecified,
);
}, },
); );
true true
@@ -54,3 +71,14 @@ pub(super) fn check<'tcx>(
_ => false, _ => 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 rustc_hir::Expr;
use clippy_utils::source::snippet;
use if_chain::if_chain;
use rustc_hir::{Expr, GenericArg, QPath, TyKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty::{cast::CastKind, Ty}; use rustc_middle::ty::{cast::CastKind, Ty};
use rustc_span::DUMMY_SP; use rustc_span::DUMMY_SP;
use rustc_typeck::check::{cast::CastCheck, FnCtxt, Inherited}; 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, // check if the component types of the transmuted collection and the result have different ABI,
// size or alignment // size or alignment
pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool { pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool {

View File

@@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, Visitor}; use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, Visitor};
use rustc_hir::{Body, Expr, ExprKind, FnDecl, FnHeader, HirId, IsAsync, YieldSource}; use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, IsAsync, YieldSource};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter; use rustc_middle::hir::nested_filter;
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -68,8 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
span: Span, span: Span,
hir_id: HirId, hir_id: HirId,
) { ) {
if let FnKind::ItemFn(_, _, FnHeader { asyncness, .. }) = &fn_kind { if !span.from_expansion() && fn_kind.asyncness() == IsAsync::Async {
if matches!(asyncness, IsAsync::Async) {
let mut visitor = AsyncFnVisitor { cx, found_await: false }; let mut visitor = AsyncFnVisitor { cx, found_await: false };
walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), span, hir_id); walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), span, hir_id);
if !visitor.found_await { if !visitor.found_await {
@@ -84,5 +83,4 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
} }
} }
} }
}
} }

View File

@@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::higher; use clippy_utils::higher;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{path_to_local, usage::is_potentially_mutated}; use clippy_utils::{path_to_local, usage::is_potentially_mutated};
@@ -251,9 +251,10 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
unwrappable.kind.error_variant_pattern() unwrappable.kind.error_variant_pattern()
}; };
span_lint_and_then( span_lint_hir_and_then(
self.cx, self.cx,
UNNECESSARY_UNWRAP, UNNECESSARY_UNWRAP,
expr.hir_id,
expr.span, expr.span,
&format!( &format!(
"called `{}` on `{}` after checking its variant with `{}`", "called `{}` on `{}` after checking its variant with `{}`",
@@ -283,9 +284,10 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
}, },
); );
} else { } else {
span_lint_and_then( span_lint_hir_and_then(
self.cx, self.cx,
PANICKING_UNWRAP, PANICKING_UNWRAP,
expr.hir_id,
expr.span, expr.span,
&format!("this call to `{}()` will always panic", &format!("this call to `{}()` will always panic",
method_name.ident.name), method_name.ident.name),

View File

@@ -1,3 +1,4 @@
use crate::utils::internal_lints::metadata_collector::is_deprecated_lint;
use clippy_utils::consts::{constant_simple, Constant}; use clippy_utils::consts::{constant_simple, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::macros::root_macro_call_first_node; use clippy_utils::macros::root_macro_call_first_node;
@@ -338,6 +339,46 @@ declare_clippy_lint! {
"checking if all necessary steps were taken when adding a MSRV to a lint" "checking if all necessary steps were taken when adding a MSRV to a lint"
} }
declare_clippy_lint! {
/// ### What it does
/// Checks for cases of an auto-generated deprecated lint without an updated reason,
/// i.e. `"default deprecation note"`.
///
/// ### Why is this bad?
/// Indicates that the documentation is incomplete.
///
/// ### Example
/// ```rust,ignore
/// declare_deprecated_lint! {
/// /// ### What it does
/// /// Nothing. This lint has been deprecated.
/// ///
/// /// ### Deprecation reason
/// /// TODO
/// #[clippy::version = "1.63.0"]
/// pub COOL_LINT,
/// "default deprecation note"
/// }
/// ```
///
/// Use instead:
/// ```rust,ignore
/// declare_deprecated_lint! {
/// /// ### What it does
/// /// Nothing. This lint has been deprecated.
/// ///
/// /// ### Deprecation reason
/// /// This lint has been replaced by `cooler_lint`
/// #[clippy::version = "1.63.0"]
/// pub COOL_LINT,
/// "this lint has been replaced by `cooler_lint`"
/// }
/// ```
pub DEFAULT_DEPRECATION_REASON,
internal,
"found 'default deprecation note' in a deprecated lint declaration"
}
declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]); declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
impl EarlyLintPass for ClippyLintsInternal { impl EarlyLintPass for ClippyLintsInternal {
@@ -375,33 +416,49 @@ pub struct LintWithoutLintPass {
registered_lints: FxHashSet<Symbol>, registered_lints: FxHashSet<Symbol>,
} }
impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE, MISSING_CLIPPY_VERSION_ATTRIBUTE]); impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE, MISSING_CLIPPY_VERSION_ATTRIBUTE, DEFAULT_DEPRECATION_REASON]);
impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if is_lint_allowed(cx, DEFAULT_LINT, item.hir_id()) { if is_lint_allowed(cx, DEFAULT_LINT, item.hir_id())
|| is_lint_allowed(cx, DEFAULT_DEPRECATION_REASON, item.hir_id())
{
return; return;
} }
if let hir::ItemKind::Static(ty, Mutability::Not, body_id) = item.kind { if let hir::ItemKind::Static(ty, Mutability::Not, body_id) = item.kind {
if is_lint_ref_type(cx, ty) { let is_lint_ref_ty = is_lint_ref_type(cx, ty);
if is_deprecated_lint(cx, ty) || is_lint_ref_ty {
check_invalid_clippy_version_attribute(cx, item); check_invalid_clippy_version_attribute(cx, item);
let expr = &cx.tcx.hir().body(body_id).value; let expr = &cx.tcx.hir().body(body_id).value;
if_chain! { let fields;
if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind; if is_lint_ref_ty {
if let ExprKind::Struct(_, fields, _) = inner_exp.kind; if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind
&& let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind {
fields = struct_fields;
} else {
return;
}
} else if let ExprKind::Struct(_, struct_fields, _) = expr.kind {
fields = struct_fields;
} else {
return;
}
let field = fields let field = fields
.iter() .iter()
.find(|f| f.ident.as_str() == "desc") .find(|f| f.ident.as_str() == "desc")
.expect("lints must have a description field"); .expect("lints must have a description field");
if let ExprKind::Lit(Spanned { if let ExprKind::Lit(Spanned {
node: LitKind::Str(ref sym, _), node: LitKind::Str(ref sym, _),
.. ..
}) = field.expr.kind; }) = field.expr.kind
if sym.as_str() == "default lint description"; {
let sym_str = sym.as_str();
then { if is_lint_ref_ty {
if sym_str == "default lint description" {
span_lint( span_lint(
cx, cx,
DEFAULT_LINT, DEFAULT_LINT,
@@ -409,8 +466,17 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
&format!("the lint `{}` has the default lint description", item.ident.name), &format!("the lint `{}` has the default lint description", item.ident.name),
); );
} }
}
self.declared_lints.insert(item.ident.name, item.span); self.declared_lints.insert(item.ident.name, item.span);
} else if sym_str == "default deprecation note" {
span_lint(
cx,
DEFAULT_DEPRECATION_REASON,
item.span,
&format!("the lint `{}` has the default deprecation reason", item.ident.name),
);
}
}
} }
} else if let Some(macro_call) = root_macro_call_first_node(cx, item) { } else if let Some(macro_call) = root_macro_call_first_node(cx, item) {
if !matches!( if !matches!(
@@ -668,6 +734,7 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
let body = cx.tcx.hir().body(*body); let body = cx.tcx.hir().body(*body);
let only_expr = peel_blocks_with_stmt(&body.value); let only_expr = peel_blocks_with_stmt(&body.value);
if let ExprKind::MethodCall(ps, span_call_args, _) = &only_expr.kind; if let ExprKind::MethodCall(ps, span_call_args, _) = &only_expr.kind;
if let ExprKind::Path(..) = span_call_args[0].kind;
then { then {
let and_then_snippets = get_and_then_snippets(cx, and_then_args); let and_then_snippets = get_and_then_snippets(cx, and_then_args);
let mut sle = SpanlessEq::new(cx).deny_side_effects(); let mut sle = SpanlessEq::new(cx).deny_side_effects();

View File

@@ -104,7 +104,7 @@ macro_rules! RENAME_VALUE_TEMPLATE {
}; };
} }
const LINT_EMISSION_FUNCTIONS: [&[&str]; 8] = [ const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [
&["clippy_utils", "diagnostics", "span_lint"], &["clippy_utils", "diagnostics", "span_lint"],
&["clippy_utils", "diagnostics", "span_lint_and_help"], &["clippy_utils", "diagnostics", "span_lint_and_help"],
&["clippy_utils", "diagnostics", "span_lint_and_note"], &["clippy_utils", "diagnostics", "span_lint_and_note"],
@@ -190,7 +190,12 @@ impl MetadataCollector {
lints: BinaryHeap::<LintMetadata>::default(), lints: BinaryHeap::<LintMetadata>::default(),
applicability_info: FxHashMap::<String, ApplicabilityInfo>::default(), applicability_info: FxHashMap::<String, ApplicabilityInfo>::default(),
config: collect_configs(), config: collect_configs(),
clippy_project_root: clippy_dev::clippy_project_root(), clippy_project_root: std::env::current_dir()
.expect("failed to get current dir")
.ancestors()
.nth(1)
.expect("failed to get project root")
.to_path_buf(),
} }
} }
@@ -841,7 +846,7 @@ fn get_lint_level_from_group(lint_group: &str) -> Option<&'static str> {
.find_map(|(group_name, group_level)| (*group_name == lint_group).then(|| *group_level)) .find_map(|(group_name, group_level)| (*group_name == lint_group).then(|| *group_level))
} }
fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { pub(super) fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
if let hir::TyKind::Path(ref path) = ty.kind { if let hir::TyKind::Path(ref path) = ty.kind {
if let hir::def::Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, ty.hir_id) { if let hir::def::Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, ty.hir_id) {
return match_def_path(cx, def_id, &DEPRECATED_LINT_TYPE); return match_def_path(cx, def_id, &DEPRECATED_LINT_TYPE);

View File

@@ -20,6 +20,11 @@ declare_clippy_lint! {
/// ```rust /// ```rust
/// vec!(1, 2, 3, 4, 5).resize(0, 5) /// vec!(1, 2, 3, 4, 5).resize(0, 5)
/// ``` /// ```
///
/// Use instead:
/// ```rust
/// vec!(1, 2, 3, 4, 5).clear()
/// ```
#[clippy::version = "1.46.0"] #[clippy::version = "1.46.0"]
pub VEC_RESIZE_TO_ZERO, pub VEC_RESIZE_TO_ZERO,
correctness, correctness,

View File

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

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