mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-25 16:24:46 +00:00
Merge commit '0cb0f7636851f9fcc57085cf80197a2ef6db098f' into clippyup
This commit is contained in:
parent
ee37029afa
commit
09f5df5087
2
.github/workflows/remark.yml
vendored
2
.github/workflows/remark.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1.4.4
|
||||
with:
|
||||
node-version: '12.x'
|
||||
node-version: '14.x'
|
||||
|
||||
- name: Install remark
|
||||
run: npm install remark-cli remark-lint remark-lint-maximum-line-length remark-preset-lint-recommended remark-gfm
|
||||
|
@ -3348,6 +3348,7 @@ Released 2018-09-13
|
||||
[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
|
||||
[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation
|
||||
[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const
|
||||
[`default_instead_of_iter_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_instead_of_iter_empty
|
||||
[`default_numeric_fallback`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_numeric_fallback
|
||||
[`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access
|
||||
[`default_union_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_union_representation
|
||||
@ -3399,6 +3400,7 @@ Released 2018-09-13
|
||||
[`expect_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call
|
||||
[`expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_used
|
||||
[`expl_impl_clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#expl_impl_clone_on_copy
|
||||
[`explicit_auto_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_auto_deref
|
||||
[`explicit_counter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_counter_loop
|
||||
[`explicit_deref_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods
|
||||
[`explicit_into_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_into_iter_loop
|
||||
@ -3519,6 +3521,7 @@ Released 2018-09-13
|
||||
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
|
||||
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
|
||||
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
|
||||
[`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find
|
||||
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
|
||||
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
|
||||
[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map
|
||||
@ -3526,6 +3529,8 @@ Released 2018-09-13
|
||||
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
|
||||
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
|
||||
[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
|
||||
[`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid
|
||||
[`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain
|
||||
[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
|
||||
[`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once
|
||||
[`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clippy"
|
||||
version = "0.1.63"
|
||||
version = "0.1.64"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
@ -31,6 +31,7 @@ termize = "0.1"
|
||||
compiletest_rs = { version = "0.8", features = ["tmp"] }
|
||||
tester = "0.9"
|
||||
regex = "1.5"
|
||||
toml = "0.5"
|
||||
# This is used by the `collect-metadata` alias.
|
||||
filetime = "0.2"
|
||||
|
||||
|
10
README.md
10
README.md
@ -5,7 +5,7 @@
|
||||
|
||||
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
|
||||
|
||||
[There are over 500 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
[There are over 550 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
|
||||
Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
|
||||
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.
|
||||
@ -214,6 +214,14 @@ specifying the minimum supported Rust version (MSRV) in the clippy configuration
|
||||
msrv = "1.30.0"
|
||||
```
|
||||
|
||||
Alternatively, the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field)
|
||||
in the `Cargo.toml` can be used.
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
rust-version = "1.30"
|
||||
```
|
||||
|
||||
The MSRV can also be specified as an inner attribute, like below.
|
||||
|
||||
```rust
|
||||
|
@ -6,7 +6,7 @@
|
||||
A collection of lints to catch common mistakes and improve your
|
||||
[Rust](https://github.com/rust-lang/rust) code.
|
||||
|
||||
[There are over 500 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
[There are over 550 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
|
||||
Lints are divided into categories, each with a default [lint
|
||||
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how
|
||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
aho-corasick = "0.7"
|
||||
clap = "3.1"
|
||||
clap = "3.2"
|
||||
indoc = "1.0"
|
||||
itertools = "0.10.1"
|
||||
opener = "0.5"
|
||||
|
@ -5,6 +5,7 @@
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command, PossibleValue};
|
||||
use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints};
|
||||
use indoc::indoc;
|
||||
|
||||
fn main() {
|
||||
let matches = get_clap_config();
|
||||
|
||||
@ -85,6 +86,11 @@ fn main() {
|
||||
let uplift = matches.contains_id("uplift");
|
||||
update_lints::rename(old_name, new_name, uplift);
|
||||
},
|
||||
Some(("deprecate", matches)) => {
|
||||
let name = matches.get_one::<String>("name").unwrap();
|
||||
let reason = matches.get_one("reason");
|
||||
update_lints::deprecate(name, reason);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
@ -266,6 +272,18 @@ fn get_clap_config() -> ArgMatches {
|
||||
.long("uplift")
|
||||
.help("This lint will be uplifted into rustc"),
|
||||
]),
|
||||
Command::new("deprecate").about("Deprecates the given lint").args([
|
||||
Arg::new("name")
|
||||
.index(1)
|
||||
.required(true)
|
||||
.help("The name of the lint to deprecate"),
|
||||
Arg::new("reason")
|
||||
.long("reason")
|
||||
.short('r')
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.help("The reason for deprecation"),
|
||||
]),
|
||||
])
|
||||
.get_matches()
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ fn to_camel_case(name: &str) -> String {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_stabilization_version() -> String {
|
||||
pub(crate) fn get_stabilization_version() -> String {
|
||||
fn parse_manifest(contents: &str) -> Option<String> {
|
||||
let version = contents
|
||||
.lines()
|
||||
|
@ -1,16 +1,17 @@
|
||||
use crate::clippy_project_root;
|
||||
use aho_corasick::AhoCorasickBuilder;
|
||||
use core::fmt::Write as _;
|
||||
use indoc::writedoc;
|
||||
use itertools::Itertools;
|
||||
use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::io::{self, Read as _, Seek as _, Write as _};
|
||||
use std::fmt::Write;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write as _};
|
||||
use std::ops::Range;
|
||||
use std::path::{Path, PathBuf};
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::clippy_project_root;
|
||||
|
||||
const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\
|
||||
// Use that command to update this file and do not edit by hand.\n\
|
||||
// Manual edits will be overwritten.\n\n";
|
||||
@ -326,6 +327,200 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
|
||||
println!("note: `cargo uitest` still needs to be run to update the test results");
|
||||
}
|
||||
|
||||
const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note";
|
||||
/// Runs the `deprecate` command
|
||||
///
|
||||
/// This does the following:
|
||||
/// * Adds an entry to `deprecated_lints.rs`.
|
||||
/// * Removes the lint declaration (and the entire file if applicable)
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If a file path could not read from or written to
|
||||
pub fn deprecate(name: &str, reason: Option<&String>) {
|
||||
fn finish(
|
||||
(lints, mut deprecated_lints, renamed_lints): (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>),
|
||||
name: &str,
|
||||
reason: &str,
|
||||
) {
|
||||
deprecated_lints.push(DeprecatedLint {
|
||||
name: name.to_string(),
|
||||
reason: reason.to_string(),
|
||||
declaration_range: Range::default(),
|
||||
});
|
||||
|
||||
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
|
||||
println!("info: `{}` has successfully been deprecated", name);
|
||||
|
||||
if reason == DEFAULT_DEPRECATION_REASON {
|
||||
println!("note: the deprecation reason must be updated in `clippy_lints/src/deprecated_lints.rs`");
|
||||
}
|
||||
println!("note: you must run `cargo uitest` to update the test results");
|
||||
}
|
||||
|
||||
let reason = reason.map_or(DEFAULT_DEPRECATION_REASON, String::as_str);
|
||||
let name_lower = name.to_lowercase();
|
||||
let name_upper = name.to_uppercase();
|
||||
|
||||
let (mut lints, deprecated_lints, renamed_lints) = gather_all();
|
||||
let Some(lint) = lints.iter().find(|l| l.name == name_lower) else { eprintln!("error: failed to find lint `{}`", name); return; };
|
||||
|
||||
let mod_path = {
|
||||
let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module));
|
||||
if mod_path.is_dir() {
|
||||
mod_path = mod_path.join("mod");
|
||||
}
|
||||
|
||||
mod_path.set_extension("rs");
|
||||
mod_path
|
||||
};
|
||||
|
||||
let deprecated_lints_path = &*clippy_project_root().join("clippy_lints/src/deprecated_lints.rs");
|
||||
|
||||
if remove_lint_declaration(&name_lower, &mod_path, &mut lints).unwrap_or(false) {
|
||||
declare_deprecated(&name_upper, deprecated_lints_path, reason).unwrap();
|
||||
finish((lints, deprecated_lints, renamed_lints), name, reason);
|
||||
return;
|
||||
}
|
||||
|
||||
eprintln!("error: lint not found");
|
||||
}
|
||||
|
||||
fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io::Result<bool> {
|
||||
fn remove_lint(name: &str, lints: &mut Vec<Lint>) {
|
||||
lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos));
|
||||
}
|
||||
|
||||
fn remove_test_assets(name: &str) {
|
||||
let test_file_stem = format!("tests/ui/{}", name);
|
||||
let path = Path::new(&test_file_stem);
|
||||
|
||||
// Some lints have their own directories, delete them
|
||||
if path.is_dir() {
|
||||
fs::remove_dir_all(path).ok();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove all related test files
|
||||
fs::remove_file(path.with_extension("rs")).ok();
|
||||
fs::remove_file(path.with_extension("stderr")).ok();
|
||||
fs::remove_file(path.with_extension("fixed")).ok();
|
||||
}
|
||||
|
||||
fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) {
|
||||
let impl_lint_pass_start = content.find("impl_lint_pass!").unwrap_or_else(|| {
|
||||
content
|
||||
.find("declare_lint_pass!")
|
||||
.unwrap_or_else(|| panic!("failed to find `impl_lint_pass`"))
|
||||
});
|
||||
let mut impl_lint_pass_end = content[impl_lint_pass_start..]
|
||||
.find(']')
|
||||
.expect("failed to find `impl_lint_pass` terminator");
|
||||
|
||||
impl_lint_pass_end += impl_lint_pass_start;
|
||||
if let Some(lint_name_pos) = content[impl_lint_pass_start..impl_lint_pass_end].find(&lint_name_upper) {
|
||||
let mut lint_name_end = impl_lint_pass_start + (lint_name_pos + lint_name_upper.len());
|
||||
for c in content[lint_name_end..impl_lint_pass_end].chars() {
|
||||
// Remove trailing whitespace
|
||||
if c == ',' || c.is_whitespace() {
|
||||
lint_name_end += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
content.replace_range(impl_lint_pass_start + lint_name_pos..lint_name_end, "");
|
||||
}
|
||||
}
|
||||
|
||||
if path.exists() {
|
||||
if let Some(lint) = lints.iter().find(|l| l.name == name) {
|
||||
if lint.module == name {
|
||||
// The lint name is the same as the file, we can just delete the entire file
|
||||
fs::remove_file(path)?;
|
||||
} else {
|
||||
// We can't delete the entire file, just remove the declaration
|
||||
|
||||
if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) {
|
||||
// Remove clippy_lints/src/some_mod/some_lint.rs
|
||||
let mut lint_mod_path = path.to_path_buf();
|
||||
lint_mod_path.set_file_name(name);
|
||||
lint_mod_path.set_extension("rs");
|
||||
|
||||
fs::remove_file(lint_mod_path).ok();
|
||||
}
|
||||
|
||||
let mut content =
|
||||
fs::read_to_string(&path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy()));
|
||||
|
||||
eprintln!(
|
||||
"warn: you will have to manually remove any code related to `{}` from `{}`",
|
||||
name,
|
||||
path.display()
|
||||
);
|
||||
|
||||
assert!(
|
||||
content[lint.declaration_range.clone()].contains(&name.to_uppercase()),
|
||||
"error: `{}` does not contain lint `{}`'s declaration",
|
||||
path.display(),
|
||||
lint.name
|
||||
);
|
||||
|
||||
// Remove lint declaration (declare_clippy_lint!)
|
||||
content.replace_range(lint.declaration_range.clone(), "");
|
||||
|
||||
// Remove the module declaration (mod xyz;)
|
||||
let mod_decl = format!("\nmod {};", name);
|
||||
content = content.replacen(&mod_decl, "", 1);
|
||||
|
||||
remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content);
|
||||
fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy()));
|
||||
}
|
||||
|
||||
remove_test_assets(name);
|
||||
remove_lint(name, lints);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn declare_deprecated(name: &str, path: &Path, reason: &str) -> io::Result<()> {
|
||||
let mut file = OpenOptions::new().write(true).open(path)?;
|
||||
|
||||
file.seek(SeekFrom::End(0))?;
|
||||
|
||||
let version = crate::new_lint::get_stabilization_version();
|
||||
let deprecation_reason = if reason == DEFAULT_DEPRECATION_REASON {
|
||||
"TODO"
|
||||
} else {
|
||||
reason
|
||||
};
|
||||
|
||||
writedoc!(
|
||||
file,
|
||||
"
|
||||
|
||||
declare_deprecated_lint! {{
|
||||
/// ### What it does
|
||||
/// Nothing. This lint has been deprecated.
|
||||
///
|
||||
/// ### Deprecation reason
|
||||
/// {}
|
||||
#[clippy::version = \"{}\"]
|
||||
pub {},
|
||||
\"{}\"
|
||||
}}
|
||||
|
||||
",
|
||||
deprecation_reason,
|
||||
version,
|
||||
name,
|
||||
reason,
|
||||
)
|
||||
}
|
||||
|
||||
/// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there
|
||||
/// were no replacements.
|
||||
fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<String> {
|
||||
@ -393,16 +588,18 @@ struct Lint {
|
||||
group: String,
|
||||
desc: String,
|
||||
module: String,
|
||||
declaration_range: Range<usize>,
|
||||
}
|
||||
|
||||
impl Lint {
|
||||
#[must_use]
|
||||
fn new(name: &str, group: &str, desc: &str, module: &str) -> Self {
|
||||
fn new(name: &str, group: &str, desc: &str, module: &str, declaration_range: Range<usize>) -> Self {
|
||||
Self {
|
||||
name: name.to_lowercase(),
|
||||
group: group.into(),
|
||||
desc: remove_line_splices(desc),
|
||||
module: module.into(),
|
||||
declaration_range,
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,12 +630,14 @@ impl Lint {
|
||||
struct DeprecatedLint {
|
||||
name: String,
|
||||
reason: String,
|
||||
declaration_range: Range<usize>,
|
||||
}
|
||||
impl DeprecatedLint {
|
||||
fn new(name: &str, reason: &str) -> Self {
|
||||
fn new(name: &str, reason: &str, declaration_range: Range<usize>) -> Self {
|
||||
Self {
|
||||
name: name.to_lowercase(),
|
||||
reason: remove_line_splices(reason),
|
||||
declaration_range,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -610,7 +809,11 @@ fn clippy_lints_src_files() -> impl Iterator<Item = (PathBuf, DirEntry)> {
|
||||
macro_rules! match_tokens {
|
||||
($iter:ident, $($token:ident $({$($fields:tt)*})? $(($capture:ident))?)*) => {
|
||||
{
|
||||
$($(let $capture =)? if let Some((TokenKind::$token $({$($fields)*})?, _x)) = $iter.next() {
|
||||
$($(let $capture =)? if let Some(LintDeclSearchResult {
|
||||
token_kind: TokenKind::$token $({$($fields)*})?,
|
||||
content: _x,
|
||||
..
|
||||
}) = $iter.next() {
|
||||
_x
|
||||
} else {
|
||||
continue;
|
||||
@ -621,40 +824,72 @@ macro_rules! match_tokens {
|
||||
}
|
||||
}
|
||||
|
||||
struct LintDeclSearchResult<'a> {
|
||||
token_kind: TokenKind,
|
||||
content: &'a str,
|
||||
range: Range<usize>,
|
||||
}
|
||||
|
||||
/// Parse a source file looking for `declare_clippy_lint` macro invocations.
|
||||
fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
|
||||
let mut offset = 0usize;
|
||||
let mut iter = tokenize(contents).map(|t| {
|
||||
let range = offset..offset + t.len;
|
||||
offset = range.end;
|
||||
(t.kind, &contents[range])
|
||||
|
||||
LintDeclSearchResult {
|
||||
token_kind: t.kind,
|
||||
content: &contents[range.clone()],
|
||||
range,
|
||||
}
|
||||
});
|
||||
|
||||
while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_clippy_lint") {
|
||||
while let Some(LintDeclSearchResult { range, .. }) = iter.find(
|
||||
|LintDeclSearchResult {
|
||||
token_kind, content, ..
|
||||
}| token_kind == &TokenKind::Ident && *content == "declare_clippy_lint",
|
||||
) {
|
||||
let start = range.start;
|
||||
|
||||
let mut iter = iter
|
||||
.by_ref()
|
||||
.filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
|
||||
.filter(|t| !matches!(t.token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
|
||||
// matches `!{`
|
||||
match_tokens!(iter, Bang OpenBrace);
|
||||
match iter.next() {
|
||||
// #[clippy::version = "version"] pub
|
||||
Some((TokenKind::Pound, _)) => {
|
||||
Some(LintDeclSearchResult {
|
||||
token_kind: TokenKind::Pound,
|
||||
..
|
||||
}) => {
|
||||
match_tokens!(iter, OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Ident);
|
||||
},
|
||||
// pub
|
||||
Some((TokenKind::Ident, _)) => (),
|
||||
Some(LintDeclSearchResult {
|
||||
token_kind: TokenKind::Ident,
|
||||
..
|
||||
}) => (),
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
let (name, group, desc) = match_tokens!(
|
||||
iter,
|
||||
// LINT_NAME
|
||||
Ident(name) Comma
|
||||
// group,
|
||||
Ident(group) Comma
|
||||
// "description" }
|
||||
Literal{..}(desc) CloseBrace
|
||||
// "description"
|
||||
Literal{..}(desc)
|
||||
);
|
||||
lints.push(Lint::new(name, group, desc, module));
|
||||
|
||||
if let Some(LintDeclSearchResult {
|
||||
token_kind: TokenKind::CloseBrace,
|
||||
range,
|
||||
..
|
||||
}) = iter.next()
|
||||
{
|
||||
lints.push(Lint::new(name, group, desc, module, start..range.end));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -664,12 +899,24 @@ fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) {
|
||||
let mut iter = tokenize(contents).map(|t| {
|
||||
let range = offset..offset + t.len;
|
||||
offset = range.end;
|
||||
(t.kind, &contents[range])
|
||||
|
||||
LintDeclSearchResult {
|
||||
token_kind: t.kind,
|
||||
content: &contents[range.clone()],
|
||||
range,
|
||||
}
|
||||
});
|
||||
while iter.any(|(kind, s)| kind == TokenKind::Ident && s == "declare_deprecated_lint") {
|
||||
let mut iter = iter
|
||||
.by_ref()
|
||||
.filter(|&(kind, _)| !matches!(kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
|
||||
|
||||
while let Some(LintDeclSearchResult { range, .. }) = iter.find(
|
||||
|LintDeclSearchResult {
|
||||
token_kind, content, ..
|
||||
}| token_kind == &TokenKind::Ident && *content == "declare_deprecated_lint",
|
||||
) {
|
||||
let start = range.start;
|
||||
|
||||
let mut iter = iter.by_ref().filter(|LintDeclSearchResult { ref token_kind, .. }| {
|
||||
!matches!(token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. })
|
||||
});
|
||||
let (name, reason) = match_tokens!(
|
||||
iter,
|
||||
// !{
|
||||
@ -680,10 +927,16 @@ fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) {
|
||||
Ident Ident(name) Comma
|
||||
// "description"
|
||||
Literal{kind: LiteralKind::Str{..},..}(reason)
|
||||
// }
|
||||
CloseBrace
|
||||
);
|
||||
lints.push(DeprecatedLint::new(name, reason));
|
||||
|
||||
if let Some(LintDeclSearchResult {
|
||||
token_kind: TokenKind::CloseBrace,
|
||||
range,
|
||||
..
|
||||
}) = iter.next()
|
||||
{
|
||||
lints.push(DeprecatedLint::new(name, reason, start..range.end));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -693,8 +946,14 @@ fn parse_renamed_contents(contents: &str, lints: &mut Vec<RenamedLint>) {
|
||||
let mut iter = tokenize(line).map(|t| {
|
||||
let range = offset..offset + t.len;
|
||||
offset = range.end;
|
||||
(t.kind, &line[range])
|
||||
|
||||
LintDeclSearchResult {
|
||||
token_kind: t.kind,
|
||||
content: &line[range.clone()],
|
||||
range,
|
||||
}
|
||||
});
|
||||
|
||||
let (old_name, new_name) = match_tokens!(
|
||||
iter,
|
||||
// ("old_name",
|
||||
@ -844,10 +1103,25 @@ mod tests {
|
||||
"#;
|
||||
let mut result = Vec::new();
|
||||
parse_contents(CONTENTS, "module_name", &mut result);
|
||||
for r in &mut result {
|
||||
r.declaration_range = Range::default();
|
||||
}
|
||||
|
||||
let expected = vec![
|
||||
Lint::new("ptr_arg", "style", "\"really long text\"", "module_name"),
|
||||
Lint::new("doc_markdown", "pedantic", "\"single line\"", "module_name"),
|
||||
Lint::new(
|
||||
"ptr_arg",
|
||||
"style",
|
||||
"\"really long text\"",
|
||||
"module_name",
|
||||
Range::default(),
|
||||
),
|
||||
Lint::new(
|
||||
"doc_markdown",
|
||||
"pedantic",
|
||||
"\"single line\"",
|
||||
"module_name",
|
||||
Range::default(),
|
||||
),
|
||||
];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
@ -865,10 +1139,14 @@ mod tests {
|
||||
|
||||
let mut result = Vec::new();
|
||||
parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result);
|
||||
for r in &mut result {
|
||||
r.declaration_range = Range::default();
|
||||
}
|
||||
|
||||
let expected = vec![DeprecatedLint::new(
|
||||
"should_assert_eq",
|
||||
"\"`assert!()` will be more flexible with RFC 2011\"",
|
||||
Range::default(),
|
||||
)];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
@ -876,15 +1154,34 @@ mod tests {
|
||||
#[test]
|
||||
fn test_usable_lints() {
|
||||
let lints = vec![
|
||||
Lint::new("should_assert_eq2", "Not Deprecated", "\"abc\"", "module_name"),
|
||||
Lint::new("should_assert_eq2", "internal", "\"abc\"", "module_name"),
|
||||
Lint::new("should_assert_eq2", "internal_style", "\"abc\"", "module_name"),
|
||||
Lint::new(
|
||||
"should_assert_eq2",
|
||||
"Not Deprecated",
|
||||
"\"abc\"",
|
||||
"module_name",
|
||||
Range::default(),
|
||||
),
|
||||
Lint::new(
|
||||
"should_assert_eq2",
|
||||
"internal",
|
||||
"\"abc\"",
|
||||
"module_name",
|
||||
Range::default(),
|
||||
),
|
||||
Lint::new(
|
||||
"should_assert_eq2",
|
||||
"internal_style",
|
||||
"\"abc\"",
|
||||
"module_name",
|
||||
Range::default(),
|
||||
),
|
||||
];
|
||||
let expected = vec![Lint::new(
|
||||
"should_assert_eq2",
|
||||
"Not Deprecated",
|
||||
"\"abc\"",
|
||||
"module_name",
|
||||
Range::default(),
|
||||
)];
|
||||
assert_eq!(expected, Lint::usable_lints(&lints));
|
||||
}
|
||||
@ -892,21 +1189,33 @@ mod tests {
|
||||
#[test]
|
||||
fn test_by_lint_group() {
|
||||
let lints = vec![
|
||||
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
|
||||
Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name"),
|
||||
Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"),
|
||||
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()),
|
||||
Lint::new(
|
||||
"should_assert_eq2",
|
||||
"group2",
|
||||
"\"abc\"",
|
||||
"module_name",
|
||||
Range::default(),
|
||||
),
|
||||
Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()),
|
||||
];
|
||||
let mut expected: HashMap<String, Vec<Lint>> = HashMap::new();
|
||||
expected.insert(
|
||||
"group1".to_string(),
|
||||
vec![
|
||||
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
|
||||
Lint::new("incorrect_match", "group1", "\"abc\"", "module_name"),
|
||||
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()),
|
||||
Lint::new("incorrect_match", "group1", "\"abc\"", "module_name", Range::default()),
|
||||
],
|
||||
);
|
||||
expected.insert(
|
||||
"group2".to_string(),
|
||||
vec![Lint::new("should_assert_eq2", "group2", "\"abc\"", "module_name")],
|
||||
vec![Lint::new(
|
||||
"should_assert_eq2",
|
||||
"group2",
|
||||
"\"abc\"",
|
||||
"module_name",
|
||||
Range::default(),
|
||||
)],
|
||||
);
|
||||
assert_eq!(expected, Lint::by_lint_group(lints.into_iter()));
|
||||
}
|
||||
@ -914,8 +1223,12 @@ mod tests {
|
||||
#[test]
|
||||
fn test_gen_deprecated() {
|
||||
let lints = vec![
|
||||
DeprecatedLint::new("should_assert_eq", "\"has been superseded by should_assert_eq2\""),
|
||||
DeprecatedLint::new("another_deprecated", "\"will be removed\""),
|
||||
DeprecatedLint::new(
|
||||
"should_assert_eq",
|
||||
"\"has been superseded by should_assert_eq2\"",
|
||||
Range::default(),
|
||||
),
|
||||
DeprecatedLint::new("another_deprecated", "\"will be removed\"", Range::default()),
|
||||
];
|
||||
|
||||
let expected = GENERATED_FILE_COMMENT.to_string()
|
||||
@ -940,9 +1253,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_gen_lint_group_list() {
|
||||
let lints = vec![
|
||||
Lint::new("abc", "group1", "\"abc\"", "module_name"),
|
||||
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name"),
|
||||
Lint::new("internal", "internal_style", "\"abc\"", "module_name"),
|
||||
Lint::new("abc", "group1", "\"abc\"", "module_name", Range::default()),
|
||||
Lint::new("should_assert_eq", "group1", "\"abc\"", "module_name", Range::default()),
|
||||
Lint::new("internal", "internal_style", "\"abc\"", "module_name", Range::default()),
|
||||
];
|
||||
let expected = GENERATED_FILE_COMMENT.to_string()
|
||||
+ &[
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clippy_lints"
|
||||
version = "0.1.63"
|
||||
version = "0.1.64"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
@ -10,7 +10,6 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cargo_metadata = "0.14"
|
||||
clippy_dev = { path = "../clippy_dev", optional = true }
|
||||
clippy_utils = { path = "../clippy_utils" }
|
||||
if_chain = "1.0"
|
||||
itertools = "0.10.1"
|
||||
@ -32,7 +31,7 @@ url = { version = "2.2", features = ["serde"] }
|
||||
[features]
|
||||
deny-warnings = ["clippy_utils/deny-warnings"]
|
||||
# build clippy with internal lints enabled, off by default
|
||||
internal = ["clippy_utils/internal", "serde_json", "tempfile", "clippy_dev"]
|
||||
internal = ["clippy_utils/internal", "serde_json", "tempfile"]
|
||||
|
||||
[package.metadata.rust-analyzer]
|
||||
# This crate uses #[feature(rustc_private)]
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -78,10 +78,17 @@ declare_clippy_lint! {
|
||||
/// Checks for `extern crate` and `use` items annotated with
|
||||
/// lint attributes.
|
||||
///
|
||||
/// This lint permits `#[allow(unused_imports)]`, `#[allow(deprecated)]`,
|
||||
/// `#[allow(unreachable_pub)]`, `#[allow(clippy::wildcard_imports)]` and
|
||||
/// `#[allow(clippy::enum_glob_use)]` on `use` items and `#[allow(unused_imports)]` on
|
||||
/// `extern crate` items with a `#[macro_use]` attribute.
|
||||
/// This lint permits lint attributes for lints emitted on the items themself.
|
||||
/// For `use` items these lints are:
|
||||
/// * deprecated
|
||||
/// * unreachable_pub
|
||||
/// * unused_imports
|
||||
/// * clippy::enum_glob_use
|
||||
/// * clippy::macro_use_imports
|
||||
/// * clippy::wildcard_imports
|
||||
///
|
||||
/// For `extern crate` items these lints are:
|
||||
/// * `unused_imports` on items with `#[macro_use]`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Lint attributes have no effect on crate imports. Most
|
||||
@ -347,7 +354,10 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|
||||
|| extract_clippy_lint(lint).map_or(false, |s| {
|
||||
matches!(
|
||||
s.as_str(),
|
||||
"wildcard_imports" | "enum_glob_use" | "redundant_pub_crate",
|
||||
"wildcard_imports"
|
||||
| "enum_glob_use"
|
||||
| "redundant_pub_crate"
|
||||
| "macro_use_imports",
|
||||
)
|
||||
})
|
||||
{
|
||||
|
@ -1,4 +1,4 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
|
||||
use clippy_utils::{eq_expr_value, get_trait_def_id, paths};
|
||||
@ -394,9 +394,10 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
|
||||
continue 'simplified;
|
||||
}
|
||||
if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
|
||||
span_lint_and_then(
|
||||
span_lint_hir_and_then(
|
||||
self.cx,
|
||||
LOGIC_BUG,
|
||||
e.hir_id,
|
||||
e.span,
|
||||
"this boolean expression contains a logic bug",
|
||||
|diag| {
|
||||
@ -429,9 +430,10 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
|
||||
}
|
||||
}
|
||||
let nonminimal_bool_lint = |suggestions: Vec<_>| {
|
||||
span_lint_and_then(
|
||||
span_lint_hir_and_then(
|
||||
self.cx,
|
||||
NONMINIMAL_BOOL,
|
||||
e.hir_id,
|
||||
e.span,
|
||||
"this boolean expression can be simplified",
|
||||
|diag| {
|
||||
|
68
clippy_lints/src/default_instead_of_iter_empty.rs
Normal file
68
clippy_lints/src/default_instead_of_iter_empty.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -1,16 +1,21 @@
|
||||
// NOTE: if you add a deprecated lint in this file, please add a corresponding test in
|
||||
// tests/ui/deprecated.rs
|
||||
// NOTE: Entries should be created with `cargo dev deprecate`
|
||||
|
||||
/// This struct fakes the `Lint` declaration that is usually created by `declare_lint!`. This
|
||||
/// enables the simple extraction of the metadata without changing the current deprecation
|
||||
/// declaration.
|
||||
pub struct ClippyDeprecatedLint;
|
||||
pub struct ClippyDeprecatedLint {
|
||||
#[allow(dead_code)]
|
||||
pub desc: &'static str,
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! declare_deprecated_lint {
|
||||
{ $(#[$attr:meta])* pub $name: ident, $_reason: expr} => {
|
||||
{ $(#[$attr:meta])* pub $name: ident, $reason: literal} => {
|
||||
$(#[$attr])*
|
||||
#[allow(dead_code)]
|
||||
pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint {};
|
||||
pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint {
|
||||
desc: $reason
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,24 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use clippy_utils::ty::peel_mid_ty_refs;
|
||||
use clippy_utils::{get_parent_expr, get_parent_node, is_lint_allowed, path_to_local};
|
||||
use clippy_utils::ty::{expr_sig, peel_mid_ty_refs, variant_of_res};
|
||||
use clippy_utils::{get_parent_expr, is_lint_allowed, path_to_local, walk_to_expr_usage};
|
||||
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_ty, Visitor};
|
||||
use rustc_hir::{
|
||||
BindingAnnotation, Body, BodyId, BorrowKind, Destination, Expr, ExprKind, HirId, MatchSource, Mutability, Node,
|
||||
Pat, PatKind, UnOp,
|
||||
self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, GenericArg, HirId, ImplItem,
|
||||
ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
|
||||
TraitItemKind, TyKind, UnOp,
|
||||
};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt, TypeckResults};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeckResults};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{symbol::sym, Span};
|
||||
use rustc_span::{symbol::sym, Span, Symbol};
|
||||
use rustc_trait_selection::infer::InferCtxtExt;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -104,10 +108,34 @@ declare_clippy_lint! {
|
||||
"`ref` binding to a reference"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for dereferencing expressions which would be covered by auto-deref.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This unnecessarily complicates the code.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let x = String::new();
|
||||
/// let y: &str = &*x;
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// let x = String::new();
|
||||
/// let y: &str = &x;
|
||||
/// ```
|
||||
#[clippy::version = "1.60.0"]
|
||||
pub EXPLICIT_AUTO_DEREF,
|
||||
complexity,
|
||||
"dereferencing when the compiler would automatically dereference"
|
||||
}
|
||||
|
||||
impl_lint_pass!(Dereferencing => [
|
||||
EXPLICIT_DEREF_METHODS,
|
||||
NEEDLESS_BORROW,
|
||||
REF_BINDING_TO_REFERENCE,
|
||||
EXPLICIT_AUTO_DEREF,
|
||||
]);
|
||||
|
||||
#[derive(Default)]
|
||||
@ -136,6 +164,12 @@ struct StateData {
|
||||
/// Span of the top level expression
|
||||
span: Span,
|
||||
hir_id: HirId,
|
||||
position: Position,
|
||||
}
|
||||
|
||||
struct DerefedBorrow {
|
||||
count: usize,
|
||||
msg: &'static str,
|
||||
}
|
||||
|
||||
enum State {
|
||||
@ -147,11 +181,19 @@ enum State {
|
||||
/// The required mutability
|
||||
target_mut: Mutability,
|
||||
},
|
||||
DerefedBorrow {
|
||||
count: usize,
|
||||
required_precedence: i8,
|
||||
msg: &'static str,
|
||||
DerefedBorrow(DerefedBorrow),
|
||||
ExplicitDeref {
|
||||
// Span and id of the top-level deref expression if the parent expression is a borrow.
|
||||
deref_span_id: Option<(Span, HirId)>,
|
||||
},
|
||||
ExplicitDerefField {
|
||||
name: Symbol,
|
||||
},
|
||||
Reborrow {
|
||||
deref_span: Span,
|
||||
deref_hir_id: HirId,
|
||||
},
|
||||
Borrow,
|
||||
}
|
||||
|
||||
// A reference operation considered by this lint pass
|
||||
@ -207,13 +249,28 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
|
||||
|
||||
match (self.state.take(), kind) {
|
||||
(None, kind) => {
|
||||
let parent = get_parent_node(cx.tcx, expr.hir_id);
|
||||
let expr_ty = typeck.expr_ty(expr);
|
||||
let (position, adjustments) = walk_parents(cx, expr);
|
||||
|
||||
match kind {
|
||||
RefOp::Deref => {
|
||||
if let Position::FieldAccess(name) = position
|
||||
&& !ty_contains_field(typeck.expr_ty(sub_expr), name)
|
||||
{
|
||||
self.state = Some((
|
||||
State::ExplicitDerefField { name },
|
||||
StateData { span: expr.span, hir_id: expr.hir_id, position },
|
||||
));
|
||||
} else if position.is_deref_stable() {
|
||||
self.state = Some((
|
||||
State::ExplicitDeref { deref_span_id: None },
|
||||
StateData { span: expr.span, hir_id: expr.hir_id, position },
|
||||
));
|
||||
}
|
||||
}
|
||||
RefOp::Method(target_mut)
|
||||
if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id)
|
||||
&& is_linted_explicit_deref_position(parent, expr.hir_id, expr.span) =>
|
||||
&& position.lint_explicit_deref() =>
|
||||
{
|
||||
self.state = Some((
|
||||
State::DerefMethod {
|
||||
@ -228,12 +285,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
|
||||
StateData {
|
||||
span: expr.span,
|
||||
hir_id: expr.hir_id,
|
||||
position
|
||||
},
|
||||
));
|
||||
},
|
||||
RefOp::AddrOf => {
|
||||
// Find the number of times the borrow is auto-derefed.
|
||||
let mut iter = find_adjustments(cx.tcx, typeck, expr).iter();
|
||||
let mut iter = adjustments.iter();
|
||||
let mut deref_count = 0usize;
|
||||
let next_adjust = loop {
|
||||
match iter.next() {
|
||||
@ -274,40 +332,43 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
|
||||
"this expression creates a reference which is immediately dereferenced by the compiler";
|
||||
let borrow_msg = "this expression borrows a value the compiler would automatically borrow";
|
||||
|
||||
let (required_refs, required_precedence, msg) = if is_auto_borrow_position(parent, expr.hir_id)
|
||||
{
|
||||
(1, PREC_POSTFIX, if deref_count == 1 { borrow_msg } else { deref_msg })
|
||||
let (required_refs, msg) = if position.can_auto_borrow() {
|
||||
(1, if deref_count == 1 { borrow_msg } else { deref_msg })
|
||||
} else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) =
|
||||
next_adjust.map(|a| &a.kind)
|
||||
{
|
||||
if matches!(mutability, AutoBorrowMutability::Mut { .. })
|
||||
&& !is_auto_reborrow_position(parent)
|
||||
if matches!(mutability, AutoBorrowMutability::Mut { .. }) && !position.is_reborrow_stable()
|
||||
{
|
||||
(3, 0, deref_msg)
|
||||
(3, deref_msg)
|
||||
} else {
|
||||
(2, 0, deref_msg)
|
||||
(2, deref_msg)
|
||||
}
|
||||
} else {
|
||||
(2, 0, deref_msg)
|
||||
(2, deref_msg)
|
||||
};
|
||||
|
||||
if deref_count >= required_refs {
|
||||
self.state = Some((
|
||||
State::DerefedBorrow {
|
||||
State::DerefedBorrow(DerefedBorrow {
|
||||
// One of the required refs is for the current borrow expression, the remaining ones
|
||||
// can't be removed without breaking the code. See earlier comment.
|
||||
count: deref_count - required_refs,
|
||||
required_precedence,
|
||||
msg,
|
||||
},
|
||||
}),
|
||||
StateData { span: expr.span, hir_id: expr.hir_id, position },
|
||||
));
|
||||
} else if position.is_deref_stable() {
|
||||
self.state = Some((
|
||||
State::Borrow,
|
||||
StateData {
|
||||
span: expr.span,
|
||||
hir_id: expr.hir_id,
|
||||
position
|
||||
},
|
||||
));
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
RefOp::Method(..) => (),
|
||||
}
|
||||
},
|
||||
(
|
||||
@ -334,26 +395,90 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
|
||||
data,
|
||||
));
|
||||
},
|
||||
(Some((State::DerefedBorrow(state), data)), RefOp::AddrOf) if state.count != 0 => {
|
||||
self.state = Some((
|
||||
State::DerefedBorrow(DerefedBorrow {
|
||||
count: state.count - 1,
|
||||
..state
|
||||
}),
|
||||
data,
|
||||
));
|
||||
},
|
||||
(Some((State::DerefedBorrow(state), data)), RefOp::AddrOf) => {
|
||||
let position = data.position;
|
||||
report(cx, expr, State::DerefedBorrow(state), data);
|
||||
if position.is_deref_stable() {
|
||||
self.state = Some((
|
||||
State::Borrow,
|
||||
StateData {
|
||||
span: expr.span,
|
||||
hir_id: expr.hir_id,
|
||||
position,
|
||||
},
|
||||
));
|
||||
}
|
||||
},
|
||||
(Some((State::DerefedBorrow(state), data)), RefOp::Deref) => {
|
||||
let position = data.position;
|
||||
report(cx, expr, State::DerefedBorrow(state), data);
|
||||
if let Position::FieldAccess(name) = position
|
||||
&& !ty_contains_field(typeck.expr_ty(sub_expr), name)
|
||||
{
|
||||
self.state = Some((
|
||||
State::ExplicitDerefField { name },
|
||||
StateData { span: expr.span, hir_id: expr.hir_id, position },
|
||||
));
|
||||
} else if position.is_deref_stable() {
|
||||
self.state = Some((
|
||||
State::ExplicitDeref { deref_span_id: None },
|
||||
StateData { span: expr.span, hir_id: expr.hir_id, position },
|
||||
));
|
||||
}
|
||||
},
|
||||
|
||||
(Some((State::Borrow, data)), RefOp::Deref) => {
|
||||
if typeck.expr_ty(sub_expr).is_ref() {
|
||||
self.state = Some((
|
||||
State::Reborrow {
|
||||
deref_span: expr.span,
|
||||
deref_hir_id: expr.hir_id,
|
||||
},
|
||||
data,
|
||||
));
|
||||
} else {
|
||||
self.state = Some((
|
||||
State::ExplicitDeref {
|
||||
deref_span_id: Some((expr.span, expr.hir_id)),
|
||||
},
|
||||
data,
|
||||
));
|
||||
}
|
||||
},
|
||||
(
|
||||
Some((
|
||||
State::DerefedBorrow {
|
||||
count,
|
||||
required_precedence,
|
||||
msg,
|
||||
State::Reborrow {
|
||||
deref_span,
|
||||
deref_hir_id,
|
||||
},
|
||||
data,
|
||||
)),
|
||||
RefOp::AddrOf,
|
||||
) if count != 0 => {
|
||||
RefOp::Deref,
|
||||
) => {
|
||||
self.state = Some((
|
||||
State::DerefedBorrow {
|
||||
count: count - 1,
|
||||
required_precedence,
|
||||
msg,
|
||||
State::ExplicitDeref {
|
||||
deref_span_id: Some((deref_span, deref_hir_id)),
|
||||
},
|
||||
data,
|
||||
));
|
||||
},
|
||||
(state @ Some((State::ExplicitDeref { .. }, _)), RefOp::Deref) => {
|
||||
self.state = state;
|
||||
},
|
||||
(Some((State::ExplicitDerefField { name }, data)), RefOp::Deref)
|
||||
if !ty_contains_field(typeck.expr_ty(sub_expr), name) =>
|
||||
{
|
||||
self.state = Some((State::ExplicitDerefField { name }, data));
|
||||
},
|
||||
|
||||
(Some((state, data)), _) => report(cx, expr, state, data),
|
||||
}
|
||||
@ -473,131 +598,362 @@ fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Checks whether the parent node is a suitable context for switching from a deref method to the
|
||||
// deref operator.
|
||||
fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId, child_span: Span) -> bool {
|
||||
let parent = match parent {
|
||||
Some(Node::Expr(e)) if e.span.ctxt() == child_span.ctxt() => e,
|
||||
_ => return true,
|
||||
};
|
||||
match parent.kind {
|
||||
// Leave deref calls in the middle of a method chain.
|
||||
// e.g. x.deref().foo()
|
||||
ExprKind::MethodCall(_, [self_arg, ..], _) if self_arg.hir_id == child_id => false,
|
||||
|
||||
// Leave deref calls resulting in a called function
|
||||
// e.g. (x.deref())()
|
||||
ExprKind::Call(func_expr, _) if func_expr.hir_id == child_id => false,
|
||||
|
||||
// Makes an ugly suggestion
|
||||
// e.g. *x.deref() => *&*x
|
||||
ExprKind::Unary(UnOp::Deref, _)
|
||||
// Postfix expressions would require parens
|
||||
| ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
|
||||
| ExprKind::Field(..)
|
||||
| ExprKind::Index(..)
|
||||
| ExprKind::Err => false,
|
||||
|
||||
ExprKind::Box(..)
|
||||
| ExprKind::ConstBlock(..)
|
||||
| ExprKind::Array(_)
|
||||
| ExprKind::Call(..)
|
||||
| ExprKind::MethodCall(..)
|
||||
| ExprKind::Tup(..)
|
||||
| ExprKind::Binary(..)
|
||||
| ExprKind::Unary(..)
|
||||
| ExprKind::Lit(..)
|
||||
| ExprKind::Cast(..)
|
||||
| ExprKind::Type(..)
|
||||
| ExprKind::DropTemps(..)
|
||||
| ExprKind::If(..)
|
||||
| ExprKind::Loop(..)
|
||||
| ExprKind::Match(..)
|
||||
| ExprKind::Let(..)
|
||||
| ExprKind::Closure{..}
|
||||
| ExprKind::Block(..)
|
||||
| ExprKind::Assign(..)
|
||||
| ExprKind::AssignOp(..)
|
||||
| ExprKind::Path(..)
|
||||
| ExprKind::AddrOf(..)
|
||||
| ExprKind::Break(..)
|
||||
| ExprKind::Continue(..)
|
||||
| ExprKind::Ret(..)
|
||||
| ExprKind::InlineAsm(..)
|
||||
| ExprKind::Struct(..)
|
||||
| ExprKind::Repeat(..)
|
||||
| ExprKind::Yield(..) => true,
|
||||
}
|
||||
/// The position of an expression relative to it's parent.
|
||||
#[derive(Clone, Copy)]
|
||||
enum Position {
|
||||
MethodReceiver,
|
||||
/// The method is defined on a reference type. e.g. `impl Foo for &T`
|
||||
MethodReceiverRefImpl,
|
||||
Callee,
|
||||
FieldAccess(Symbol),
|
||||
Postfix,
|
||||
Deref,
|
||||
/// Any other location which will trigger auto-deref to a specific time.
|
||||
DerefStable(i8),
|
||||
/// Any other location which will trigger auto-reborrowing.
|
||||
ReborrowStable(i8),
|
||||
Other(i8),
|
||||
}
|
||||
|
||||
/// Checks if the given expression is in a position which can be auto-reborrowed.
|
||||
/// Note: This is only correct assuming auto-deref is already occurring.
|
||||
fn is_auto_reborrow_position(parent: Option<Node<'_>>) -> bool {
|
||||
match parent {
|
||||
Some(Node::Expr(parent)) => matches!(parent.kind, ExprKind::MethodCall(..) | ExprKind::Call(..)),
|
||||
Some(Node::Local(_)) => true,
|
||||
_ => false,
|
||||
impl Position {
|
||||
fn is_deref_stable(self) -> bool {
|
||||
matches!(self, Self::DerefStable(_))
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the given expression is a position which can auto-borrow.
|
||||
fn is_auto_borrow_position(parent: Option<Node<'_>>, child_id: HirId) -> bool {
|
||||
if let Some(Node::Expr(parent)) = parent {
|
||||
match parent.kind {
|
||||
// ExprKind::MethodCall(_, [self_arg, ..], _) => self_arg.hir_id == child_id,
|
||||
ExprKind::Field(..) => true,
|
||||
ExprKind::Call(f, _) => f.hir_id == child_id,
|
||||
_ => false,
|
||||
fn is_reborrow_stable(self) -> bool {
|
||||
matches!(self, Self::DerefStable(_) | Self::ReborrowStable(_))
|
||||
}
|
||||
|
||||
fn can_auto_borrow(self) -> bool {
|
||||
matches!(self, Self::MethodReceiver | Self::FieldAccess(_) | Self::Callee)
|
||||
}
|
||||
|
||||
fn lint_explicit_deref(self) -> bool {
|
||||
matches!(self, Self::Other(_) | Self::DerefStable(_) | Self::ReborrowStable(_))
|
||||
}
|
||||
|
||||
fn precedence(self) -> i8 {
|
||||
match self {
|
||||
Self::MethodReceiver
|
||||
| Self::MethodReceiverRefImpl
|
||||
| Self::Callee
|
||||
| Self::FieldAccess(_)
|
||||
| Self::Postfix => PREC_POSTFIX,
|
||||
Self::Deref => PREC_PREFIX,
|
||||
Self::DerefStable(p) | Self::ReborrowStable(p) | Self::Other(p) => p,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Walks up the parent expressions attempting to determine both how stable the auto-deref result
|
||||
/// is, and which adjustments will be applied to it. Note this will not consider auto-borrow
|
||||
/// locations as those follow different rules.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &'tcx [Adjustment<'tcx>]) {
|
||||
let mut adjustments = [].as_slice();
|
||||
let mut precedence = 0i8;
|
||||
let ctxt = e.span.ctxt();
|
||||
let position = walk_to_expr_usage(cx, e, &mut |parent, child_id| {
|
||||
// LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead.
|
||||
if adjustments.is_empty() && let Node::Expr(e) = cx.tcx.hir().get(child_id) {
|
||||
adjustments = cx.typeck_results().expr_adjustments(e);
|
||||
}
|
||||
match parent {
|
||||
Node::Local(Local { ty: Some(ty), span, .. }) if span.ctxt() == ctxt => {
|
||||
Some(binding_ty_auto_deref_stability(ty, precedence))
|
||||
},
|
||||
Node::Item(&Item {
|
||||
kind: ItemKind::Static(..) | ItemKind::Const(..),
|
||||
def_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::TraitItem(&TraitItem {
|
||||
kind: TraitItemKind::Const(..),
|
||||
def_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::ImplItem(&ImplItem {
|
||||
kind: ImplItemKind::Const(..),
|
||||
def_id,
|
||||
span,
|
||||
..
|
||||
}) if span.ctxt() == ctxt => {
|
||||
let ty = cx.tcx.type_of(def_id);
|
||||
Some(if ty.is_ref() {
|
||||
Position::DerefStable(precedence)
|
||||
} else {
|
||||
Position::Other(precedence)
|
||||
})
|
||||
},
|
||||
|
||||
Node::Item(&Item {
|
||||
kind: ItemKind::Fn(..),
|
||||
def_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::TraitItem(&TraitItem {
|
||||
kind: TraitItemKind::Fn(..),
|
||||
def_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::ImplItem(&ImplItem {
|
||||
kind: ImplItemKind::Fn(..),
|
||||
def_id,
|
||||
span,
|
||||
..
|
||||
}) if span.ctxt() == ctxt => {
|
||||
let output = cx.tcx.fn_sig(def_id.to_def_id()).skip_binder().output();
|
||||
Some(if !output.is_ref() {
|
||||
Position::Other(precedence)
|
||||
} else if output.has_placeholders() || output.has_opaque_types() {
|
||||
Position::ReborrowStable(precedence)
|
||||
} else {
|
||||
Position::DerefStable(precedence)
|
||||
})
|
||||
},
|
||||
|
||||
Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind {
|
||||
ExprKind::Ret(_) => {
|
||||
let output = cx
|
||||
.tcx
|
||||
.fn_sig(cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()))
|
||||
.skip_binder()
|
||||
.output();
|
||||
Some(if !output.is_ref() {
|
||||
Position::Other(precedence)
|
||||
} else if output.has_placeholders() || output.has_opaque_types() {
|
||||
Position::ReborrowStable(precedence)
|
||||
} else {
|
||||
Position::DerefStable(precedence)
|
||||
})
|
||||
},
|
||||
ExprKind::Call(func, _) if func.hir_id == child_id => (child_id == e.hir_id).then(|| Position::Callee),
|
||||
ExprKind::Call(func, args) => args
|
||||
.iter()
|
||||
.position(|arg| arg.hir_id == child_id)
|
||||
.zip(expr_sig(cx, func))
|
||||
.and_then(|(i, sig)| sig.input_with_hir(i))
|
||||
.map(|(hir_ty, ty)| match hir_ty {
|
||||
// Type inference for closures can depend on how they're called. Only go by the explicit
|
||||
// types here.
|
||||
Some(ty) => binding_ty_auto_deref_stability(ty, precedence),
|
||||
None => param_auto_deref_stability(ty.skip_binder(), precedence),
|
||||
}),
|
||||
ExprKind::MethodCall(_, args, _) => {
|
||||
let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap();
|
||||
args.iter().position(|arg| arg.hir_id == child_id).map(|i| {
|
||||
if i == 0 {
|
||||
// Check for calls to trait methods where the trait is implemented on a reference.
|
||||
// Two cases need to be handled:
|
||||
// * `self` methods on `&T` will never have auto-borrow
|
||||
// * `&self` methods on `&T` can have auto-borrow, but `&self` methods on `T` will take
|
||||
// priority.
|
||||
if e.hir_id != child_id {
|
||||
Position::ReborrowStable(precedence)
|
||||
} else if let Some(trait_id) = cx.tcx.trait_of_item(id)
|
||||
&& let arg_ty = cx.tcx.erase_regions(cx.typeck_results().expr_ty_adjusted(e))
|
||||
&& let ty::Ref(_, sub_ty, _) = *arg_ty.kind()
|
||||
&& let subs = cx.typeck_results().node_substs_opt(child_id).unwrap_or_else(
|
||||
|| cx.tcx.mk_substs([].iter())
|
||||
) && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() {
|
||||
// Trait methods taking `&self`
|
||||
sub_ty
|
||||
} else {
|
||||
// Trait methods taking `self`
|
||||
arg_ty
|
||||
} && impl_ty.is_ref()
|
||||
&& cx.tcx.infer_ctxt().enter(|infcx|
|
||||
infcx
|
||||
.type_implements_trait(trait_id, impl_ty, subs, cx.param_env)
|
||||
.must_apply_modulo_regions()
|
||||
)
|
||||
{
|
||||
Position::MethodReceiverRefImpl
|
||||
} else {
|
||||
Position::MethodReceiver
|
||||
}
|
||||
} else {
|
||||
param_auto_deref_stability(cx.tcx.fn_sig(id).skip_binder().inputs()[i], precedence)
|
||||
}
|
||||
})
|
||||
},
|
||||
ExprKind::Struct(path, fields, _) => {
|
||||
let variant = variant_of_res(cx, cx.qpath_res(path, parent.hir_id));
|
||||
fields
|
||||
.iter()
|
||||
.find(|f| f.expr.hir_id == child_id)
|
||||
.zip(variant)
|
||||
.and_then(|(field, variant)| variant.fields.iter().find(|f| f.name == field.ident.name))
|
||||
.map(|field| param_auto_deref_stability(cx.tcx.type_of(field.did), precedence))
|
||||
},
|
||||
ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(Position::FieldAccess(name.name)),
|
||||
ExprKind::Unary(UnOp::Deref, child) if child.hir_id == e.hir_id => Some(Position::Deref),
|
||||
ExprKind::Match(child, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
|
||||
| ExprKind::Index(child, _)
|
||||
if child.hir_id == e.hir_id =>
|
||||
{
|
||||
Some(Position::Postfix)
|
||||
},
|
||||
_ if child_id == e.hir_id => {
|
||||
precedence = parent.precedence().order();
|
||||
None
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.unwrap_or(Position::Other(precedence));
|
||||
(position, adjustments)
|
||||
}
|
||||
|
||||
// Checks the stability of auto-deref when assigned to a binding with the given explicit type.
|
||||
//
|
||||
// e.g.
|
||||
// let x = Box::new(Box::new(0u32));
|
||||
// let y1: &Box<_> = x.deref();
|
||||
// let y2: &Box<_> = &x;
|
||||
//
|
||||
// Here `y1` and `y2` would resolve to different types, so the type `&Box<_>` is not stable when
|
||||
// switching to auto-dereferencing.
|
||||
fn binding_ty_auto_deref_stability(ty: &hir::Ty<'_>, precedence: i8) -> Position {
|
||||
let TyKind::Rptr(_, ty) = &ty.kind else {
|
||||
return Position::Other(precedence);
|
||||
};
|
||||
let mut ty = ty;
|
||||
|
||||
loop {
|
||||
break match ty.ty.kind {
|
||||
TyKind::Rptr(_, ref ref_ty) => {
|
||||
ty = ref_ty;
|
||||
continue;
|
||||
},
|
||||
TyKind::Path(
|
||||
QPath::TypeRelative(_, path)
|
||||
| QPath::Resolved(
|
||||
_,
|
||||
Path {
|
||||
segments: [.., path], ..
|
||||
},
|
||||
),
|
||||
) => {
|
||||
if let Some(args) = path.args
|
||||
&& args.args.iter().any(|arg| match arg {
|
||||
GenericArg::Infer(_) => true,
|
||||
GenericArg::Type(ty) => ty_contains_infer(ty),
|
||||
_ => false,
|
||||
})
|
||||
{
|
||||
Position::ReborrowStable(precedence)
|
||||
} else {
|
||||
Position::DerefStable(precedence)
|
||||
}
|
||||
},
|
||||
TyKind::Slice(_)
|
||||
| TyKind::Array(..)
|
||||
| TyKind::BareFn(_)
|
||||
| TyKind::Never
|
||||
| TyKind::Tup(_)
|
||||
| TyKind::Ptr(_)
|
||||
| TyKind::TraitObject(..)
|
||||
| TyKind::Path(_) => Position::DerefStable(precedence),
|
||||
TyKind::OpaqueDef(..)
|
||||
| TyKind::Infer
|
||||
| TyKind::Typeof(..)
|
||||
| TyKind::Err => Position::ReborrowStable(precedence),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Checks whether a type is inferred at some point.
|
||||
// e.g. `_`, `Box<_>`, `[_]`
|
||||
fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
|
||||
struct V(bool);
|
||||
impl Visitor<'_> for V {
|
||||
fn visit_ty(&mut self, ty: &hir::Ty<'_>) {
|
||||
if self.0
|
||||
|| matches!(
|
||||
ty.kind,
|
||||
TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(_) | TyKind::Err
|
||||
)
|
||||
{
|
||||
self.0 = true;
|
||||
} else {
|
||||
walk_ty(self, ty);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_generic_arg(&mut self, arg: &GenericArg<'_>) {
|
||||
if self.0 || matches!(arg, GenericArg::Infer(_)) {
|
||||
self.0 = true;
|
||||
} else if let GenericArg::Type(ty) = arg {
|
||||
self.visit_ty(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut v = V(false);
|
||||
v.visit_ty(ty);
|
||||
v.0
|
||||
}
|
||||
|
||||
// Checks whether a type is stable when switching to auto dereferencing,
|
||||
fn param_auto_deref_stability(ty: Ty<'_>, precedence: i8) -> Position {
|
||||
let ty::Ref(_, mut ty, _) = *ty.kind() else {
|
||||
return Position::Other(precedence);
|
||||
};
|
||||
|
||||
loop {
|
||||
break match *ty.kind() {
|
||||
ty::Ref(_, ref_ty, _) => {
|
||||
ty = ref_ty;
|
||||
continue;
|
||||
},
|
||||
ty::Infer(_)
|
||||
| ty::Error(_)
|
||||
| ty::Param(_)
|
||||
| ty::Bound(..)
|
||||
| ty::Opaque(..)
|
||||
| ty::Placeholder(_)
|
||||
| ty::Dynamic(..) => Position::ReborrowStable(precedence),
|
||||
ty::Adt(..) if ty.has_placeholders() || ty.has_param_types_or_consts() => {
|
||||
Position::ReborrowStable(precedence)
|
||||
},
|
||||
ty::Adt(..)
|
||||
| ty::Bool
|
||||
| ty::Char
|
||||
| ty::Int(_)
|
||||
| ty::Uint(_)
|
||||
| ty::Float(_)
|
||||
| ty::Foreign(_)
|
||||
| ty::Str
|
||||
| ty::Array(..)
|
||||
| ty::Slice(..)
|
||||
| ty::RawPtr(..)
|
||||
| ty::FnDef(..)
|
||||
| ty::FnPtr(_)
|
||||
| ty::Closure(..)
|
||||
| ty::Generator(..)
|
||||
| ty::GeneratorWitness(..)
|
||||
| ty::Never
|
||||
| ty::Tuple(_)
|
||||
| ty::Projection(_) => Position::DerefStable(precedence),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool {
|
||||
if let ty::Adt(adt, _) = *ty.kind() {
|
||||
adt.is_struct() && adt.all_fields().any(|f| f.name == name)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjustments are sometimes made in the parent block rather than the expression itself.
|
||||
fn find_adjustments<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
typeck: &'tcx TypeckResults<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
) -> &'tcx [Adjustment<'tcx>] {
|
||||
let map = tcx.hir();
|
||||
let mut iter = map.parent_iter(expr.hir_id);
|
||||
let mut prev = expr;
|
||||
|
||||
loop {
|
||||
match typeck.expr_adjustments(prev) {
|
||||
[] => (),
|
||||
a => break a,
|
||||
};
|
||||
|
||||
match iter.next().map(|(_, x)| x) {
|
||||
Some(Node::Block(_)) => {
|
||||
if let Some((_, Node::Expr(e))) = iter.next() {
|
||||
prev = e;
|
||||
} else {
|
||||
// This shouldn't happen. Blocks are always contained in an expression.
|
||||
break &[];
|
||||
}
|
||||
},
|
||||
Some(Node::Expr(&Expr {
|
||||
kind: ExprKind::Break(Destination { target_id: Ok(id), .. }, _),
|
||||
..
|
||||
})) => {
|
||||
if let Some(Node::Expr(e)) = map.find(id) {
|
||||
prev = e;
|
||||
iter = map.parent_iter(id);
|
||||
} else {
|
||||
// This shouldn't happen. The destination should exist.
|
||||
break &[];
|
||||
}
|
||||
},
|
||||
_ => break &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::needless_pass_by_value)]
|
||||
fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: StateData) {
|
||||
#[expect(clippy::needless_pass_by_value, clippy::too_many_lines)]
|
||||
fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData) {
|
||||
match state {
|
||||
State::DerefMethod {
|
||||
ty_changed_count,
|
||||
@ -647,15 +1003,14 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: S
|
||||
app,
|
||||
);
|
||||
},
|
||||
State::DerefedBorrow {
|
||||
required_precedence,
|
||||
msg,
|
||||
..
|
||||
} => {
|
||||
State::DerefedBorrow(state) => {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0;
|
||||
span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, msg, |diag| {
|
||||
let sugg = if required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) {
|
||||
let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
|
||||
span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| {
|
||||
let sugg = if !snip_is_macro
|
||||
&& expr.precedence().order() < data.position.precedence()
|
||||
&& !has_enclosing_paren(&snip)
|
||||
{
|
||||
format!("({})", snip)
|
||||
} else {
|
||||
snip.into()
|
||||
@ -663,6 +1018,48 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: S
|
||||
diag.span_suggestion(data.span, "change this to", sugg, app);
|
||||
});
|
||||
},
|
||||
State::ExplicitDeref { deref_span_id } => {
|
||||
let (span, hir_id, precedence) = if let Some((span, hir_id)) = deref_span_id
|
||||
&& !cx.typeck_results().expr_ty(expr).is_ref()
|
||||
{
|
||||
(span, hir_id, PREC_PREFIX)
|
||||
} else {
|
||||
(data.span, data.hir_id, data.position.precedence())
|
||||
};
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
EXPLICIT_AUTO_DEREF,
|
||||
hir_id,
|
||||
span,
|
||||
"deref which would be done by auto-deref",
|
||||
|diag| {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, span.ctxt(), "..", &mut app);
|
||||
let sugg =
|
||||
if !snip_is_macro && expr.precedence().order() < precedence && !has_enclosing_paren(&snip) {
|
||||
format!("({})", snip)
|
||||
} else {
|
||||
snip.into()
|
||||
};
|
||||
diag.span_suggestion(span, "try this", sugg, app);
|
||||
},
|
||||
);
|
||||
},
|
||||
State::ExplicitDerefField { .. } => {
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
EXPLICIT_AUTO_DEREF,
|
||||
data.hir_id,
|
||||
data.span,
|
||||
"deref which would be done by auto-deref",
|
||||
|diag| {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0;
|
||||
diag.span_suggestion(data.span, "try this", snip.into_owned(), app);
|
||||
},
|
||||
);
|
||||
},
|
||||
State::Borrow | State::Reborrow { .. } => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -190,7 +190,7 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n
|
||||
.map(|e| *e.0)
|
||||
.collect();
|
||||
}
|
||||
let (what, value) = match (pre.is_empty(), post.is_empty()) {
|
||||
let (what, value) = match (have_no_extra_prefix(&pre), post.is_empty()) {
|
||||
(true, true) => return,
|
||||
(false, _) => ("pre", pre.join("")),
|
||||
(true, false) => {
|
||||
@ -212,6 +212,11 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n
|
||||
);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn have_no_extra_prefix(prefixes: &[&str]) -> bool {
|
||||
prefixes.iter().all(|p| p == &"" || p == &"_")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn to_camel_case(item_name: &str) -> String {
|
||||
let mut s = String::new();
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::diagnostics::span_lint_hir;
|
||||
use clippy_utils::ty::contains_ty;
|
||||
use rustc_hir::intravisit;
|
||||
use rustc_hir::{self, AssocItemKind, Body, FnDecl, HirId, HirIdSet, Impl, ItemKind, Node};
|
||||
@ -118,9 +118,10 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
|
||||
});
|
||||
|
||||
for node in v.set {
|
||||
span_lint(
|
||||
span_lint_hir(
|
||||
cx,
|
||||
BOXED_LOCAL,
|
||||
node,
|
||||
cx.tcx.hir().span(node),
|
||||
"local variable doesn't need to be boxed here",
|
||||
);
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use clippy_utils::{
|
||||
diagnostics::span_lint_and_sugg,
|
||||
diagnostics::span_lint_hir_and_then,
|
||||
get_async_fn_body, is_async_fn,
|
||||
source::{snippet_with_applicability, snippet_with_context, walk_span_to_context},
|
||||
visitors::expr_visitor_no_bodies,
|
||||
@ -43,31 +43,38 @@ declare_clippy_lint! {
|
||||
|
||||
declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]);
|
||||
|
||||
fn lint_return(cx: &LateContext<'_>, span: Span) {
|
||||
fn lint_return(cx: &LateContext<'_>, emission_place: HirId, span: Span) {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let snip = snippet_with_applicability(cx, span, "..", &mut app);
|
||||
span_lint_and_sugg(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
IMPLICIT_RETURN,
|
||||
emission_place,
|
||||
span,
|
||||
"missing `return` statement",
|
||||
"add `return` as shown",
|
||||
format!("return {}", snip),
|
||||
app,
|
||||
|diag| {
|
||||
diag.span_suggestion(span, "add `return` as shown", format!("return {}", snip), app);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn lint_break(cx: &LateContext<'_>, break_span: Span, expr_span: Span) {
|
||||
fn lint_break(cx: &LateContext<'_>, emission_place: HirId, break_span: Span, expr_span: Span) {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0;
|
||||
span_lint_and_sugg(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
IMPLICIT_RETURN,
|
||||
emission_place,
|
||||
break_span,
|
||||
"missing `return` statement",
|
||||
"change `break` to `return` as shown",
|
||||
format!("return {}", snip),
|
||||
app,
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
break_span,
|
||||
"change `break` to `return` as shown",
|
||||
format!("return {}", snip),
|
||||
app,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -152,7 +159,7 @@ fn lint_implicit_returns(
|
||||
// At this point sub_expr can be `None` in async functions which either diverge, or return
|
||||
// the unit type.
|
||||
if let Some(sub_expr) = sub_expr {
|
||||
lint_break(cx, e.span, sub_expr.span);
|
||||
lint_break(cx, e.hir_id, e.span, sub_expr.span);
|
||||
}
|
||||
} else {
|
||||
// the break expression is from a macro call, add a return to the loop
|
||||
@ -166,10 +173,10 @@ fn lint_implicit_returns(
|
||||
if add_return {
|
||||
#[expect(clippy::option_if_let_else)]
|
||||
if let Some(span) = call_site_span {
|
||||
lint_return(cx, span);
|
||||
lint_return(cx, expr.hir_id, span);
|
||||
LintLocation::Parent
|
||||
} else {
|
||||
lint_return(cx, expr.span);
|
||||
lint_return(cx, expr.hir_id, expr.span);
|
||||
LintLocation::Inner
|
||||
}
|
||||
} else {
|
||||
@ -198,10 +205,10 @@ fn lint_implicit_returns(
|
||||
{
|
||||
#[expect(clippy::option_if_let_else)]
|
||||
if let Some(span) = call_site_span {
|
||||
lint_return(cx, span);
|
||||
lint_return(cx, expr.hir_id, span);
|
||||
LintLocation::Parent
|
||||
} else {
|
||||
lint_return(cx, expr.span);
|
||||
lint_return(cx, expr.hir_id, expr.span);
|
||||
LintLocation::Inner
|
||||
}
|
||||
},
|
||||
|
@ -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
|
||||
}
|
@ -24,7 +24,7 @@ declare_clippy_lint! {
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust.ignore
|
||||
/// ```rust,ignore
|
||||
/// pub static a = [0u32; 1_000_000];
|
||||
/// ```
|
||||
#[clippy::version = "1.44.0"]
|
||||
|
@ -99,12 +99,13 @@ declare_clippy_lint! {
|
||||
|
||||
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_DROP]);
|
||||
|
||||
const SYNC_GUARD_PATHS: [&[&str]; 5] = [
|
||||
const SYNC_GUARD_PATHS: [&[&str]; 6] = [
|
||||
&paths::MUTEX_GUARD,
|
||||
&paths::RWLOCK_READ_GUARD,
|
||||
&paths::RWLOCK_WRITE_GUARD,
|
||||
&paths::PARKING_LOT_RAWMUTEX,
|
||||
&paths::PARKING_LOT_RAWRWLOCK,
|
||||
&paths::PARKING_LOT_MUTEX_GUARD,
|
||||
&paths::PARKING_LOT_RWLOCK_READ_GUARD,
|
||||
&paths::PARKING_LOT_RWLOCK_WRITE_GUARD,
|
||||
];
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
|
||||
|
@ -3,12 +3,9 @@
|
||||
// Manual edits will be overwritten.
|
||||
|
||||
store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS),
|
||||
LintId::of(almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE),
|
||||
LintId::of(approx_const::APPROX_CONSTANT),
|
||||
LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
|
||||
LintId::of(assign_ops::ASSIGN_OP_PATTERN),
|
||||
LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP),
|
||||
LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC),
|
||||
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
|
||||
LintId::of(attrs::DEPRECATED_CFG_ATTR),
|
||||
@ -18,8 +15,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE),
|
||||
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
|
||||
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
|
||||
LintId::of(bit_mask::BAD_BIT_MASK),
|
||||
LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
|
||||
LintId::of(blacklisted_name::BLACKLISTED_NAME),
|
||||
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
|
||||
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
|
||||
@ -43,6 +38,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(copies::IF_SAME_THEN_ELSE),
|
||||
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
|
||||
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
|
||||
LintId::of(default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY),
|
||||
LintId::of(dereference::EXPLICIT_AUTO_DEREF),
|
||||
LintId::of(dereference::NEEDLESS_BORROW),
|
||||
LintId::of(derivable_impls::DERIVABLE_IMPLS),
|
||||
LintId::of(derive::DERIVE_HASH_XOR_EQ),
|
||||
@ -52,7 +49,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(disallowed_types::DISALLOWED_TYPES),
|
||||
LintId::of(doc::MISSING_SAFETY_DOC),
|
||||
LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
|
||||
LintId::of(double_comparison::DOUBLE_COMPARISONS),
|
||||
LintId::of(double_parens::DOUBLE_PARENS),
|
||||
LintId::of(drop_forget_ref::DROP_COPY),
|
||||
LintId::of(drop_forget_ref::DROP_NON_DROP),
|
||||
@ -62,18 +58,13 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(drop_forget_ref::FORGET_REF),
|
||||
LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS),
|
||||
LintId::of(duplicate_mod::DUPLICATE_MOD),
|
||||
LintId::of(duration_subsec::DURATION_SUBSEC),
|
||||
LintId::of(entry::MAP_ENTRY),
|
||||
LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
|
||||
LintId::of(enum_variants::ENUM_VARIANT_NAMES),
|
||||
LintId::of(enum_variants::MODULE_INCEPTION),
|
||||
LintId::of(eq_op::EQ_OP),
|
||||
LintId::of(eq_op::OP_REF),
|
||||
LintId::of(erasing_op::ERASING_OP),
|
||||
LintId::of(escape::BOXED_LOCAL),
|
||||
LintId::of(eta_reduction::REDUNDANT_CLOSURE),
|
||||
LintId::of(explicit_write::EXPLICIT_WRITE),
|
||||
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
|
||||
LintId::of(float_literal::EXCESSIVE_PRECISION),
|
||||
LintId::of(format::USELESS_FORMAT),
|
||||
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
|
||||
@ -93,7 +84,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(functions::RESULT_UNIT_ERR),
|
||||
LintId::of(functions::TOO_MANY_ARGUMENTS),
|
||||
LintId::of(get_first::GET_FIRST),
|
||||
LintId::of(identity_op::IDENTITY_OP),
|
||||
LintId::of(if_let_mutex::IF_LET_MUTEX),
|
||||
LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING),
|
||||
LintId::of(infinite_iter::INFINITE_ITER),
|
||||
@ -118,6 +108,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(loops::FOR_KV_MAP),
|
||||
LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
|
||||
LintId::of(loops::ITER_NEXT_LOOP),
|
||||
LintId::of(loops::MANUAL_FIND),
|
||||
LintId::of(loops::MANUAL_FLATTEN),
|
||||
LintId::of(loops::MANUAL_MEMCPY),
|
||||
LintId::of(loops::MISSING_SPIN_LOOP),
|
||||
@ -134,6 +125,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(manual_async_fn::MANUAL_ASYNC_FN),
|
||||
LintId::of(manual_bits::MANUAL_BITS),
|
||||
LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
|
||||
LintId::of(manual_rem_euclid::MANUAL_REM_EUCLID),
|
||||
LintId::of(manual_retain::MANUAL_RETAIN),
|
||||
LintId::of(manual_strip::MANUAL_STRIP),
|
||||
LintId::of(map_clone::MAP_CLONE),
|
||||
LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
|
||||
@ -220,9 +213,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(methods::WRONG_SELF_CONVENTION),
|
||||
LintId::of(methods::ZST_OFFSET),
|
||||
LintId::of(minmax::MIN_MAX),
|
||||
LintId::of(misc::CMP_NAN),
|
||||
LintId::of(misc::CMP_OWNED),
|
||||
LintId::of(misc::MODULO_ONE),
|
||||
LintId::of(misc::SHORT_CIRCUIT_STATEMENT),
|
||||
LintId::of(misc::TOPLEVEL_REF_ARG),
|
||||
LintId::of(misc::ZERO_PTR),
|
||||
@ -256,6 +246,23 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
|
||||
LintId::of(octal_escapes::OCTAL_ESCAPES),
|
||||
LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
|
||||
LintId::of(operators::ABSURD_EXTREME_COMPARISONS),
|
||||
LintId::of(operators::ASSIGN_OP_PATTERN),
|
||||
LintId::of(operators::BAD_BIT_MASK),
|
||||
LintId::of(operators::CMP_NAN),
|
||||
LintId::of(operators::CMP_OWNED),
|
||||
LintId::of(operators::DOUBLE_COMPARISONS),
|
||||
LintId::of(operators::DURATION_SUBSEC),
|
||||
LintId::of(operators::EQ_OP),
|
||||
LintId::of(operators::ERASING_OP),
|
||||
LintId::of(operators::FLOAT_EQUALITY_WITHOUT_ABS),
|
||||
LintId::of(operators::IDENTITY_OP),
|
||||
LintId::of(operators::INEFFECTIVE_BIT_MASK),
|
||||
LintId::of(operators::MISREFACTORED_ASSIGN_OP),
|
||||
LintId::of(operators::MODULO_ONE),
|
||||
LintId::of(operators::OP_REF),
|
||||
LintId::of(operators::PTR_EQ),
|
||||
LintId::of(operators::SELF_ASSIGNMENT),
|
||||
LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
|
||||
LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
|
||||
LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
|
||||
@ -264,7 +271,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(ptr::INVALID_NULL_PTR_USAGE),
|
||||
LintId::of(ptr::MUT_FROM_REF),
|
||||
LintId::of(ptr::PTR_ARG),
|
||||
LintId::of(ptr_eq::PTR_EQ),
|
||||
LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST),
|
||||
LintId::of(question_mark::QUESTION_MARK),
|
||||
LintId::of(ranges::MANUAL_RANGE_CONTAINS),
|
||||
@ -282,7 +288,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(repeat_once::REPEAT_ONCE),
|
||||
LintId::of(returns::LET_AND_RETURN),
|
||||
LintId::of(returns::NEEDLESS_RETURN),
|
||||
LintId::of(self_assignment::SELF_ASSIGNMENT),
|
||||
LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS),
|
||||
LintId::of(serde_api::SERDE_API_MISUSE),
|
||||
LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
|
||||
|
@ -9,21 +9,21 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
|
||||
LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN),
|
||||
LintId::of(casts::CHAR_LIT_AS_U8),
|
||||
LintId::of(casts::UNNECESSARY_CAST),
|
||||
LintId::of(dereference::EXPLICIT_AUTO_DEREF),
|
||||
LintId::of(derivable_impls::DERIVABLE_IMPLS),
|
||||
LintId::of(double_comparison::DOUBLE_COMPARISONS),
|
||||
LintId::of(double_parens::DOUBLE_PARENS),
|
||||
LintId::of(duration_subsec::DURATION_SUBSEC),
|
||||
LintId::of(explicit_write::EXPLICIT_WRITE),
|
||||
LintId::of(format::USELESS_FORMAT),
|
||||
LintId::of(functions::TOO_MANY_ARGUMENTS),
|
||||
LintId::of(identity_op::IDENTITY_OP),
|
||||
LintId::of(int_plus_one::INT_PLUS_ONE),
|
||||
LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
|
||||
LintId::of(lifetimes::NEEDLESS_LIFETIMES),
|
||||
LintId::of(loops::EXPLICIT_COUNTER_LOOP),
|
||||
LintId::of(loops::MANUAL_FIND),
|
||||
LintId::of(loops::MANUAL_FLATTEN),
|
||||
LintId::of(loops::SINGLE_ELEMENT_LOOP),
|
||||
LintId::of(loops::WHILE_LET_LOOP),
|
||||
LintId::of(manual_rem_euclid::MANUAL_REM_EUCLID),
|
||||
LintId::of(manual_strip::MANUAL_STRIP),
|
||||
LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
|
||||
LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
|
||||
@ -69,6 +69,9 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
|
||||
LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
|
||||
LintId::of(no_effect::NO_EFFECT),
|
||||
LintId::of(no_effect::UNNECESSARY_OPERATION),
|
||||
LintId::of(operators::DOUBLE_COMPARISONS),
|
||||
LintId::of(operators::DURATION_SUBSEC),
|
||||
LintId::of(operators::IDENTITY_OP),
|
||||
LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
|
||||
LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
|
||||
LintId::of(precedence::PRECEDENCE),
|
||||
|
@ -3,14 +3,11 @@
|
||||
// Manual edits will be overwritten.
|
||||
|
||||
store.register_group(true, "clippy::correctness", Some("clippy_correctness"), vec![
|
||||
LintId::of(absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS),
|
||||
LintId::of(approx_const::APPROX_CONSTANT),
|
||||
LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC),
|
||||
LintId::of(attrs::DEPRECATED_SEMVER),
|
||||
LintId::of(attrs::MISMATCHED_TARGET_OS),
|
||||
LintId::of(attrs::USELESS_ATTRIBUTE),
|
||||
LintId::of(bit_mask::BAD_BIT_MASK),
|
||||
LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
|
||||
LintId::of(booleans::LOGIC_BUG),
|
||||
LintId::of(casts::CAST_REF_TO_MUT),
|
||||
LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
|
||||
@ -24,8 +21,6 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
|
||||
LintId::of(drop_forget_ref::FORGET_REF),
|
||||
LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS),
|
||||
LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
|
||||
LintId::of(eq_op::EQ_OP),
|
||||
LintId::of(erasing_op::ERASING_OP),
|
||||
LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
|
||||
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
|
||||
LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF),
|
||||
@ -47,17 +42,22 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
|
||||
LintId::of(methods::UNINIT_ASSUMED_INIT),
|
||||
LintId::of(methods::ZST_OFFSET),
|
||||
LintId::of(minmax::MIN_MAX),
|
||||
LintId::of(misc::CMP_NAN),
|
||||
LintId::of(misc::MODULO_ONE),
|
||||
LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
|
||||
LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
|
||||
LintId::of(operators::ABSURD_EXTREME_COMPARISONS),
|
||||
LintId::of(operators::BAD_BIT_MASK),
|
||||
LintId::of(operators::CMP_NAN),
|
||||
LintId::of(operators::EQ_OP),
|
||||
LintId::of(operators::ERASING_OP),
|
||||
LintId::of(operators::INEFFECTIVE_BIT_MASK),
|
||||
LintId::of(operators::MODULO_ONE),
|
||||
LintId::of(operators::SELF_ASSIGNMENT),
|
||||
LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
|
||||
LintId::of(ptr::INVALID_NULL_PTR_USAGE),
|
||||
LintId::of(ptr::MUT_FROM_REF),
|
||||
LintId::of(ranges::REVERSED_EMPTY_RANGES),
|
||||
LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC),
|
||||
LintId::of(regex::INVALID_REGEX),
|
||||
LintId::of(self_assignment::SELF_ASSIGNMENT),
|
||||
LintId::of(serde_api::SERDE_API_MISUSE),
|
||||
LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
|
||||
LintId::of(swap::ALMOST_SWAPPED),
|
||||
|
@ -6,6 +6,7 @@ store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
|
||||
LintId::of(utils::internal_lints::CLIPPY_LINTS_INTERNAL),
|
||||
LintId::of(utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
|
||||
LintId::of(utils::internal_lints::COMPILER_LINT_FUNCTIONS),
|
||||
LintId::of(utils::internal_lints::DEFAULT_DEPRECATION_REASON),
|
||||
LintId::of(utils::internal_lints::DEFAULT_LINT),
|
||||
LintId::of(utils::internal_lints::IF_CHAIN_STYLE),
|
||||
LintId::of(utils::internal_lints::INTERNING_DEFINED_SYMBOL),
|
||||
|
@ -10,6 +10,8 @@ store.register_lints(&[
|
||||
#[cfg(feature = "internal")]
|
||||
utils::internal_lints::COMPILER_LINT_FUNCTIONS,
|
||||
#[cfg(feature = "internal")]
|
||||
utils::internal_lints::DEFAULT_DEPRECATION_REASON,
|
||||
#[cfg(feature = "internal")]
|
||||
utils::internal_lints::DEFAULT_LINT,
|
||||
#[cfg(feature = "internal")]
|
||||
utils::internal_lints::IF_CHAIN_STYLE,
|
||||
@ -33,7 +35,6 @@ store.register_lints(&[
|
||||
utils::internal_lints::PRODUCE_ICE,
|
||||
#[cfg(feature = "internal")]
|
||||
utils::internal_lints::UNNECESSARY_SYMBOL_STR,
|
||||
absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS,
|
||||
almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE,
|
||||
approx_const::APPROX_CONSTANT,
|
||||
as_conversions::AS_CONVERSIONS,
|
||||
@ -41,8 +42,6 @@ store.register_lints(&[
|
||||
asm_syntax::INLINE_ASM_X86_ATT_SYNTAX,
|
||||
asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX,
|
||||
assertions_on_constants::ASSERTIONS_ON_CONSTANTS,
|
||||
assign_ops::ASSIGN_OP_PATTERN,
|
||||
assign_ops::MISREFACTORED_ASSIGN_OP,
|
||||
async_yields_async::ASYNC_YIELDS_ASYNC,
|
||||
attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON,
|
||||
attrs::BLANKET_CLIPPY_RESTRICTION_LINTS,
|
||||
@ -55,9 +54,6 @@ store.register_lints(&[
|
||||
await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE,
|
||||
await_holding_invalid::AWAIT_HOLDING_LOCK,
|
||||
await_holding_invalid::AWAIT_HOLDING_REFCELL_REF,
|
||||
bit_mask::BAD_BIT_MASK,
|
||||
bit_mask::INEFFECTIVE_BIT_MASK,
|
||||
bit_mask::VERBOSE_BIT_MASK,
|
||||
blacklisted_name::BLACKLISTED_NAME,
|
||||
blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS,
|
||||
bool_assert_comparison::BOOL_ASSERT_COMPARISON,
|
||||
@ -105,8 +101,10 @@ store.register_lints(&[
|
||||
dbg_macro::DBG_MACRO,
|
||||
default::DEFAULT_TRAIT_ACCESS,
|
||||
default::FIELD_REASSIGN_WITH_DEFAULT,
|
||||
default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY,
|
||||
default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK,
|
||||
default_union_representation::DEFAULT_UNION_REPRESENTATION,
|
||||
dereference::EXPLICIT_AUTO_DEREF,
|
||||
dereference::EXPLICIT_DEREF_METHODS,
|
||||
dereference::NEEDLESS_BORROW,
|
||||
dereference::REF_BINDING_TO_REFERENCE,
|
||||
@ -125,7 +123,6 @@ store.register_lints(&[
|
||||
doc::MISSING_SAFETY_DOC,
|
||||
doc::NEEDLESS_DOCTEST_MAIN,
|
||||
doc_link_with_quotes::DOC_LINK_WITH_QUOTES,
|
||||
double_comparison::DOUBLE_COMPARISONS,
|
||||
double_parens::DOUBLE_PARENS,
|
||||
drop_forget_ref::DROP_COPY,
|
||||
drop_forget_ref::DROP_NON_DROP,
|
||||
@ -135,7 +132,6 @@ store.register_lints(&[
|
||||
drop_forget_ref::FORGET_REF,
|
||||
drop_forget_ref::UNDROPPED_MANUALLY_DROPS,
|
||||
duplicate_mod::DUPLICATE_MOD,
|
||||
duration_subsec::DURATION_SUBSEC,
|
||||
else_if_without_else::ELSE_IF_WITHOUT_ELSE,
|
||||
empty_drop::EMPTY_DROP,
|
||||
empty_enum::EMPTY_ENUM,
|
||||
@ -145,10 +141,7 @@ store.register_lints(&[
|
||||
enum_variants::ENUM_VARIANT_NAMES,
|
||||
enum_variants::MODULE_INCEPTION,
|
||||
enum_variants::MODULE_NAME_REPETITIONS,
|
||||
eq_op::EQ_OP,
|
||||
eq_op::OP_REF,
|
||||
equatable_if_let::EQUATABLE_IF_LET,
|
||||
erasing_op::ERASING_OP,
|
||||
escape::BOXED_LOCAL,
|
||||
eta_reduction::REDUNDANT_CLOSURE,
|
||||
eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
|
||||
@ -159,7 +152,6 @@ store.register_lints(&[
|
||||
exit::EXIT,
|
||||
explicit_write::EXPLICIT_WRITE,
|
||||
fallible_impl_from::FALLIBLE_IMPL_FROM,
|
||||
float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS,
|
||||
float_literal::EXCESSIVE_PRECISION,
|
||||
float_literal::LOSSY_FLOAT_LITERAL,
|
||||
floating_point_arithmetic::IMPRECISE_FLOPS,
|
||||
@ -185,7 +177,6 @@ store.register_lints(&[
|
||||
functions::TOO_MANY_LINES,
|
||||
future_not_send::FUTURE_NOT_SEND,
|
||||
get_first::GET_FIRST,
|
||||
identity_op::IDENTITY_OP,
|
||||
if_let_mutex::IF_LET_MUTEX,
|
||||
if_not_else::IF_NOT_ELSE,
|
||||
if_then_some_else_none::IF_THEN_SOME_ELSE_NONE,
|
||||
@ -204,7 +195,6 @@ store.register_lints(&[
|
||||
init_numbered_fields::INIT_NUMBERED_FIELDS,
|
||||
inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
|
||||
int_plus_one::INT_PLUS_ONE,
|
||||
integer_division::INTEGER_DIVISION,
|
||||
invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS,
|
||||
items_after_statements::ITEMS_AFTER_STATEMENTS,
|
||||
iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR,
|
||||
@ -234,6 +224,7 @@ store.register_lints(&[
|
||||
loops::FOR_KV_MAP,
|
||||
loops::FOR_LOOPS_OVER_FALLIBLES,
|
||||
loops::ITER_NEXT_LOOP,
|
||||
loops::MANUAL_FIND,
|
||||
loops::MANUAL_FLATTEN,
|
||||
loops::MANUAL_MEMCPY,
|
||||
loops::MISSING_SPIN_LOOP,
|
||||
@ -253,6 +244,8 @@ store.register_lints(&[
|
||||
manual_bits::MANUAL_BITS,
|
||||
manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
|
||||
manual_ok_or::MANUAL_OK_OR,
|
||||
manual_rem_euclid::MANUAL_REM_EUCLID,
|
||||
manual_retain::MANUAL_RETAIN,
|
||||
manual_strip::MANUAL_STRIP,
|
||||
map_clone::MAP_CLONE,
|
||||
map_err_ignore::MAP_ERR_IGNORE,
|
||||
@ -364,11 +357,6 @@ store.register_lints(&[
|
||||
methods::WRONG_SELF_CONVENTION,
|
||||
methods::ZST_OFFSET,
|
||||
minmax::MIN_MAX,
|
||||
misc::CMP_NAN,
|
||||
misc::CMP_OWNED,
|
||||
misc::FLOAT_CMP,
|
||||
misc::FLOAT_CMP_CONST,
|
||||
misc::MODULO_ONE,
|
||||
misc::SHORT_CIRCUIT_STATEMENT,
|
||||
misc::TOPLEVEL_REF_ARG,
|
||||
misc::USED_UNDERSCORE_BINDING,
|
||||
@ -392,7 +380,6 @@ store.register_lints(&[
|
||||
mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION,
|
||||
module_style::MOD_MODULE_FILES,
|
||||
module_style::SELF_NAMED_MODULE_FILES,
|
||||
modulo_arithmetic::MODULO_ARITHMETIC,
|
||||
mut_key::MUTABLE_KEY_TYPE,
|
||||
mut_mut::MUT_MUT,
|
||||
mut_mutex_lock::MUT_MUTEX_LOCK,
|
||||
@ -401,7 +388,6 @@ store.register_lints(&[
|
||||
mutex_atomic::MUTEX_ATOMIC,
|
||||
mutex_atomic::MUTEX_INTEGER,
|
||||
needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE,
|
||||
needless_bitwise_bool::NEEDLESS_BITWISE_BOOL,
|
||||
needless_bool::BOOL_COMPARISON,
|
||||
needless_bool::NEEDLESS_BOOL,
|
||||
needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE,
|
||||
@ -426,11 +412,34 @@ store.register_lints(&[
|
||||
non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS,
|
||||
non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY,
|
||||
nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES,
|
||||
numeric_arithmetic::FLOAT_ARITHMETIC,
|
||||
numeric_arithmetic::INTEGER_ARITHMETIC,
|
||||
octal_escapes::OCTAL_ESCAPES,
|
||||
only_used_in_recursion::ONLY_USED_IN_RECURSION,
|
||||
open_options::NONSENSICAL_OPEN_OPTIONS,
|
||||
operators::ABSURD_EXTREME_COMPARISONS,
|
||||
operators::ASSIGN_OP_PATTERN,
|
||||
operators::BAD_BIT_MASK,
|
||||
operators::CMP_NAN,
|
||||
operators::CMP_OWNED,
|
||||
operators::DOUBLE_COMPARISONS,
|
||||
operators::DURATION_SUBSEC,
|
||||
operators::EQ_OP,
|
||||
operators::ERASING_OP,
|
||||
operators::FLOAT_ARITHMETIC,
|
||||
operators::FLOAT_CMP,
|
||||
operators::FLOAT_CMP_CONST,
|
||||
operators::FLOAT_EQUALITY_WITHOUT_ABS,
|
||||
operators::IDENTITY_OP,
|
||||
operators::INEFFECTIVE_BIT_MASK,
|
||||
operators::INTEGER_ARITHMETIC,
|
||||
operators::INTEGER_DIVISION,
|
||||
operators::MISREFACTORED_ASSIGN_OP,
|
||||
operators::MODULO_ARITHMETIC,
|
||||
operators::MODULO_ONE,
|
||||
operators::NEEDLESS_BITWISE_BOOL,
|
||||
operators::OP_REF,
|
||||
operators::PTR_EQ,
|
||||
operators::SELF_ASSIGNMENT,
|
||||
operators::VERBOSE_BIT_MASK,
|
||||
option_env_unwrap::OPTION_ENV_UNWRAP,
|
||||
option_if_let_else::OPTION_IF_LET_ELSE,
|
||||
overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL,
|
||||
@ -449,7 +458,6 @@ store.register_lints(&[
|
||||
ptr::INVALID_NULL_PTR_USAGE,
|
||||
ptr::MUT_FROM_REF,
|
||||
ptr::PTR_ARG,
|
||||
ptr_eq::PTR_EQ,
|
||||
ptr_offset_with_cast::PTR_OFFSET_WITH_CAST,
|
||||
pub_use::PUB_USE,
|
||||
question_mark::QUESTION_MARK,
|
||||
@ -477,7 +485,6 @@ store.register_lints(&[
|
||||
returns::LET_AND_RETURN,
|
||||
returns::NEEDLESS_RETURN,
|
||||
same_name_method::SAME_NAME_METHOD,
|
||||
self_assignment::SELF_ASSIGNMENT,
|
||||
self_named_constructors::SELF_NAMED_CONSTRUCTORS,
|
||||
semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED,
|
||||
serde_api::SERDE_API_MISUSE,
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
|
||||
LintId::of(attrs::INLINE_ALWAYS),
|
||||
LintId::of(bit_mask::VERBOSE_BIT_MASK),
|
||||
LintId::of(borrow_as_ptr::BORROW_AS_PTR),
|
||||
LintId::of(bytecount::NAIVE_BYTECOUNT),
|
||||
LintId::of(case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS),
|
||||
@ -65,17 +64,18 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
|
||||
LintId::of(methods::INEFFICIENT_TO_STRING),
|
||||
LintId::of(methods::MAP_UNWRAP_OR),
|
||||
LintId::of(methods::UNNECESSARY_JOIN),
|
||||
LintId::of(misc::FLOAT_CMP),
|
||||
LintId::of(misc::USED_UNDERSCORE_BINDING),
|
||||
LintId::of(mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER),
|
||||
LintId::of(mut_mut::MUT_MUT),
|
||||
LintId::of(needless_bitwise_bool::NEEDLESS_BITWISE_BOOL),
|
||||
LintId::of(needless_continue::NEEDLESS_CONTINUE),
|
||||
LintId::of(needless_for_each::NEEDLESS_FOR_EACH),
|
||||
LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE),
|
||||
LintId::of(no_effect::NO_EFFECT_UNDERSCORE_BINDING),
|
||||
LintId::of(non_expressive_names::MANY_SINGLE_CHAR_NAMES),
|
||||
LintId::of(non_expressive_names::SIMILAR_NAMES),
|
||||
LintId::of(operators::FLOAT_CMP),
|
||||
LintId::of(operators::NEEDLESS_BITWISE_BOOL),
|
||||
LintId::of(operators::VERBOSE_BIT_MASK),
|
||||
LintId::of(pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE),
|
||||
LintId::of(pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF),
|
||||
LintId::of(ranges::RANGE_MINUS_ONE),
|
||||
|
@ -13,6 +13,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
|
||||
LintId::of(loops::MANUAL_MEMCPY),
|
||||
LintId::of(loops::MISSING_SPIN_LOOP),
|
||||
LintId::of(loops::NEEDLESS_COLLECT),
|
||||
LintId::of(manual_retain::MANUAL_RETAIN),
|
||||
LintId::of(methods::EXPECT_FUN_CALL),
|
||||
LintId::of(methods::EXTEND_WITH_DRAIN),
|
||||
LintId::of(methods::ITER_NTH),
|
||||
@ -21,7 +22,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
|
||||
LintId::of(methods::OR_FUN_CALL),
|
||||
LintId::of(methods::SINGLE_CHAR_PATTERN),
|
||||
LintId::of(methods::UNNECESSARY_TO_OWNED),
|
||||
LintId::of(misc::CMP_OWNED),
|
||||
LintId::of(operators::CMP_OWNED),
|
||||
LintId::of(redundant_clone::REDUNDANT_CLONE),
|
||||
LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
|
||||
LintId::of(types::BOX_COLLECTION),
|
||||
|
@ -25,7 +25,6 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
|
||||
LintId::of(implicit_return::IMPLICIT_RETURN),
|
||||
LintId::of(indexing_slicing::INDEXING_SLICING),
|
||||
LintId::of(inherent_impl::MULTIPLE_INHERENT_IMPL),
|
||||
LintId::of(integer_division::INTEGER_DIVISION),
|
||||
LintId::of(large_include_file::LARGE_INCLUDE_FILE),
|
||||
LintId::of(let_underscore::LET_UNDERSCORE_MUST_USE),
|
||||
LintId::of(literal_representation::DECIMAL_LITERAL_REPRESENTATION),
|
||||
@ -39,7 +38,6 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
|
||||
LintId::of(methods::FILETYPE_IS_FILE),
|
||||
LintId::of(methods::GET_UNWRAP),
|
||||
LintId::of(methods::UNWRAP_USED),
|
||||
LintId::of(misc::FLOAT_CMP_CONST),
|
||||
LintId::of(misc_early::SEPARATED_LITERAL_SUFFIX),
|
||||
LintId::of(misc_early::UNNEEDED_FIELD_PATTERN),
|
||||
LintId::of(misc_early::UNSEPARATED_LITERAL_SUFFIX),
|
||||
@ -49,9 +47,11 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
|
||||
LintId::of(mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION),
|
||||
LintId::of(module_style::MOD_MODULE_FILES),
|
||||
LintId::of(module_style::SELF_NAMED_MODULE_FILES),
|
||||
LintId::of(modulo_arithmetic::MODULO_ARITHMETIC),
|
||||
LintId::of(numeric_arithmetic::FLOAT_ARITHMETIC),
|
||||
LintId::of(numeric_arithmetic::INTEGER_ARITHMETIC),
|
||||
LintId::of(operators::FLOAT_ARITHMETIC),
|
||||
LintId::of(operators::FLOAT_CMP_CONST),
|
||||
LintId::of(operators::INTEGER_ARITHMETIC),
|
||||
LintId::of(operators::INTEGER_DIVISION),
|
||||
LintId::of(operators::MODULO_ARITHMETIC),
|
||||
LintId::of(panic_in_result_fn::PANIC_IN_RESULT_FN),
|
||||
LintId::of(panic_unimplemented::PANIC),
|
||||
LintId::of(panic_unimplemented::TODO),
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
store.register_group(true, "clippy::style", Some("clippy_style"), vec![
|
||||
LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
|
||||
LintId::of(assign_ops::ASSIGN_OP_PATTERN),
|
||||
LintId::of(blacklisted_name::BLACKLISTED_NAME),
|
||||
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
|
||||
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
|
||||
@ -14,6 +13,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
|
||||
LintId::of(collapsible_if::COLLAPSIBLE_IF),
|
||||
LintId::of(comparison_chain::COMPARISON_CHAIN),
|
||||
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
|
||||
LintId::of(default_instead_of_iter_empty::DEFAULT_INSTEAD_OF_ITER_EMPTY),
|
||||
LintId::of(dereference::NEEDLESS_BORROW),
|
||||
LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
|
||||
LintId::of(disallowed_methods::DISALLOWED_METHODS),
|
||||
@ -22,7 +22,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
|
||||
LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
|
||||
LintId::of(enum_variants::ENUM_VARIANT_NAMES),
|
||||
LintId::of(enum_variants::MODULE_INCEPTION),
|
||||
LintId::of(eq_op::OP_REF),
|
||||
LintId::of(eta_reduction::REDUNDANT_CLOSURE),
|
||||
LintId::of(float_literal::EXCESSIVE_PRECISION),
|
||||
LintId::of(from_over_into::FROM_OVER_INTO),
|
||||
@ -97,9 +96,11 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
|
||||
LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
|
||||
LintId::of(non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
|
||||
LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
|
||||
LintId::of(operators::ASSIGN_OP_PATTERN),
|
||||
LintId::of(operators::OP_REF),
|
||||
LintId::of(operators::PTR_EQ),
|
||||
LintId::of(ptr::CMP_NULL),
|
||||
LintId::of(ptr::PTR_ARG),
|
||||
LintId::of(ptr_eq::PTR_EQ),
|
||||
LintId::of(question_mark::QUESTION_MARK),
|
||||
LintId::of(ranges::MANUAL_RANGE_CONTAINS),
|
||||
LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES),
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec![
|
||||
LintId::of(almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE),
|
||||
LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP),
|
||||
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
|
||||
LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE),
|
||||
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
|
||||
@ -16,7 +15,6 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
|
||||
LintId::of(drop_forget_ref::DROP_NON_DROP),
|
||||
LintId::of(drop_forget_ref::FORGET_NON_DROP),
|
||||
LintId::of(duplicate_mod::DUPLICATE_MOD),
|
||||
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
|
||||
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
|
||||
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
|
||||
LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
|
||||
@ -29,6 +27,8 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
|
||||
LintId::of(methods::SUSPICIOUS_MAP),
|
||||
LintId::of(mut_key::MUTABLE_KEY_TYPE),
|
||||
LintId::of(octal_escapes::OCTAL_ESCAPES),
|
||||
LintId::of(operators::FLOAT_EQUALITY_WITHOUT_ABS),
|
||||
LintId::of(operators::MISREFACTORED_ASSIGN_OP),
|
||||
LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT),
|
||||
LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
|
||||
LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
|
||||
|
@ -7,6 +7,7 @@
|
||||
#![feature(let_chains)]
|
||||
#![feature(let_else)]
|
||||
#![feature(lint_reasons)]
|
||||
#![feature(never_type)]
|
||||
#![feature(once_cell)]
|
||||
#![feature(rustc_private)]
|
||||
#![feature(stmt_expr_attributes)]
|
||||
@ -52,6 +53,7 @@ extern crate clippy_utils;
|
||||
use clippy_utils::parse_msrv;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_lint::LintId;
|
||||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::Session;
|
||||
|
||||
/// Macro used to declare a Clippy lint.
|
||||
@ -159,25 +161,22 @@ macro_rules! declare_clippy_lint {
|
||||
}
|
||||
|
||||
#[cfg(feature = "internal")]
|
||||
mod deprecated_lints;
|
||||
pub mod deprecated_lints;
|
||||
#[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))]
|
||||
mod utils;
|
||||
|
||||
mod renamed_lints;
|
||||
|
||||
// begin lints modules, do not remove this comment, it’s used in `update_lints`
|
||||
mod absurd_extreme_comparisons;
|
||||
mod almost_complete_letter_range;
|
||||
mod approx_const;
|
||||
mod as_conversions;
|
||||
mod as_underscore;
|
||||
mod asm_syntax;
|
||||
mod assertions_on_constants;
|
||||
mod assign_ops;
|
||||
mod async_yields_async;
|
||||
mod attrs;
|
||||
mod await_holding_invalid;
|
||||
mod bit_mask;
|
||||
mod blacklisted_name;
|
||||
mod blocks_in_if_conditions;
|
||||
mod bool_assert_comparison;
|
||||
@ -199,6 +198,7 @@ mod crate_in_macro_def;
|
||||
mod create_dir;
|
||||
mod dbg_macro;
|
||||
mod default;
|
||||
mod default_instead_of_iter_empty;
|
||||
mod default_numeric_fallback;
|
||||
mod default_union_representation;
|
||||
mod dereference;
|
||||
@ -209,11 +209,9 @@ mod disallowed_script_idents;
|
||||
mod disallowed_types;
|
||||
mod doc;
|
||||
mod doc_link_with_quotes;
|
||||
mod double_comparison;
|
||||
mod double_parens;
|
||||
mod drop_forget_ref;
|
||||
mod duplicate_mod;
|
||||
mod duration_subsec;
|
||||
mod else_if_without_else;
|
||||
mod empty_drop;
|
||||
mod empty_enum;
|
||||
@ -221,9 +219,7 @@ mod empty_structs_with_brackets;
|
||||
mod entry;
|
||||
mod enum_clike;
|
||||
mod enum_variants;
|
||||
mod eq_op;
|
||||
mod equatable_if_let;
|
||||
mod erasing_op;
|
||||
mod escape;
|
||||
mod eta_reduction;
|
||||
mod excessive_bools;
|
||||
@ -231,7 +227,6 @@ mod exhaustive_items;
|
||||
mod exit;
|
||||
mod explicit_write;
|
||||
mod fallible_impl_from;
|
||||
mod float_equality_without_abs;
|
||||
mod float_literal;
|
||||
mod floating_point_arithmetic;
|
||||
mod format;
|
||||
@ -244,7 +239,6 @@ mod from_str_radix_10;
|
||||
mod functions;
|
||||
mod future_not_send;
|
||||
mod get_first;
|
||||
mod identity_op;
|
||||
mod if_let_mutex;
|
||||
mod if_not_else;
|
||||
mod if_then_some_else_none;
|
||||
@ -260,7 +254,6 @@ mod inherent_to_string;
|
||||
mod init_numbered_fields;
|
||||
mod inline_fn_without_body;
|
||||
mod int_plus_one;
|
||||
mod integer_division;
|
||||
mod invalid_upcast_comparisons;
|
||||
mod items_after_statements;
|
||||
mod iter_not_returning_iterator;
|
||||
@ -281,6 +274,8 @@ mod manual_async_fn;
|
||||
mod manual_bits;
|
||||
mod manual_non_exhaustive;
|
||||
mod manual_ok_or;
|
||||
mod manual_rem_euclid;
|
||||
mod manual_retain;
|
||||
mod manual_strip;
|
||||
mod map_clone;
|
||||
mod map_err_ignore;
|
||||
@ -300,7 +295,6 @@ mod missing_enforced_import_rename;
|
||||
mod missing_inline;
|
||||
mod mixed_read_write_in_expression;
|
||||
mod module_style;
|
||||
mod modulo_arithmetic;
|
||||
mod mut_key;
|
||||
mod mut_mut;
|
||||
mod mut_mutex_lock;
|
||||
@ -308,7 +302,6 @@ mod mut_reference;
|
||||
mod mutable_debug_assertion;
|
||||
mod mutex_atomic;
|
||||
mod needless_arbitrary_self_type;
|
||||
mod needless_bitwise_bool;
|
||||
mod needless_bool;
|
||||
mod needless_borrowed_ref;
|
||||
mod needless_continue;
|
||||
@ -327,10 +320,10 @@ mod non_expressive_names;
|
||||
mod non_octal_unix_permissions;
|
||||
mod non_send_fields_in_send_ty;
|
||||
mod nonstandard_macro_braces;
|
||||
mod numeric_arithmetic;
|
||||
mod octal_escapes;
|
||||
mod only_used_in_recursion;
|
||||
mod open_options;
|
||||
mod operators;
|
||||
mod option_env_unwrap;
|
||||
mod option_if_let_else;
|
||||
mod overflow_check_conditional;
|
||||
@ -342,7 +335,6 @@ mod path_buf_push_overwrite;
|
||||
mod pattern_type_mismatch;
|
||||
mod precedence;
|
||||
mod ptr;
|
||||
mod ptr_eq;
|
||||
mod ptr_offset_with_cast;
|
||||
mod pub_use;
|
||||
mod question_mark;
|
||||
@ -363,7 +355,6 @@ mod repeat_once;
|
||||
mod return_self_not_must_use;
|
||||
mod returns;
|
||||
mod same_name_method;
|
||||
mod self_assignment;
|
||||
mod self_named_constructors;
|
||||
mod semicolon_if_nothing_returned;
|
||||
mod serde_api;
|
||||
@ -448,6 +439,39 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Se
|
||||
store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv }));
|
||||
}
|
||||
|
||||
fn read_msrv(conf: &Conf, sess: &Session) -> Option<RustcVersion> {
|
||||
let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION")
|
||||
.ok()
|
||||
.and_then(|v| parse_msrv(&v, None, None));
|
||||
let clippy_msrv = conf.msrv.as_ref().and_then(|s| {
|
||||
parse_msrv(s, None, None).or_else(|| {
|
||||
sess.err(&format!(
|
||||
"error reading Clippy's configuration file. `{}` is not a valid Rust version",
|
||||
s
|
||||
));
|
||||
None
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(cargo_msrv) = cargo_msrv {
|
||||
if let Some(clippy_msrv) = clippy_msrv {
|
||||
// if both files have an msrv, let's compare them and emit a warning if they differ
|
||||
if clippy_msrv != cargo_msrv {
|
||||
sess.warn(&format!(
|
||||
"the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{}` from `clippy.toml`",
|
||||
clippy_msrv
|
||||
));
|
||||
}
|
||||
|
||||
Some(clippy_msrv)
|
||||
} else {
|
||||
Some(cargo_msrv)
|
||||
}
|
||||
} else {
|
||||
clippy_msrv
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn read_conf(sess: &Session) -> Conf {
|
||||
let file_name = match utils::conf::lookup_conf_file() {
|
||||
@ -463,12 +487,11 @@ pub fn read_conf(sess: &Session) -> Conf {
|
||||
let TryConf { conf, errors } = utils::conf::read(&file_name);
|
||||
// all conf errors are non-fatal, we just use the default conf in case of error
|
||||
for error in errors {
|
||||
sess.struct_err(&format!(
|
||||
sess.err(&format!(
|
||||
"error reading Clippy's configuration file `{}`: {}",
|
||||
file_name.display(),
|
||||
format_error(error)
|
||||
))
|
||||
.emit();
|
||||
));
|
||||
}
|
||||
|
||||
conf
|
||||
@ -543,21 +566,14 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
))
|
||||
});
|
||||
store.register_late_pass(|| Box::new(booleans::NonminimalBool));
|
||||
store.register_late_pass(|| Box::new(needless_bitwise_bool::NeedlessBitwiseBool));
|
||||
store.register_late_pass(|| Box::new(eq_op::EqOp));
|
||||
store.register_late_pass(|| Box::new(enum_clike::UnportableVariant));
|
||||
store.register_late_pass(|| Box::new(float_literal::FloatLiteral));
|
||||
let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
|
||||
store.register_late_pass(move || Box::new(bit_mask::BitMask::new(verbose_bit_mask_threshold)));
|
||||
store.register_late_pass(|| Box::new(ptr::Ptr));
|
||||
store.register_late_pass(|| Box::new(ptr_eq::PtrEq));
|
||||
store.register_late_pass(|| Box::new(needless_bool::NeedlessBool));
|
||||
store.register_late_pass(|| Box::new(needless_bool::BoolComparison));
|
||||
store.register_late_pass(|| Box::new(needless_for_each::NeedlessForEach));
|
||||
store.register_late_pass(|| Box::new(misc::MiscLints));
|
||||
store.register_late_pass(|| Box::new(eta_reduction::EtaReduction));
|
||||
store.register_late_pass(|| Box::new(identity_op::IdentityOp));
|
||||
store.register_late_pass(|| Box::new(erasing_op::ErasingOp));
|
||||
store.register_late_pass(|| Box::new(mut_mut::MutMut));
|
||||
store.register_late_pass(|| Box::new(mut_reference::UnnecessaryMutPassed));
|
||||
store.register_late_pass(|| Box::new(len_zero::LenZero));
|
||||
@ -575,16 +591,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
|
||||
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
|
||||
|
||||
let msrv = conf.msrv.as_ref().and_then(|s| {
|
||||
parse_msrv(s, None, None).or_else(|| {
|
||||
sess.err(&format!(
|
||||
"error reading Clippy's configuration file. `{}` is not a valid Rust version",
|
||||
s
|
||||
));
|
||||
None
|
||||
})
|
||||
});
|
||||
|
||||
let msrv = read_msrv(conf, sess);
|
||||
let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
|
||||
let allow_expect_in_tests = conf.allow_expect_in_tests;
|
||||
let allow_unwrap_in_tests = conf.allow_unwrap_in_tests;
|
||||
@ -639,7 +646,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|| Box::new(borrow_deref_ref::BorrowDerefRef));
|
||||
store.register_late_pass(|| Box::new(no_effect::NoEffect));
|
||||
store.register_late_pass(|| Box::new(temporary_assignment::TemporaryAssignment));
|
||||
store.register_late_pass(|| Box::new(transmute::Transmute));
|
||||
store.register_late_pass(move || Box::new(transmute::Transmute::new(msrv)));
|
||||
let cognitive_complexity_threshold = conf.cognitive_complexity_threshold;
|
||||
store.register_late_pass(move || {
|
||||
Box::new(cognitive_complexity::CognitiveComplexity::new(
|
||||
@ -655,7 +662,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|| Box::new(derivable_impls::DerivableImpls));
|
||||
store.register_late_pass(|| Box::new(drop_forget_ref::DropForgetRef));
|
||||
store.register_late_pass(|| Box::new(empty_enum::EmptyEnum));
|
||||
store.register_late_pass(|| Box::new(absurd_extreme_comparisons::AbsurdExtremeComparisons));
|
||||
store.register_late_pass(|| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
|
||||
store.register_late_pass(|| Box::new(regex::Regex));
|
||||
store.register_late_pass(|| Box::new(copies::CopyAndPaste));
|
||||
@ -678,8 +684,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(move || Box::new(doc::DocMarkdown::new(doc_valid_idents.clone())));
|
||||
store.register_late_pass(|| Box::new(neg_multiply::NegMultiply));
|
||||
store.register_late_pass(|| Box::new(mem_forget::MemForget));
|
||||
store.register_late_pass(|| Box::new(numeric_arithmetic::NumericArithmetic::default()));
|
||||
store.register_late_pass(|| Box::new(assign_ops::AssignOps));
|
||||
store.register_late_pass(|| Box::new(let_if_seq::LetIfSeq));
|
||||
store.register_late_pass(|| Box::new(mixed_read_write_in_expression::EvalOrderDependence));
|
||||
store.register_late_pass(|| Box::new(missing_doc::MissingDoc::new()));
|
||||
@ -706,7 +710,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|| Box::new(useless_conversion::UselessConversion::default()));
|
||||
store.register_late_pass(|| Box::new(implicit_hasher::ImplicitHasher));
|
||||
store.register_late_pass(|| Box::new(fallible_impl_from::FallibleImplFrom));
|
||||
store.register_late_pass(|| Box::new(double_comparison::DoubleComparisons));
|
||||
store.register_late_pass(|| Box::new(question_mark::QuestionMark));
|
||||
store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings));
|
||||
store.register_late_pass(|| Box::new(suspicious_trait_impl::SuspiciousImpl));
|
||||
@ -714,7 +717,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|| Box::new(inherent_impl::MultipleInherentImpl));
|
||||
store.register_late_pass(|| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd));
|
||||
store.register_late_pass(|| Box::new(unwrap::Unwrap));
|
||||
store.register_late_pass(|| Box::new(duration_subsec::DurationSubsec));
|
||||
store.register_late_pass(|| Box::new(indexing_slicing::IndexingSlicing));
|
||||
store.register_late_pass(|| Box::new(non_copy_const::NonCopyConst));
|
||||
store.register_late_pass(|| Box::new(ptr_offset_with_cast::PtrOffsetWithCast));
|
||||
@ -725,13 +727,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|| Box::new(assertions_on_constants::AssertionsOnConstants));
|
||||
store.register_late_pass(|| Box::new(transmuting_null::TransmutingNull));
|
||||
store.register_late_pass(|| Box::new(path_buf_push_overwrite::PathBufPushOverwrite));
|
||||
store.register_late_pass(|| Box::new(integer_division::IntegerDivision));
|
||||
store.register_late_pass(|| Box::new(inherent_to_string::InherentToString));
|
||||
let max_trait_bounds = conf.max_trait_bounds;
|
||||
store.register_late_pass(move || Box::new(trait_bounds::TraitBounds::new(max_trait_bounds)));
|
||||
store.register_late_pass(|| Box::new(comparison_chain::ComparisonChain));
|
||||
store.register_late_pass(|| Box::new(mut_key::MutableKeyType));
|
||||
store.register_late_pass(|| Box::new(modulo_arithmetic::ModuloArithmetic));
|
||||
store.register_early_pass(|| Box::new(reference::DerefAddrOf));
|
||||
store.register_early_pass(|| Box::new(double_parens::DoubleParens));
|
||||
store.register_late_pass(|| Box::new(format_impl::FormatImpl::new()));
|
||||
@ -828,9 +828,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|| Box::new(stable_sort_primitive::StableSortPrimitive));
|
||||
store.register_late_pass(|| Box::new(repeat_once::RepeatOnce));
|
||||
store.register_late_pass(|| Box::new(unwrap_in_result::UnwrapInResult));
|
||||
store.register_late_pass(|| Box::new(self_assignment::SelfAssignment));
|
||||
store.register_late_pass(|| Box::new(manual_ok_or::ManualOkOr));
|
||||
store.register_late_pass(|| Box::new(float_equality_without_abs::FloatEqualityWithoutAbs));
|
||||
store.register_late_pass(|| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
|
||||
store.register_late_pass(|| Box::new(async_yields_async::AsyncYieldsAsync));
|
||||
let disallowed_methods = conf.disallowed_methods.clone();
|
||||
@ -910,6 +908,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|| Box::new(mismatching_type_param_order::TypeParamMismatch));
|
||||
store.register_late_pass(|| Box::new(as_underscore::AsUnderscore));
|
||||
store.register_late_pass(|| Box::new(read_zero_byte_vec::ReadZeroByteVec));
|
||||
store.register_late_pass(|| Box::new(default_instead_of_iter_empty::DefaultIterEmpty));
|
||||
store.register_late_pass(move || Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv)));
|
||||
store.register_late_pass(move || Box::new(manual_retain::ManualRetain::new(msrv)));
|
||||
let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
|
||||
store.register_late_pass(move || Box::new(operators::Operators::new(verbose_bit_mask_threshold)));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,9 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
|
||||
if let ItemKind::Fn(ref sig, generics, id) = item.kind {
|
||||
check_fn_inner(cx, sig.decl, Some(id), None, generics, item.span, true);
|
||||
} else if let ItemKind::Impl(impl_) = item.kind {
|
||||
report_extra_impl_lifetimes(cx, impl_);
|
||||
if !item.span.from_expansion() {
|
||||
report_extra_impl_lifetimes(cx, impl_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
158
clippy_lints/src/loops/manual_find.rs
Normal file
158
clippy_lints/src/loops/manual_find.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ mod explicit_iter_loop;
|
||||
mod for_kv_map;
|
||||
mod for_loops_over_fallibles;
|
||||
mod iter_next_loop;
|
||||
mod manual_find;
|
||||
mod manual_flatten;
|
||||
mod manual_memcpy;
|
||||
mod missing_spin_loop;
|
||||
@ -346,7 +347,14 @@ declare_clippy_lint! {
|
||||
///
|
||||
/// ### Example
|
||||
/// ```ignore
|
||||
/// while let Some(val) = iter() {
|
||||
/// while let Some(val) = iter.next() {
|
||||
/// ..
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```ignore
|
||||
/// for val in &mut iter {
|
||||
/// ..
|
||||
/// }
|
||||
/// ```
|
||||
@ -602,6 +610,37 @@ declare_clippy_lint! {
|
||||
"An empty busy waiting loop"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Check for manual implementations of Iterator::find
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It doesn't affect performance, but using `find` is shorter and easier to read.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```rust
|
||||
/// fn example(arr: Vec<i32>) -> Option<i32> {
|
||||
/// for el in arr {
|
||||
/// if el == 1 {
|
||||
/// return Some(el);
|
||||
/// }
|
||||
/// }
|
||||
/// None
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// fn example(arr: Vec<i32>) -> Option<i32> {
|
||||
/// arr.into_iter().find(|&el| el == 1)
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub MANUAL_FIND,
|
||||
complexity,
|
||||
"manual implementation of `Iterator::find`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(Loops => [
|
||||
MANUAL_MEMCPY,
|
||||
MANUAL_FLATTEN,
|
||||
@ -622,6 +661,7 @@ declare_lint_pass!(Loops => [
|
||||
SAME_ITEM_PUSH,
|
||||
SINGLE_ELEMENT_LOOP,
|
||||
MISSING_SPIN_LOOP,
|
||||
MANUAL_FIND,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Loops {
|
||||
@ -696,6 +736,7 @@ fn check_for_loop<'tcx>(
|
||||
single_element_loop::check(cx, pat, arg, body, expr);
|
||||
same_item_push::check(cx, pat, arg, body, expr);
|
||||
manual_flatten::check(cx, pat, arg, body, span);
|
||||
manual_find::check(cx, pat, arg, body, span, expr);
|
||||
}
|
||||
|
||||
fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) {
|
||||
|
@ -2,71 +2,60 @@ use super::WHILE_LET_LOOP;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::higher;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::needs_ordered_drop;
|
||||
use clippy_utils::visitors::any_temporaries_need_ordered_drop;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Block, Expr, ExprKind, MatchSource, Pat, StmtKind};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_hir::{Block, Expr, ExprKind, Local, MatchSource, Pat, StmtKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) {
|
||||
// extract the expression from the first statement (if any) in a block
|
||||
let inner_stmt_expr = extract_expr_from_first_stmt(loop_block);
|
||||
// or extract the first expression (if any) from the block
|
||||
if let Some(inner) = inner_stmt_expr.or_else(|| extract_first_expr(loop_block)) {
|
||||
if let Some(higher::IfLet {
|
||||
let_pat,
|
||||
let_expr,
|
||||
if_else: Some(if_else),
|
||||
..
|
||||
}) = higher::IfLet::hir(cx, inner)
|
||||
{
|
||||
if is_simple_break_expr(if_else) {
|
||||
could_be_while_let(cx, expr, let_pat, let_expr);
|
||||
let (init, has_trailing_exprs) = match (loop_block.stmts, loop_block.expr) {
|
||||
([stmt, stmts @ ..], expr) => {
|
||||
if let StmtKind::Local(&Local { init: Some(e), .. }) | StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind {
|
||||
(e, !stmts.is_empty() || expr.is_some())
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let ExprKind::Match(matchexpr, arms, MatchSource::Normal) = inner.kind {
|
||||
if arms.len() == 2
|
||||
&& arms[0].guard.is_none()
|
||||
&& arms[1].guard.is_none()
|
||||
&& is_simple_break_expr(arms[1].body)
|
||||
{
|
||||
could_be_while_let(cx, expr, arms[0].pat, matchexpr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If a block begins with a statement (possibly a `let` binding) and has an
|
||||
/// expression, return it.
|
||||
fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
if let Some(first_stmt) = block.stmts.get(0) {
|
||||
if let StmtKind::Local(local) = first_stmt.kind {
|
||||
return local.init;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// If a block begins with an expression (with or without semicolon), return it.
|
||||
fn extract_first_expr<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
match block.expr {
|
||||
Some(expr) if block.stmts.is_empty() => Some(expr),
|
||||
None if !block.stmts.is_empty() => match block.stmts[0].kind {
|
||||
StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some(expr),
|
||||
StmtKind::Local(..) | StmtKind::Item(..) => None,
|
||||
},
|
||||
_ => None,
|
||||
([], Some(e)) => (e, false),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if let Some(if_let) = higher::IfLet::hir(cx, init)
|
||||
&& let Some(else_expr) = if_let.if_else
|
||||
&& is_simple_break_expr(else_expr)
|
||||
{
|
||||
could_be_while_let(cx, expr, if_let.let_pat, if_let.let_expr, has_trailing_exprs);
|
||||
} else if let ExprKind::Match(scrutinee, [arm1, arm2], MatchSource::Normal) = init.kind
|
||||
&& arm1.guard.is_none()
|
||||
&& arm2.guard.is_none()
|
||||
&& is_simple_break_expr(arm2.body)
|
||||
{
|
||||
could_be_while_let(cx, expr, arm1.pat, scrutinee, has_trailing_exprs);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if expr contains a single break expr without destination label
|
||||
/// and
|
||||
/// passed expression. The expression may be within a block.
|
||||
fn is_simple_break_expr(expr: &Expr<'_>) -> bool {
|
||||
match expr.kind {
|
||||
ExprKind::Break(dest, ref passed_expr) if dest.label.is_none() && passed_expr.is_none() => true,
|
||||
ExprKind::Block(b, _) => extract_first_expr(b).map_or(false, is_simple_break_expr),
|
||||
_ => false,
|
||||
/// Returns `true` if expr contains a single break expression without a label or eub-expression.
|
||||
fn is_simple_break_expr(e: &Expr<'_>) -> bool {
|
||||
matches!(peel_blocks(e).kind, ExprKind::Break(dest, None) if dest.label.is_none())
|
||||
}
|
||||
|
||||
/// Removes any blocks containing only a single expression.
|
||||
fn peel_blocks<'tcx>(e: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
|
||||
if let ExprKind::Block(b, _) = e.kind {
|
||||
match (b.stmts, b.expr) {
|
||||
([s], None) => {
|
||||
if let StmtKind::Expr(e) | StmtKind::Semi(e) = s.kind {
|
||||
peel_blocks(e)
|
||||
} else {
|
||||
e
|
||||
}
|
||||
},
|
||||
([], Some(e)) => peel_blocks(e),
|
||||
_ => e,
|
||||
}
|
||||
} else {
|
||||
e
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,8 +64,13 @@ fn could_be_while_let<'tcx>(
|
||||
expr: &'tcx Expr<'_>,
|
||||
let_pat: &'tcx Pat<'_>,
|
||||
let_expr: &'tcx Expr<'_>,
|
||||
has_trailing_exprs: bool,
|
||||
) {
|
||||
if in_external_macro(cx.sess(), expr.span) {
|
||||
if has_trailing_exprs
|
||||
&& (needs_ordered_drop(cx, cx.typeck_results().expr_ty(let_expr))
|
||||
|| any_temporaries_need_ordered_drop(cx, let_expr))
|
||||
{
|
||||
// Switching to a `while let` loop will extend the lifetime of some values.
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::source::snippet;
|
||||
use hir::def::{DefKind, Res};
|
||||
use if_chain::if_chain;
|
||||
@ -50,8 +50,9 @@ impl MacroRefData {
|
||||
#[derive(Default)]
|
||||
#[expect(clippy::module_name_repetitions)]
|
||||
pub struct MacroUseImports {
|
||||
/// the actual import path used and the span of the attribute above it.
|
||||
imports: Vec<(String, Span)>,
|
||||
/// the actual import path used and the span of the attribute above it. The value is
|
||||
/// the location, where the lint should be emitted.
|
||||
imports: Vec<(String, Span, hir::HirId)>,
|
||||
/// the span of the macro reference, kept to ensure only one reference is used per macro call.
|
||||
collected: FxHashSet<Span>,
|
||||
mac_refs: Vec<MacroRefData>,
|
||||
@ -90,7 +91,8 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
|
||||
if_chain! {
|
||||
if cx.sess().opts.edition >= Edition::Edition2018;
|
||||
if let hir::ItemKind::Use(path, _kind) = &item.kind;
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let hir_id = item.hir_id();
|
||||
let attrs = cx.tcx.hir().attrs(hir_id);
|
||||
if let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use));
|
||||
if let Res::Def(DefKind::Mod, id) = path.res;
|
||||
if !id.is_local();
|
||||
@ -99,7 +101,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
|
||||
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
|
||||
let span = mac_attr.span;
|
||||
let def_path = cx.tcx.def_path_str(mac_id);
|
||||
self.imports.push((def_path, span));
|
||||
self.imports.push((def_path, span, hir_id));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -137,7 +139,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
|
||||
fn check_crate_post(&mut self, cx: &LateContext<'_>) {
|
||||
let mut used = FxHashMap::default();
|
||||
let mut check_dup = vec![];
|
||||
for (import, span) in &self.imports {
|
||||
for (import, span, hir_id) in &self.imports {
|
||||
let found_idx = self.mac_refs.iter().position(|mac| import.ends_with(&mac.name));
|
||||
|
||||
if let Some(idx) = found_idx {
|
||||
@ -150,7 +152,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
|
||||
[] | [_] => return,
|
||||
[root, item] => {
|
||||
if !check_dup.contains(&(*item).to_string()) {
|
||||
used.entry(((*root).to_string(), span))
|
||||
used.entry(((*root).to_string(), span, hir_id))
|
||||
.or_insert_with(Vec::new)
|
||||
.push((*item).to_string());
|
||||
check_dup.push((*item).to_string());
|
||||
@ -168,13 +170,13 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
used.entry(((*root).to_string(), span))
|
||||
used.entry(((*root).to_string(), span, hir_id))
|
||||
.or_insert_with(Vec::new)
|
||||
.push(filtered.join("::"));
|
||||
check_dup.extend(filtered);
|
||||
} else {
|
||||
let rest = rest.to_vec();
|
||||
used.entry(((*root).to_string(), span))
|
||||
used.entry(((*root).to_string(), span, hir_id))
|
||||
.or_insert_with(Vec::new)
|
||||
.push(rest.join("::"));
|
||||
check_dup.extend(rest.iter().map(ToString::to_string));
|
||||
@ -185,27 +187,33 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
|
||||
}
|
||||
|
||||
let mut suggestions = vec![];
|
||||
for ((root, span), path) in used {
|
||||
for ((root, span, hir_id), path) in used {
|
||||
if path.len() == 1 {
|
||||
suggestions.push((span, format!("{}::{}", root, path[0])));
|
||||
suggestions.push((span, format!("{}::{}", root, path[0]), hir_id));
|
||||
} else {
|
||||
suggestions.push((span, format!("{}::{{{}}}", root, path.join(", "))));
|
||||
suggestions.push((span, format!("{}::{{{}}}", root, path.join(", ")), hir_id));
|
||||
}
|
||||
}
|
||||
|
||||
// If mac_refs is not empty we have encountered an import we could not handle
|
||||
// such as `std::prelude::v1::foo` or some other macro that expands to an import.
|
||||
if self.mac_refs.is_empty() {
|
||||
for (span, import) in suggestions {
|
||||
for (span, import, hir_id) in suggestions {
|
||||
let help = format!("use {};", import);
|
||||
span_lint_and_sugg(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
MACRO_USE_IMPORTS,
|
||||
*hir_id,
|
||||
*span,
|
||||
"`macro_use` attributes are no longer needed in the Rust 2018 edition",
|
||||
"remove the attribute and import the macro directly, try",
|
||||
help,
|
||||
Applicability::MaybeIncorrect,
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
*span,
|
||||
"remove the attribute and import the macro directly, try",
|
||||
help,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::{is_doc_hidden, is_lint_allowed, meets_msrv, msrvs};
|
||||
use clippy_utils::{is_doc_hidden, meets_msrv, msrvs};
|
||||
use rustc_ast::ast::{self, VisibilityKind};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
@ -190,12 +190,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
|
||||
!self
|
||||
.constructed_enum_variants
|
||||
.contains(&(enum_id.to_def_id(), variant_id.to_def_id()))
|
||||
&& !is_lint_allowed(cx, MANUAL_NON_EXHAUSTIVE, cx.tcx.hir().local_def_id_to_hir_id(enum_id))
|
||||
})
|
||||
{
|
||||
span_lint_and_then(
|
||||
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(enum_id);
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
MANUAL_NON_EXHAUSTIVE,
|
||||
hir_id,
|
||||
enum_span,
|
||||
"this seems like a manual implementation of the non-exhaustive pattern",
|
||||
|diag| {
|
||||
|
123
clippy_lints/src/manual_rem_euclid.rs
Normal file
123
clippy_lints/src/manual_rem_euclid.rs
Normal 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),
|
||||
}
|
||||
}
|
228
clippy_lints/src/manual_retain.rs
Normal file
228
clippy_lints/src/manual_retain.rs
Normal 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))
|
||||
})
|
||||
}
|
@ -285,7 +285,7 @@ impl<'a> NormalizedPat<'a> {
|
||||
// TODO: Handle negative integers. They're currently treated as a wild match.
|
||||
ExprKind::Lit(lit) => match lit.node {
|
||||
LitKind::Str(sym, _) => Self::LitStr(sym),
|
||||
LitKind::ByteStr(ref bytes) => Self::LitBytes(&**bytes),
|
||||
LitKind::ByteStr(ref bytes) => Self::LitBytes(bytes),
|
||||
LitKind::Byte(val) => Self::LitInt(val.into()),
|
||||
LitKind::Char(val) => Self::LitInt(val.into()),
|
||||
LitKind::Int(val, _) => Self::LitInt(val),
|
||||
|
@ -55,7 +55,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
|
||||
cx,
|
||||
(ex, expr),
|
||||
(bind_names, matched_vars),
|
||||
&*snippet_body,
|
||||
&snippet_body,
|
||||
&mut applicability,
|
||||
Some(span),
|
||||
);
|
||||
@ -88,7 +88,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
|
||||
cx,
|
||||
(ex, expr),
|
||||
(bind_names, matched_vars),
|
||||
&*snippet_body,
|
||||
&snippet_body,
|
||||
&mut applicability,
|
||||
None,
|
||||
);
|
||||
|
@ -118,7 +118,7 @@ fn lint(cx: &LateContext<'_>, case_method: &CaseMethod, bad_case_span: Span, bad
|
||||
MATCH_STR_CASE_MISMATCH,
|
||||
bad_case_span,
|
||||
"this `match` arm has a differing case than its expression",
|
||||
&*format!("consider changing the case of this arm to respect `{}`", method_str),
|
||||
&format!("consider changing the case of this arm to respect `{}`", method_str),
|
||||
format!("\"{}\"", suggestion),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
@ -791,7 +791,7 @@ declare_clippy_lint! {
|
||||
/// the match block and thus will not unlock.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust.ignore
|
||||
/// ```rust,ignore
|
||||
/// # use std::sync::Mutex;
|
||||
///
|
||||
/// # struct State {}
|
||||
@ -963,7 +963,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
|
||||
return;
|
||||
}
|
||||
if matches!(source, MatchSource::Normal | MatchSource::ForLoopDesugar) {
|
||||
significant_drop_in_scrutinee::check(cx, expr, ex, source);
|
||||
significant_drop_in_scrutinee::check(cx, expr, ex, arms, source);
|
||||
}
|
||||
|
||||
collapsible_match::check_match(cx, arms);
|
||||
|
@ -3,16 +3,13 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::needs_ordered_drop;
|
||||
use clippy_utils::{higher, match_def_path};
|
||||
use clippy_utils::{is_lang_ctor, is_trait_method, paths};
|
||||
use clippy_utils::visitors::any_temporaries_need_ordered_drop;
|
||||
use clippy_utils::{higher, is_lang_ctor, is_trait_method, match_def_path, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::{OptionNone, PollPending};
|
||||
use rustc_hir::{
|
||||
intravisit::{walk_expr, Visitor},
|
||||
Arm, Block, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp,
|
||||
};
|
||||
use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty};
|
||||
use rustc_span::sym;
|
||||
@ -47,79 +44,6 @@ fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if there are any temporaries created in the given expression for which drop order
|
||||
// matters.
|
||||
fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
|
||||
struct V<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
res: bool,
|
||||
}
|
||||
impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
|
||||
match expr.kind {
|
||||
// Taking the reference of a value leaves a temporary
|
||||
// e.g. In `&String::new()` the string is a temporary value.
|
||||
// Remaining fields are temporary values
|
||||
// e.g. In `(String::new(), 0).1` the string is a temporary value.
|
||||
ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => {
|
||||
if !matches!(expr.kind, ExprKind::Path(_)) {
|
||||
if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) {
|
||||
self.res = true;
|
||||
} else {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
}
|
||||
},
|
||||
// the base type is always taken by reference.
|
||||
// e.g. In `(vec![0])[0]` the vector is a temporary value.
|
||||
ExprKind::Index(base, index) => {
|
||||
if !matches!(base.kind, ExprKind::Path(_)) {
|
||||
if needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) {
|
||||
self.res = true;
|
||||
} else {
|
||||
self.visit_expr(base);
|
||||
}
|
||||
}
|
||||
self.visit_expr(index);
|
||||
},
|
||||
// Method calls can take self by reference.
|
||||
// e.g. In `String::new().len()` the string is a temporary value.
|
||||
ExprKind::MethodCall(_, [self_arg, args @ ..], _) => {
|
||||
if !matches!(self_arg.kind, ExprKind::Path(_)) {
|
||||
let self_by_ref = self
|
||||
.cx
|
||||
.typeck_results()
|
||||
.type_dependent_def_id(expr.hir_id)
|
||||
.map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
|
||||
if self_by_ref && needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg)) {
|
||||
self.res = true;
|
||||
} else {
|
||||
self.visit_expr(self_arg);
|
||||
}
|
||||
}
|
||||
args.iter().for_each(|arg| self.visit_expr(arg));
|
||||
},
|
||||
// Either explicitly drops values, or changes control flow.
|
||||
ExprKind::DropTemps(_)
|
||||
| ExprKind::Ret(_)
|
||||
| ExprKind::Break(..)
|
||||
| ExprKind::Yield(..)
|
||||
| ExprKind::Block(Block { expr: None, .. }, _)
|
||||
| ExprKind::Loop(..) => (),
|
||||
|
||||
// Only consider the final expression.
|
||||
ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr),
|
||||
|
||||
_ => walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut v = V { cx, res: false };
|
||||
v.visit_expr(expr);
|
||||
v.res
|
||||
}
|
||||
|
||||
fn find_sugg_for_if_let<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
@ -191,7 +115,7 @@ fn find_sugg_for_if_let<'tcx>(
|
||||
// scrutinee would be, so they have to be considered as well.
|
||||
// e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
|
||||
// for the duration if body.
|
||||
let needs_drop = needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr);
|
||||
let needs_drop = needs_ordered_drop(cx, check_ty) || any_temporaries_need_ordered_drop(cx, let_expr);
|
||||
|
||||
// check that `while_let_on_iterator` lint does not trigger
|
||||
if_chain! {
|
||||
@ -362,9 +286,9 @@ fn find_good_method_for_match<'a>(
|
||||
.qpath_res(path_right, arms[1].pat.hir_id)
|
||||
.opt_def_id()?;
|
||||
let body_node_pair = if match_def_path(cx, left_id, expected_left) && match_def_path(cx, right_id, expected_right) {
|
||||
(&(*arms[0].body).kind, &(*arms[1].body).kind)
|
||||
(&arms[0].body.kind, &arms[1].body.kind)
|
||||
} else if match_def_path(cx, right_id, expected_left) && match_def_path(cx, right_id, expected_right) {
|
||||
(&(*arms[1].body).kind, &(*arms[0].body).kind)
|
||||
(&arms[1].body.kind, &arms[0].body.kind)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::FxHashSet;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::get_attr;
|
||||
use clippy_utils::source::{indent_of, snippet};
|
||||
use clippy_utils::{get_attr, is_lint_allowed};
|
||||
use rustc_errors::{Applicability, Diagnostic};
|
||||
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||
use rustc_hir::{Expr, ExprKind, MatchSource};
|
||||
use rustc_hir::{Arm, Expr, ExprKind, MatchSource};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::ty::subst::GenericArgKind;
|
||||
use rustc_middle::ty::{Ty, TypeAndMut};
|
||||
@ -16,12 +16,23 @@ pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
scrutinee: &'tcx Expr<'_>,
|
||||
arms: &'tcx [Arm<'_>],
|
||||
source: MatchSource,
|
||||
) {
|
||||
if is_lint_allowed(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, expr.hir_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((suggestions, message)) = has_significant_drop_in_scrutinee(cx, scrutinee, source) {
|
||||
for found in suggestions {
|
||||
span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| {
|
||||
set_diagnostic(diag, cx, expr, found);
|
||||
let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None);
|
||||
diag.span_label(s, "temporary lives until here");
|
||||
for span in has_significant_drop_in_arms(cx, arms) {
|
||||
diag.span_label(span, "another value with significant `Drop` created here");
|
||||
}
|
||||
diag.note("this might lead to deadlocks or other unexpected behavior");
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -80,22 +91,77 @@ fn has_significant_drop_in_scrutinee<'tcx, 'a>(
|
||||
let mut helper = SigDropHelper::new(cx);
|
||||
helper.find_sig_drop(scrutinee).map(|drops| {
|
||||
let message = if source == MatchSource::Normal {
|
||||
"temporary with significant drop in match scrutinee"
|
||||
"temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression"
|
||||
} else {
|
||||
"temporary with significant drop in for loop"
|
||||
"temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression"
|
||||
};
|
||||
(drops, message)
|
||||
})
|
||||
}
|
||||
|
||||
struct SigDropChecker<'a, 'tcx> {
|
||||
seen_types: FxHashSet<Ty<'tcx>>,
|
||||
cx: &'a LateContext<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
|
||||
fn new(cx: &'a LateContext<'tcx>) -> SigDropChecker<'a, 'tcx> {
|
||||
SigDropChecker {
|
||||
seen_types: FxHashSet::default(),
|
||||
cx,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> {
|
||||
self.cx.typeck_results().expr_ty(ex)
|
||||
}
|
||||
|
||||
fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool {
|
||||
!self.seen_types.insert(ty)
|
||||
}
|
||||
|
||||
fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
if let Some(adt) = ty.ty_adt_def() {
|
||||
if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
match ty.kind() {
|
||||
rustc_middle::ty::Adt(a, b) => {
|
||||
for f in a.all_fields() {
|
||||
let ty = f.ty(cx.tcx, b);
|
||||
if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for generic_arg in b.iter() {
|
||||
if let GenericArgKind::Type(ty) = generic_arg.unpack() {
|
||||
if self.has_sig_drop_attr(cx, ty) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
},
|
||||
rustc_middle::ty::Array(ty, _)
|
||||
| rustc_middle::ty::RawPtr(TypeAndMut { ty, .. })
|
||||
| rustc_middle::ty::Ref(_, ty, _)
|
||||
| rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SigDropHelper<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
is_chain_end: bool,
|
||||
seen_types: FxHashSet<Ty<'tcx>>,
|
||||
has_significant_drop: bool,
|
||||
current_sig_drop: Option<FoundSigDrop>,
|
||||
sig_drop_spans: Option<Vec<FoundSigDrop>>,
|
||||
special_handling_for_binary_op: bool,
|
||||
sig_drop_checker: SigDropChecker<'a, 'tcx>,
|
||||
}
|
||||
|
||||
#[expect(clippy::enum_variant_names)]
|
||||
@ -118,11 +184,11 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
|
||||
SigDropHelper {
|
||||
cx,
|
||||
is_chain_end: true,
|
||||
seen_types: FxHashSet::default(),
|
||||
has_significant_drop: false,
|
||||
current_sig_drop: None,
|
||||
sig_drop_spans: None,
|
||||
special_handling_for_binary_op: false,
|
||||
sig_drop_checker: SigDropChecker::new(cx),
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,7 +229,7 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
|
||||
if self.current_sig_drop.is_some() {
|
||||
return;
|
||||
}
|
||||
let ty = self.get_type(expr);
|
||||
let ty = self.sig_drop_checker.get_type(expr);
|
||||
if ty.is_ref() {
|
||||
// We checked that the type was ref, so builtin_deref will return Some TypeAndMut,
|
||||
// but let's avoid any chance of an ICE
|
||||
@ -187,14 +253,6 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> {
|
||||
self.cx.typeck_results().expr_ty(ex)
|
||||
}
|
||||
|
||||
fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool {
|
||||
!self.seen_types.insert(ty)
|
||||
}
|
||||
|
||||
fn visit_exprs_for_binary_ops(
|
||||
&mut self,
|
||||
left: &'tcx Expr<'_>,
|
||||
@ -214,44 +272,15 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
|
||||
|
||||
self.special_handling_for_binary_op = false;
|
||||
}
|
||||
|
||||
fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
if let Some(adt) = ty.ty_adt_def() {
|
||||
if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
match ty.kind() {
|
||||
rustc_middle::ty::Adt(a, b) => {
|
||||
for f in a.all_fields() {
|
||||
let ty = f.ty(cx.tcx, b);
|
||||
if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for generic_arg in b.iter() {
|
||||
if let GenericArgKind::Type(ty) = generic_arg.unpack() {
|
||||
if self.has_sig_drop_attr(cx, ty) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
},
|
||||
rustc_middle::ty::Array(ty, _)
|
||||
| rustc_middle::ty::RawPtr(TypeAndMut { ty, .. })
|
||||
| rustc_middle::ty::Ref(_, ty, _)
|
||||
| rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
|
||||
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
|
||||
if !self.is_chain_end && self.has_sig_drop_attr(self.cx, self.get_type(ex)) {
|
||||
if !self.is_chain_end
|
||||
&& self
|
||||
.sig_drop_checker
|
||||
.has_sig_drop_attr(self.cx, self.sig_drop_checker.get_type(ex))
|
||||
{
|
||||
self.has_significant_drop = true;
|
||||
return;
|
||||
}
|
||||
@ -330,3 +359,38 @@ impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ArmSigDropHelper<'a, 'tcx> {
|
||||
sig_drop_checker: SigDropChecker<'a, 'tcx>,
|
||||
found_sig_drop_spans: FxHashSet<Span>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> {
|
||||
fn new(cx: &'a LateContext<'tcx>) -> ArmSigDropHelper<'a, 'tcx> {
|
||||
ArmSigDropHelper {
|
||||
sig_drop_checker: SigDropChecker::new(cx),
|
||||
found_sig_drop_spans: FxHashSet::<Span>::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_significant_drop_in_arms<'tcx, 'a>(cx: &'a LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> FxHashSet<Span> {
|
||||
let mut helper = ArmSigDropHelper::new(cx);
|
||||
for arm in arms {
|
||||
helper.visit_expr(arm.body);
|
||||
}
|
||||
helper.found_sig_drop_spans
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for ArmSigDropHelper<'a, 'tcx> {
|
||||
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
|
||||
if self
|
||||
.sig_drop_checker
|
||||
.has_sig_drop_attr(self.sig_drop_checker.cx, self.sig_drop_checker.get_type(ex))
|
||||
{
|
||||
self.found_sig_drop_spans.insert(ex.span);
|
||||
return;
|
||||
}
|
||||
walk_expr(self, ex);
|
||||
}
|
||||
}
|
||||
|
@ -140,70 +140,45 @@ fn check_opt_like<'a>(
|
||||
ty: Ty<'a>,
|
||||
els: Option<&Expr<'_>>,
|
||||
) {
|
||||
// list of candidate `Enum`s we know will never get any more members
|
||||
let candidates = &[
|
||||
(&paths::COW, "Borrowed"),
|
||||
(&paths::COW, "Cow::Borrowed"),
|
||||
(&paths::COW, "Cow::Owned"),
|
||||
(&paths::COW, "Owned"),
|
||||
(&paths::OPTION, "None"),
|
||||
(&paths::RESULT, "Err"),
|
||||
(&paths::RESULT, "Ok"),
|
||||
];
|
||||
|
||||
// We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive
|
||||
// match with the second branch, without enum variants in matches.
|
||||
if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(arms[0].pat, arms[1].pat) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut paths_and_types = Vec::new();
|
||||
if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) {
|
||||
return;
|
||||
}
|
||||
|
||||
let in_candidate_enum = |path_info: &(String, Ty<'_>)| -> bool {
|
||||
let (path, ty) = path_info;
|
||||
for &(ty_path, pat_path) in candidates {
|
||||
if path == pat_path && match_type(cx, *ty, ty_path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
};
|
||||
if paths_and_types.iter().all(in_candidate_enum) {
|
||||
// We don't want to lint if the second arm contains an enum which could
|
||||
// have more variants in the future.
|
||||
if form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat) {
|
||||
report_single_pattern(cx, ex, arms, expr, els);
|
||||
}
|
||||
}
|
||||
|
||||
/// Collects paths and their types from the given patterns. Returns true if the given pattern could
|
||||
/// be simplified, false otherwise.
|
||||
fn collect_pat_paths<'a>(acc: &mut Vec<(String, Ty<'a>)>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool {
|
||||
/// Returns `true` if all of the types in the pattern are enums which we know
|
||||
/// won't be expanded in the future
|
||||
fn pat_in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'a>, pat: &Pat<'_>) -> bool {
|
||||
let mut paths_and_types = Vec::new();
|
||||
collect_pat_paths(&mut paths_and_types, cx, pat, ty);
|
||||
paths_and_types.iter().all(|ty| in_candidate_enum(cx, *ty))
|
||||
}
|
||||
|
||||
/// Returns `true` if the given type is an enum we know won't be expanded in the future
|
||||
fn in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'_>) -> bool {
|
||||
// list of candidate `Enum`s we know will never get any more members
|
||||
let candidates = [&paths::COW, &paths::OPTION, &paths::RESULT];
|
||||
|
||||
for candidate_ty in candidates {
|
||||
if match_type(cx, ty, candidate_ty) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Collects types from the given pattern
|
||||
fn collect_pat_paths<'a>(acc: &mut Vec<Ty<'a>>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) {
|
||||
match pat.kind {
|
||||
PatKind::Wild => true,
|
||||
PatKind::Tuple(inner, _) => inner.iter().all(|p| {
|
||||
PatKind::Tuple(inner, _) => inner.iter().for_each(|p| {
|
||||
let p_ty = cx.typeck_results().pat_ty(p);
|
||||
collect_pat_paths(acc, cx, p, p_ty)
|
||||
collect_pat_paths(acc, cx, p, p_ty);
|
||||
}),
|
||||
PatKind::TupleStruct(ref path, ..) => {
|
||||
let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
|
||||
s.print_qpath(path, false);
|
||||
});
|
||||
acc.push((path, ty));
|
||||
true
|
||||
PatKind::TupleStruct(..) | PatKind::Binding(BindingAnnotation::Unannotated, .., None) | PatKind::Path(_) => {
|
||||
acc.push(ty);
|
||||
},
|
||||
PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => {
|
||||
acc.push((ident.to_string(), ty));
|
||||
true
|
||||
},
|
||||
PatKind::Path(ref path) => {
|
||||
let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
|
||||
s.print_qpath(path, false);
|
||||
});
|
||||
acc.push((path, ty));
|
||||
true
|
||||
},
|
||||
_ => false,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,7 +193,7 @@ fn contains_only_wilds(pat: &Pat<'_>) -> bool {
|
||||
|
||||
/// Returns true if the given patterns forms only exhaustive matches that don't contain enum
|
||||
/// patterns without a wildcard.
|
||||
fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool {
|
||||
fn form_exhaustive_matches<'a>(cx: &LateContext<'a>, ty: Ty<'a>, left: &Pat<'_>, right: &Pat<'_>) -> bool {
|
||||
match (&left.kind, &right.kind) {
|
||||
(PatKind::Wild, _) | (_, PatKind::Wild) => true,
|
||||
(PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => {
|
||||
@ -264,6 +239,10 @@ fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool {
|
||||
}
|
||||
true
|
||||
},
|
||||
(PatKind::TupleStruct(..), PatKind::Path(_)) => pat_in_candidate_enum(cx, ty, right),
|
||||
(PatKind::TupleStruct(..), PatKind::TupleStruct(_, inner, _)) => {
|
||||
pat_in_candidate_enum(cx, ty, right) && inner.iter().all(contains_only_wilds)
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -14,14 +14,17 @@ use super::MAP_FLATTEN;
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) {
|
||||
if let Some((caller_ty_name, method_to_use)) = try_get_caller_ty_name_and_method_name(cx, expr, recv, map_arg) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
|
||||
let closure_snippet = snippet_with_applicability(cx, map_arg.span, "..", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MAP_FLATTEN,
|
||||
expr.span.with_lo(map_span.lo()),
|
||||
&format!("called `map(..).flatten()` on `{}`", caller_ty_name),
|
||||
&format!("try replacing `map` with `{}` and remove the `.flatten()`", method_to_use),
|
||||
&format!(
|
||||
"try replacing `map` with `{}` and remove the `.flatten()`",
|
||||
method_to_use
|
||||
),
|
||||
format!("{}({})", method_to_use, closure_snippet),
|
||||
applicability,
|
||||
);
|
||||
|
@ -1,28 +1,21 @@
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then};
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::source::{snippet, snippet_opt};
|
||||
use clippy_utils::ty::{implements_trait, is_copy};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{
|
||||
self as hir, def, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind, Stmt,
|
||||
StmtKind, TyKind, UnOp,
|
||||
StmtKind, TyKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::hygiene::DesugaringKind;
|
||||
use rustc_span::source_map::{ExpnKind, Span};
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{
|
||||
get_item_name, get_parent_expr, in_constant, is_integer_const, iter_input_pats, last_path_segment,
|
||||
match_any_def_paths, path_def_id, paths, unsext, SpanlessEq,
|
||||
};
|
||||
use clippy_utils::{get_parent_expr, in_constant, iter_input_pats, last_path_segment, SpanlessEq};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -58,122 +51,6 @@ declare_clippy_lint! {
|
||||
style,
|
||||
"an entire binding declared as `ref`, in a function argument or a `let` statement"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for comparisons to NaN.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// NaN does not compare meaningfully to anything – not
|
||||
/// even itself – so those comparisons are simply wrong.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let x = 1.0;
|
||||
/// if x == f32::NAN { }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # let x = 1.0f32;
|
||||
/// if x.is_nan() { }
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub CMP_NAN,
|
||||
correctness,
|
||||
"comparisons to `NAN`, which will always return false, probably not intended"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for (in-)equality comparisons on floating-point
|
||||
/// values (apart from zero), except in functions called `*eq*` (which probably
|
||||
/// implement equality for a type involving floats).
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Floating point calculations are usually imprecise, so
|
||||
/// asking if two values are *exactly* equal is asking for trouble. For a good
|
||||
/// guide on what to do, see [the floating point
|
||||
/// guide](http://www.floating-point-gui.de/errors/comparison).
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let x = 1.2331f64;
|
||||
/// let y = 1.2332f64;
|
||||
///
|
||||
/// if y == 1.23f64 { }
|
||||
/// if y != x {} // where both are floats
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # let x = 1.2331f64;
|
||||
/// # let y = 1.2332f64;
|
||||
/// let error_margin = f64::EPSILON; // Use an epsilon for comparison
|
||||
/// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
|
||||
/// // let error_margin = std::f64::EPSILON;
|
||||
/// if (y - 1.23f64).abs() < error_margin { }
|
||||
/// if (y - x).abs() > error_margin { }
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub FLOAT_CMP,
|
||||
pedantic,
|
||||
"using `==` or `!=` on float values instead of comparing difference with an epsilon"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for conversions to owned values just for the sake
|
||||
/// of a comparison.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The comparison can operate on a reference, so creating
|
||||
/// an owned value effectively throws it away directly afterwards, which is
|
||||
/// needlessly consuming code and heap space.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let x = "foo";
|
||||
/// # let y = String::from("foo");
|
||||
/// if x.to_owned() == y {}
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # let x = "foo";
|
||||
/// # let y = String::from("foo");
|
||||
/// if x == y {}
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub CMP_OWNED,
|
||||
perf,
|
||||
"creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for getting the remainder of a division by one or minus
|
||||
/// one.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The result for a divisor of one can only ever be zero; for
|
||||
/// minus one it can cause panic/overflow (if the left operand is the minimal value of
|
||||
/// the respective integer type) or results in zero. No one will write such code
|
||||
/// deliberately, unless trying to win an Underhanded Rust Contest. Even for that
|
||||
/// contest, it's probably a bad idea. Use something more underhanded.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let x = 1;
|
||||
/// let a = x % 1;
|
||||
/// let a = x % -1;
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub MODULO_ONE,
|
||||
correctness,
|
||||
"taking a number modulo +/-1, which can either panic/overflow or always returns 0"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for the use of bindings with a single leading
|
||||
@ -244,51 +121,11 @@ declare_clippy_lint! {
|
||||
"using `0 as *{const, mut} T`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for (in-)equality comparisons on floating-point
|
||||
/// value and constant, except in functions called `*eq*` (which probably
|
||||
/// implement equality for a type involving floats).
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Floating point calculations are usually imprecise, so
|
||||
/// asking if two values are *exactly* equal is asking for trouble. For a good
|
||||
/// guide on what to do, see [the floating point
|
||||
/// guide](http://www.floating-point-gui.de/errors/comparison).
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let x: f64 = 1.0;
|
||||
/// const ONE: f64 = 1.00;
|
||||
///
|
||||
/// if x == ONE { } // where both are floats
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # let x: f64 = 1.0;
|
||||
/// # const ONE: f64 = 1.00;
|
||||
/// let error_margin = f64::EPSILON; // Use an epsilon for comparison
|
||||
/// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
|
||||
/// // let error_margin = std::f64::EPSILON;
|
||||
/// if (x - ONE).abs() < error_margin { }
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub FLOAT_CMP_CONST,
|
||||
restriction,
|
||||
"using `==` or `!=` on float constants instead of comparing difference with an epsilon"
|
||||
}
|
||||
|
||||
declare_lint_pass!(MiscLints => [
|
||||
TOPLEVEL_REF_ARG,
|
||||
CMP_NAN,
|
||||
FLOAT_CMP,
|
||||
CMP_OWNED,
|
||||
MODULO_ONE,
|
||||
USED_UNDERSCORE_BINDING,
|
||||
SHORT_CIRCUIT_STATEMENT,
|
||||
ZERO_PTR,
|
||||
FLOAT_CMP_CONST
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for MiscLints {
|
||||
@ -398,16 +235,9 @@ impl<'tcx> LateLintPass<'tcx> for MiscLints {
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
match expr.kind {
|
||||
ExprKind::Cast(e, ty) => {
|
||||
check_cast(cx, expr.span, e, ty);
|
||||
return;
|
||||
},
|
||||
ExprKind::Binary(ref cmp, left, right) => {
|
||||
check_binary(cx, expr, cmp, left, right);
|
||||
return;
|
||||
},
|
||||
_ => {},
|
||||
if let ExprKind::Cast(e, ty) = expr.kind {
|
||||
check_cast(cx, expr.span, e, ty);
|
||||
return;
|
||||
}
|
||||
if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) {
|
||||
// Don't lint things expanded by #[derive(...)], etc or `await` desugaring
|
||||
@ -455,236 +285,6 @@ impl<'tcx> LateLintPass<'tcx> for MiscLints {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_lint_and_message(
|
||||
is_comparing_constants: bool,
|
||||
is_comparing_arrays: bool,
|
||||
) -> (&'static rustc_lint::Lint, &'static str) {
|
||||
if is_comparing_constants {
|
||||
(
|
||||
FLOAT_CMP_CONST,
|
||||
if is_comparing_arrays {
|
||||
"strict comparison of `f32` or `f64` constant arrays"
|
||||
} else {
|
||||
"strict comparison of `f32` or `f64` constant"
|
||||
},
|
||||
)
|
||||
} else {
|
||||
(
|
||||
FLOAT_CMP,
|
||||
if is_comparing_arrays {
|
||||
"strict comparison of `f32` or `f64` arrays"
|
||||
} else {
|
||||
"strict comparison of `f32` or `f64`"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn check_nan(cx: &LateContext<'_>, expr: &Expr<'_>, cmp_expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if !in_constant(cx, cmp_expr.hir_id);
|
||||
if let Some((value, _)) = constant(cx, cx.typeck_results(), expr);
|
||||
if match value {
|
||||
Constant::F32(num) => num.is_nan(),
|
||||
Constant::F64(num) => num.is_nan(),
|
||||
_ => false,
|
||||
};
|
||||
then {
|
||||
span_lint(
|
||||
cx,
|
||||
CMP_NAN,
|
||||
cmp_expr.span,
|
||||
"doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
|
||||
if let Some((_, res)) = constant(cx, cx.typeck_results(), expr) {
|
||||
res
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
|
||||
match constant(cx, cx.typeck_results(), expr) {
|
||||
Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(),
|
||||
Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(),
|
||||
Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f {
|
||||
Constant::F32(f) => *f == 0.0 || (*f).is_infinite(),
|
||||
Constant::F64(f) => *f == 0.0 || (*f).is_infinite(),
|
||||
_ => false,
|
||||
}),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if `expr` is the result of `signum()` invoked on a float value.
|
||||
fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
// The negation of a signum is still a signum
|
||||
if let ExprKind::Unary(UnOp::Neg, child_expr) = expr.kind {
|
||||
return is_signum(cx, child_expr);
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(method_name, [ref self_arg, ..], _) = expr.kind;
|
||||
if sym!(signum) == method_name.ident.name;
|
||||
// Check that the receiver of the signum() is a float (expressions[0] is the receiver of
|
||||
// the method call)
|
||||
then {
|
||||
return is_float(cx, self_arg);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_float(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
let value = &cx.typeck_results().expr_ty(expr).peel_refs().kind();
|
||||
|
||||
if let ty::Array(arr_ty, _) = value {
|
||||
return matches!(arr_ty.kind(), ty::Float(_));
|
||||
};
|
||||
|
||||
matches!(value, ty::Float(_))
|
||||
}
|
||||
|
||||
fn is_array(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
matches!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _))
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) {
|
||||
#[derive(Default)]
|
||||
struct EqImpl {
|
||||
ty_eq_other: bool,
|
||||
other_eq_ty: bool,
|
||||
}
|
||||
|
||||
impl EqImpl {
|
||||
fn is_implemented(&self) -> bool {
|
||||
self.ty_eq_other || self.other_eq_ty
|
||||
}
|
||||
}
|
||||
|
||||
fn symmetric_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> Option<EqImpl> {
|
||||
cx.tcx.lang_items().eq_trait().map(|def_id| EqImpl {
|
||||
ty_eq_other: implements_trait(cx, ty, def_id, &[other.into()]),
|
||||
other_eq_ty: implements_trait(cx, other, def_id, &[ty.into()]),
|
||||
})
|
||||
}
|
||||
|
||||
let typeck = cx.typeck_results();
|
||||
let (arg, arg_span) = match expr.kind {
|
||||
ExprKind::MethodCall(.., [arg], _)
|
||||
if typeck
|
||||
.type_dependent_def_id(expr.hir_id)
|
||||
.and_then(|id| cx.tcx.trait_of_item(id))
|
||||
.map_or(false, |id| {
|
||||
matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ToString | sym::ToOwned))
|
||||
}) =>
|
||||
{
|
||||
(arg, arg.span)
|
||||
},
|
||||
ExprKind::Call(path, [arg])
|
||||
if path_def_id(cx, path)
|
||||
.and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM]))
|
||||
.map_or(false, |idx| match idx {
|
||||
0 => true,
|
||||
1 => !is_copy(cx, typeck.expr_ty(expr)),
|
||||
_ => false,
|
||||
}) =>
|
||||
{
|
||||
(arg, arg.span)
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let arg_ty = typeck.expr_ty(arg);
|
||||
let other_ty = typeck.expr_ty(other);
|
||||
|
||||
let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default();
|
||||
let with_deref = arg_ty
|
||||
.builtin_deref(true)
|
||||
.and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty))
|
||||
.unwrap_or_default();
|
||||
|
||||
if !with_deref.is_implemented() && !without_deref.is_implemented() {
|
||||
return;
|
||||
}
|
||||
|
||||
let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _));
|
||||
|
||||
let lint_span = if other_gets_derefed {
|
||||
expr.span.to(other.span)
|
||||
} else {
|
||||
expr.span
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
CMP_OWNED,
|
||||
lint_span,
|
||||
"this creates an owned instance just for comparison",
|
||||
|diag| {
|
||||
// This also catches `PartialEq` implementations that call `to_owned`.
|
||||
if other_gets_derefed {
|
||||
diag.span_label(lint_span, "try implementing the comparison without allocating");
|
||||
return;
|
||||
}
|
||||
|
||||
let arg_snip = snippet(cx, arg_span, "..");
|
||||
let expr_snip;
|
||||
let eq_impl;
|
||||
if with_deref.is_implemented() {
|
||||
expr_snip = format!("*{}", arg_snip);
|
||||
eq_impl = with_deref;
|
||||
} else {
|
||||
expr_snip = arg_snip.to_string();
|
||||
eq_impl = without_deref;
|
||||
};
|
||||
|
||||
let span;
|
||||
let hint;
|
||||
if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) {
|
||||
span = expr.span;
|
||||
hint = expr_snip;
|
||||
} else {
|
||||
span = expr.span.to(other.span);
|
||||
|
||||
let cmp_span = if other.span < expr.span {
|
||||
other.span.between(expr.span)
|
||||
} else {
|
||||
expr.span.between(other.span)
|
||||
};
|
||||
if eq_impl.ty_eq_other {
|
||||
hint = format!(
|
||||
"{}{}{}",
|
||||
expr_snip,
|
||||
snippet(cx, cmp_span, ".."),
|
||||
snippet(cx, other.span, "..")
|
||||
);
|
||||
} else {
|
||||
hint = format!(
|
||||
"{}{}{}",
|
||||
snippet(cx, other.span, ".."),
|
||||
snippet(cx, cmp_span, ".."),
|
||||
expr_snip
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
"try",
|
||||
hint,
|
||||
Applicability::MachineApplicable, // snippet
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Heuristic to see if an expression is used. Should be compatible with
|
||||
/// `unused_variables`'s idea
|
||||
/// of what it means for an expression to be "used".
|
||||
@ -740,74 +340,3 @@ fn check_cast(cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_binary<'a>(
|
||||
cx: &LateContext<'a>,
|
||||
expr: &Expr<'_>,
|
||||
cmp: &rustc_span::source_map::Spanned<rustc_hir::BinOpKind>,
|
||||
left: &'a Expr<'_>,
|
||||
right: &'a Expr<'_>,
|
||||
) {
|
||||
let op = cmp.node;
|
||||
if op.is_comparison() {
|
||||
check_nan(cx, left, expr);
|
||||
check_nan(cx, right, expr);
|
||||
check_to_owned(cx, left, right, true);
|
||||
check_to_owned(cx, right, left, false);
|
||||
}
|
||||
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
|
||||
if is_allowed(cx, left) || is_allowed(cx, right) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow comparing the results of signum()
|
||||
if is_signum(cx, left) && is_signum(cx, right) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(name) = get_item_name(cx, expr) {
|
||||
let name = name.as_str();
|
||||
if name == "eq" || name == "ne" || name == "is_nan" || name.starts_with("eq_") || name.ends_with("_eq") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
|
||||
let (lint, msg) = get_lint_and_message(
|
||||
is_named_constant(cx, left) || is_named_constant(cx, right),
|
||||
is_comparing_arrays,
|
||||
);
|
||||
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
|
||||
let lhs = Sugg::hir(cx, left, "..");
|
||||
let rhs = Sugg::hir(cx, right, "..");
|
||||
|
||||
if !is_comparing_arrays {
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"consider comparing them within some margin of error",
|
||||
format!(
|
||||
"({}).abs() {} error_margin",
|
||||
lhs - rhs,
|
||||
if op == BinOpKind::Eq { '<' } else { '>' }
|
||||
),
|
||||
Applicability::HasPlaceholders, // snippet
|
||||
);
|
||||
}
|
||||
diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`");
|
||||
});
|
||||
} else if op == BinOpKind::Rem {
|
||||
if is_integer_const(cx, right, 1) {
|
||||
span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0");
|
||||
}
|
||||
|
||||
if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() {
|
||||
if is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) {
|
||||
span_lint(
|
||||
cx,
|
||||
MODULO_ONE,
|
||||
expr.span,
|
||||
"any number modulo -1 will panic/overflow or result in 0",
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
use clippy_utils::consts::{self, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::util::parser::PREC_PREFIX;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@ -58,7 +60,12 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) {
|
||||
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let suggestion = format!("-{}", snippet_with_applicability(cx, exp.span, "..", &mut applicability));
|
||||
let snip = snippet_with_applicability(cx, exp.span, "..", &mut applicability);
|
||||
let suggestion = if exp.precedence().order() < PREC_PREFIX && !has_enclosing_paren(&snip) {
|
||||
format!("-({})", snip)
|
||||
} else {
|
||||
format!("-{}", snip)
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEG_MULTIPLY,
|
||||
|
@ -6,6 +6,7 @@ use std::ptr;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::in_constant;
|
||||
use clippy_utils::macros::macro_backtrace;
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::DefId;
|
||||
@ -18,7 +19,7 @@ use rustc_middle::mir::interpret::{ConstValue, ErrorHandled};
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{InnerSpan, Span, DUMMY_SP};
|
||||
use rustc_span::{sym, InnerSpan, Span, DUMMY_SP};
|
||||
use rustc_typeck::hir_ty_to_ty;
|
||||
|
||||
// FIXME: this is a correctness problem but there's no suitable
|
||||
@ -250,8 +251,14 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) {
|
||||
if let ItemKind::Const(hir_ty, body_id) = it.kind {
|
||||
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
|
||||
|
||||
if is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, ty) {
|
||||
if !macro_backtrace(it.span).last().map_or(false, |macro_call| {
|
||||
matches!(
|
||||
cx.tcx.get_diagnostic_name(macro_call.def_id),
|
||||
Some(sym::thread_local_macro)
|
||||
)
|
||||
}) && is_unfrozen(cx, ty)
|
||||
&& is_value_unfrozen_poly(cx, body_id, ty)
|
||||
{
|
||||
lint(cx, Source::Item { item: it.span });
|
||||
}
|
||||
}
|
||||
|
@ -159,12 +159,10 @@ impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> {
|
||||
|
||||
#[must_use]
|
||||
fn get_exemptions(interned_name: &str) -> Option<&'static [&'static str]> {
|
||||
for &list in ALLOWED_TO_BE_SIMILAR {
|
||||
if allowed_to_be_similar(interned_name, list) {
|
||||
return Some(list);
|
||||
}
|
||||
}
|
||||
None
|
||||
ALLOWED_TO_BE_SIMILAR
|
||||
.iter()
|
||||
.find(|&&list| allowed_to_be_similar(interned_name, list))
|
||||
.copied()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@ -328,7 +326,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SimilarNamesLocalVisitor<'a, 'tcx> {
|
||||
// add the pattern after the expression because the bindings aren't available
|
||||
// yet in the init
|
||||
// expression
|
||||
SimilarNamesNameVisitor(self).visit_pat(&*local.pat);
|
||||
SimilarNamesNameVisitor(self).visit_pat(&local.pat);
|
||||
}
|
||||
fn visit_block(&mut self, blk: &'tcx Block) {
|
||||
self.single_char_names.push(vec![]);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
use clippy_utils::comparisons::{normalize_comparison, Rel};
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
@ -10,73 +9,41 @@ use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_isize_or_usize;
|
||||
use clippy_utils::{clip, int_bits, unsext};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for comparisons where one side of the relation is
|
||||
/// either the minimum or maximum value for its type and warns if it involves a
|
||||
/// case that is always true or always false. Only integer and boolean types are
|
||||
/// checked.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// An expression like `min <= x` may misleadingly imply
|
||||
/// that it is possible for `x` to be less than the minimum. Expressions like
|
||||
/// `max < x` are probably mistakes.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// For `usize` the size of the current compile target will
|
||||
/// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such
|
||||
/// a comparison to detect target pointer width will trigger this lint. One can
|
||||
/// use `mem::sizeof` and compare its value or conditional compilation
|
||||
/// attributes
|
||||
/// like `#[cfg(target_pointer_width = "64")] ..` instead.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let vec: Vec<isize> = Vec::new();
|
||||
/// if vec.len() <= 0 {}
|
||||
/// if 100 > i32::MAX {}
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub ABSURD_EXTREME_COMPARISONS,
|
||||
correctness,
|
||||
"a comparison with a maximum or minimum value that is always true or false"
|
||||
}
|
||||
use super::ABSURD_EXTREME_COMPARISONS;
|
||||
|
||||
declare_lint_pass!(AbsurdExtremeComparisons => [ABSURD_EXTREME_COMPARISONS]);
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
op: BinOpKind,
|
||||
lhs: &'tcx Expr<'_>,
|
||||
rhs: &'tcx Expr<'_>,
|
||||
) {
|
||||
if let Some((culprit, result)) = detect_absurd_comparison(cx, op, lhs, rhs) {
|
||||
let msg = "this comparison involving the minimum or maximum element for this \
|
||||
type contains a case that is always true or always false";
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for AbsurdExtremeComparisons {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind {
|
||||
if let Some((culprit, result)) = detect_absurd_comparison(cx, cmp.node, lhs, rhs) {
|
||||
if !expr.span.from_expansion() {
|
||||
let msg = "this comparison involving the minimum or maximum element for this \
|
||||
type contains a case that is always true or always false";
|
||||
let conclusion = match result {
|
||||
AbsurdComparisonResult::AlwaysFalse => "this comparison is always false".to_owned(),
|
||||
AbsurdComparisonResult::AlwaysTrue => "this comparison is always true".to_owned(),
|
||||
AbsurdComparisonResult::InequalityImpossible => format!(
|
||||
"the case where the two sides are not equal never occurs, consider using `{} == {}` \
|
||||
instead",
|
||||
snippet(cx, lhs.span, "lhs"),
|
||||
snippet(cx, rhs.span, "rhs")
|
||||
),
|
||||
};
|
||||
|
||||
let conclusion = match result {
|
||||
AbsurdComparisonResult::AlwaysFalse => "this comparison is always false".to_owned(),
|
||||
AbsurdComparisonResult::AlwaysTrue => "this comparison is always true".to_owned(),
|
||||
AbsurdComparisonResult::InequalityImpossible => format!(
|
||||
"the case where the two sides are not equal never occurs, consider using `{} == {}` \
|
||||
instead",
|
||||
snippet(cx, lhs.span, "lhs"),
|
||||
snippet(cx, rhs.span, "rhs")
|
||||
),
|
||||
};
|
||||
let help = format!(
|
||||
"because `{}` is the {} value for this type, {}",
|
||||
snippet(cx, culprit.expr.span, "x"),
|
||||
match culprit.which {
|
||||
ExtremeType::Minimum => "minimum",
|
||||
ExtremeType::Maximum => "maximum",
|
||||
},
|
||||
conclusion
|
||||
);
|
||||
|
||||
let help = format!(
|
||||
"because `{}` is the {} value for this type, {}",
|
||||
snippet(cx, culprit.expr.span, "x"),
|
||||
match culprit.which {
|
||||
ExtremeType::Minimum => "minimum",
|
||||
ExtremeType::Maximum => "maximum",
|
||||
},
|
||||
conclusion
|
||||
);
|
||||
|
||||
span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help);
|
||||
}
|
||||
}
|
||||
}
|
||||
span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help);
|
||||
}
|
||||
}
|
||||
|
101
clippy_lints/src/operators/assign_op_pattern.rs
Normal file
101
clippy_lints/src/operators/assign_op_pattern.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -1,161 +1,23 @@
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::source_map::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for incompatible bit masks in comparisons.
|
||||
///
|
||||
/// The formula for detecting if an expression of the type `_ <bit_op> m
|
||||
/// <cmp_op> c` (where `<bit_op>` is one of {`&`, `|`} and `<cmp_op>` is one of
|
||||
/// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following
|
||||
/// table:
|
||||
///
|
||||
/// |Comparison |Bit Op|Example |is always|Formula |
|
||||
/// |------------|------|-------------|---------|----------------------|
|
||||
/// |`==` or `!=`| `&` |`x & 2 == 3` |`false` |`c & m != c` |
|
||||
/// |`<` or `>=`| `&` |`x & 2 < 3` |`true` |`m < c` |
|
||||
/// |`>` or `<=`| `&` |`x & 1 > 1` |`false` |`m <= c` |
|
||||
/// |`==` or `!=`| `\|` |`x \| 1 == 0`|`false` |`c \| m != c` |
|
||||
/// |`<` or `>=`| `\|` |`x \| 1 < 1` |`false` |`m >= c` |
|
||||
/// |`<=` or `>` | `\|` |`x \| 1 > 0` |`true` |`m > c` |
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// If the bits that the comparison cares about are always
|
||||
/// set to zero or one by the bit mask, the comparison is constant `true` or
|
||||
/// `false` (depending on mask, compared value, and operators).
|
||||
///
|
||||
/// So the code is actively misleading, and the only reason someone would write
|
||||
/// this intentionally is to win an underhanded Rust contest or create a
|
||||
/// test-case for this lint.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let x = 1;
|
||||
/// if (x & 1 == 2) { }
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub BAD_BIT_MASK,
|
||||
correctness,
|
||||
"expressions of the form `_ & mask == select` that will only ever return `true` or `false`"
|
||||
}
|
||||
use super::{BAD_BIT_MASK, INEFFECTIVE_BIT_MASK};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for bit masks in comparisons which can be removed
|
||||
/// without changing the outcome. The basic structure can be seen in the
|
||||
/// following table:
|
||||
///
|
||||
/// |Comparison| Bit Op |Example |equals |
|
||||
/// |----------|----------|------------|-------|
|
||||
/// |`>` / `<=`|`\|` / `^`|`x \| 2 > 3`|`x > 3`|
|
||||
/// |`<` / `>=`|`\|` / `^`|`x ^ 1 < 4` |`x < 4`|
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Not equally evil as [`bad_bit_mask`](#bad_bit_mask),
|
||||
/// but still a bit misleading, because the bit mask is ineffective.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// False negatives: This lint will only match instances
|
||||
/// where we have figured out the math (which is for a power-of-two compared
|
||||
/// value). This means things like `x | 1 >= 7` (which would be better written
|
||||
/// as `x >= 6`) will not be reported (but bit masks like this are fairly
|
||||
/// uncommon).
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let x = 1;
|
||||
/// if (x | 1 > 3) { }
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub INEFFECTIVE_BIT_MASK,
|
||||
correctness,
|
||||
"expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for bit masks that can be replaced by a call
|
||||
/// to `trailing_zeros`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `x.trailing_zeros() > 4` is much clearer than `x & 15
|
||||
/// == 0`
|
||||
///
|
||||
/// ### Known problems
|
||||
/// llvm generates better code for `x & 15 == 0` on x86
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let x = 1;
|
||||
/// if x & 0b1111 == 0 { }
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub VERBOSE_BIT_MASK,
|
||||
pedantic,
|
||||
"expressions where a bit mask is less readable than the corresponding method call"
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct BitMask {
|
||||
verbose_bit_mask_threshold: u64,
|
||||
}
|
||||
|
||||
impl BitMask {
|
||||
#[must_use]
|
||||
pub fn new(verbose_bit_mask_threshold: u64) -> Self {
|
||||
Self {
|
||||
verbose_bit_mask_threshold,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(BitMask => [BAD_BIT_MASK, INEFFECTIVE_BIT_MASK, VERBOSE_BIT_MASK]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for BitMask {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Binary(cmp, left, right) = &e.kind {
|
||||
if cmp.node.is_comparison() {
|
||||
if let Some(cmp_opt) = fetch_int_literal(cx, right) {
|
||||
check_compare(cx, left, cmp.node, cmp_opt, e.span);
|
||||
} else if let Some(cmp_val) = fetch_int_literal(cx, left) {
|
||||
check_compare(cx, right, invert_cmp(cmp.node), cmp_val, e.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let ExprKind::Binary(op, left, right) = &e.kind
|
||||
&& BinOpKind::Eq == op.node
|
||||
&& let ExprKind::Binary(op1, left1, right1) = &left.kind
|
||||
&& BinOpKind::BitAnd == op1.node
|
||||
&& let ExprKind::Lit(lit) = &right1.kind
|
||||
&& let LitKind::Int(n, _) = lit.node
|
||||
&& let ExprKind::Lit(lit1) = &right.kind
|
||||
&& let LitKind::Int(0, _) = lit1.node
|
||||
&& n.leading_zeros() == n.count_zeros()
|
||||
&& n > u128::from(self.verbose_bit_mask_threshold)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
VERBOSE_BIT_MASK,
|
||||
e.span,
|
||||
"bit mask could be simplified with a call to `trailing_zeros`",
|
||||
|diag| {
|
||||
let sugg = Sugg::hir(cx, left1, "...").maybe_par();
|
||||
diag.span_suggestion(
|
||||
e.span,
|
||||
"try",
|
||||
format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &'tcx Expr<'_>,
|
||||
op: BinOpKind,
|
||||
left: &'tcx Expr<'_>,
|
||||
right: &'tcx Expr<'_>,
|
||||
) {
|
||||
if op.is_comparison() {
|
||||
if let Some(cmp_opt) = fetch_int_literal(cx, right) {
|
||||
check_compare(cx, left, op, cmp_opt, e.span);
|
||||
} else if let Some(cmp_val) = fetch_int_literal(cx, left) {
|
||||
check_compare(cx, right, invert_cmp(op), cmp_val, e.span);
|
||||
}
|
||||
}
|
||||
}
|
30
clippy_lints/src/operators/cmp_nan.rs
Normal file
30
clippy_lints/src/operators/cmp_nan.rs
Normal 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
|
||||
}
|
||||
}
|
147
clippy_lints/src/operators/cmp_owned.rs
Normal file
147
clippy_lints/src/operators/cmp_owned.rs
Normal 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
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
54
clippy_lints/src/operators/double_comparison.rs
Normal file
54
clippy_lints/src/operators/double_comparison.rs
Normal 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!(==);
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
}
|
44
clippy_lints/src/operators/duration_subsec.rs
Normal file
44
clippy_lints/src/operators/duration_subsec.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
45
clippy_lints/src/operators/eq_op.rs
Normal file
45
clippy_lints/src/operators/eq_op.rs
Normal 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()),
|
||||
);
|
||||
}
|
||||
}
|
53
clippy_lints/src/operators/erasing_op.rs
Normal file
53
clippy_lints/src/operators/erasing_op.rs
Normal 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",
|
||||
);
|
||||
}
|
||||
}
|
139
clippy_lints/src/operators/float_cmp.rs
Normal file
139
clippy_lints/src/operators/float_cmp.rs
Normal 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(_, _))
|
||||
}
|
71
clippy_lints/src/operators/float_equality_without_abs.rs
Normal file
71
clippy_lints/src/operators/float_equality_without_abs.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,61 +3,40 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{clip, unsext};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, Node};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, Node};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for identity operations, e.g., `x + 0`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This code can be removed without changing the
|
||||
/// meaning. So it just obscures what's going on. Delete it mercilessly.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let x = 1;
|
||||
/// x / 1 + 0 * 1 - 0 | 0;
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub IDENTITY_OP,
|
||||
complexity,
|
||||
"using identity operations, e.g., `x + 0` or `y / 1`"
|
||||
}
|
||||
use super::IDENTITY_OP;
|
||||
|
||||
declare_lint_pass!(IdentityOp => [IDENTITY_OP]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for IdentityOp {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if expr.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
if let ExprKind::Binary(cmp, left, right) = &expr.kind {
|
||||
if !is_allowed(cx, *cmp, left, right) {
|
||||
match cmp.node {
|
||||
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
|
||||
check(cx, left, 0, expr.span, right.span, needs_parenthesis(cx, expr, right));
|
||||
check(cx, right, 0, expr.span, left.span, Parens::Unneeded);
|
||||
},
|
||||
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
|
||||
check(cx, right, 0, expr.span, left.span, Parens::Unneeded);
|
||||
},
|
||||
BinOpKind::Mul => {
|
||||
check(cx, left, 1, expr.span, right.span, needs_parenthesis(cx, expr, right));
|
||||
check(cx, right, 1, expr.span, left.span, Parens::Unneeded);
|
||||
},
|
||||
BinOpKind::Div => check(cx, right, 1, expr.span, left.span, Parens::Unneeded),
|
||||
BinOpKind::BitAnd => {
|
||||
check(cx, left, -1, expr.span, right.span, needs_parenthesis(cx, expr, right));
|
||||
check(cx, right, -1, expr.span, left.span, Parens::Unneeded);
|
||||
},
|
||||
BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
pub(crate) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
op: BinOpKind,
|
||||
left: &'tcx Expr<'_>,
|
||||
right: &'tcx Expr<'_>,
|
||||
) {
|
||||
if !is_allowed(cx, op, left, right) {
|
||||
match op {
|
||||
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
|
||||
check_op(cx, left, 0, expr.span, right.span, needs_parenthesis(cx, expr, right));
|
||||
check_op(cx, right, 0, expr.span, left.span, Parens::Unneeded);
|
||||
},
|
||||
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
|
||||
check_op(cx, right, 0, expr.span, left.span, Parens::Unneeded);
|
||||
},
|
||||
BinOpKind::Mul => {
|
||||
check_op(cx, left, 1, expr.span, right.span, needs_parenthesis(cx, expr, right));
|
||||
check_op(cx, right, 1, expr.span, left.span, Parens::Unneeded);
|
||||
},
|
||||
BinOpKind::Div => check_op(cx, right, 1, expr.span, left.span, Parens::Unneeded),
|
||||
BinOpKind::BitAnd => {
|
||||
check_op(cx, left, -1, expr.span, right.span, needs_parenthesis(cx, expr, right));
|
||||
check_op(cx, right, -1, expr.span, left.span, Parens::Unneeded);
|
||||
},
|
||||
BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,12 +87,12 @@ fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>)
|
||||
Parens::Needed
|
||||
}
|
||||
|
||||
fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> bool {
|
||||
fn is_allowed(cx: &LateContext<'_>, cmp: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> bool {
|
||||
// This lint applies to integers
|
||||
!cx.typeck_results().expr_ty(left).peel_refs().is_integral()
|
||||
|| !cx.typeck_results().expr_ty(right).peel_refs().is_integral()
|
||||
// `1 << 0` is a common pattern in bit manipulation code
|
||||
|| (cmp.node == BinOpKind::Shl
|
||||
|| (cmp == BinOpKind::Shl
|
||||
&& constant_simple(cx, cx.typeck_results(), right) == Some(Constant::Int(0))
|
||||
&& constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1)))
|
||||
}
|
||||
@ -130,7 +109,7 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span
|
||||
}
|
||||
}
|
||||
|
||||
fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens) {
|
||||
fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens) {
|
||||
if let Some(Constant::Int(v)) = constant_simple(cx, cx.typeck_results(), e).map(Constant::peel_refs) {
|
||||
let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() {
|
||||
ty::Int(ity) => unsext(cx.tcx, -1_i128, ity),
|
27
clippy_lints/src/operators/integer_division.rs
Normal file
27
clippy_lints/src/operators/integer_division.rs
Normal 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",
|
||||
);
|
||||
}
|
||||
}
|
84
clippy_lints/src/operators/misrefactored_assign_op.rs
Normal file
84
clippy_lints/src/operators/misrefactored_assign_op.rs
Normal 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,
|
||||
}
|
||||
}
|
849
clippy_lints/src/operators/mod.rs
Normal file
849
clippy_lints/src/operators/mod.rs
Normal 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
|
||||
}
|
||||
}
|
@ -2,35 +2,35 @@ use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::sext;
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_hir::{BinOpKind, Expr};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use std::fmt::Display;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for modulo arithmetic.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The results of modulo (%) operation might differ
|
||||
/// depending on the language, when negative numbers are involved.
|
||||
/// If you interop with different languages it might be beneficial
|
||||
/// to double check all places that use modulo arithmetic.
|
||||
///
|
||||
/// For example, in Rust `17 % -3 = 2`, but in Python `17 % -3 = -1`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let x = -17 % 3;
|
||||
/// ```
|
||||
#[clippy::version = "1.42.0"]
|
||||
pub MODULO_ARITHMETIC,
|
||||
restriction,
|
||||
"any modulo arithmetic statement"
|
||||
}
|
||||
use super::MODULO_ARITHMETIC;
|
||||
|
||||
declare_lint_pass!(ModuloArithmetic => [MODULO_ARITHMETIC]);
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &'tcx Expr<'_>,
|
||||
op: BinOpKind,
|
||||
lhs: &'tcx Expr<'_>,
|
||||
rhs: &'tcx Expr<'_>,
|
||||
) {
|
||||
if op == BinOpKind::Rem {
|
||||
let lhs_operand = analyze_operand(lhs, cx, e);
|
||||
let rhs_operand = analyze_operand(rhs, cx, e);
|
||||
if_chain! {
|
||||
if let Some(lhs_operand) = lhs_operand;
|
||||
if let Some(rhs_operand) = rhs_operand;
|
||||
then {
|
||||
check_const_operands(cx, e, &lhs_operand, &rhs_operand);
|
||||
}
|
||||
else {
|
||||
check_non_const_operands(cx, e, lhs);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
struct OperandInfo {
|
||||
string_representation: Option<String>,
|
||||
@ -124,27 +124,3 @@ fn check_non_const_operands<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ModuloArithmetic {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
match &expr.kind {
|
||||
ExprKind::Binary(op, lhs, rhs) | ExprKind::AssignOp(op, lhs, rhs) => {
|
||||
if op.node == BinOpKind::Rem {
|
||||
let lhs_operand = analyze_operand(lhs, cx, expr);
|
||||
let rhs_operand = analyze_operand(rhs, cx, expr);
|
||||
if_chain! {
|
||||
if let Some(lhs_operand) = lhs_operand;
|
||||
if let Some(rhs_operand) = rhs_operand;
|
||||
then {
|
||||
check_const_operands(cx, expr, &lhs_operand, &rhs_operand);
|
||||
}
|
||||
else {
|
||||
check_non_const_operands(cx, expr, lhs);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
26
clippy_lints/src/operators/modulo_one.rs
Normal file
26
clippy_lints/src/operators/modulo_one.rs
Normal 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",
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
36
clippy_lints/src/operators/needless_bitwise_bool.rs
Normal file
36
clippy_lints/src/operators/needless_bitwise_bool.rs
Normal 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);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
127
clippy_lints/src/operators/numeric_arithmetic.rs
Normal file
127
clippy_lints/src/operators/numeric_arithmetic.rs
Normal 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;
|
||||
}
|
||||
}
|
218
clippy_lints/src/operators/op_ref.rs
Normal file
218
clippy_lints/src/operators/op_ref.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
65
clippy_lints/src/operators/ptr_eq.rs
Normal file
65
clippy_lints/src/operators/ptr_eq.rs
Normal 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
|
||||
}
|
20
clippy_lints/src/operators/self_assignment.rs
Normal file
20
clippy_lints/src/operators/self_assignment.rs
Normal 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),
|
||||
);
|
||||
}
|
||||
}
|
44
clippy_lints/src/operators/verbose_bit_mask.rs
Normal file
44
clippy_lints/src/operators/verbose_bit_mask.rs
Normal 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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -3,17 +3,20 @@ use std::iter;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy};
|
||||
use clippy_utils::{is_self, is_self_ty};
|
||||
use core::ops::ControlFlow;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::attr;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::adjustment::{Adjust, PointerCast};
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::ty::{self, RegionKind};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::{sym, Span};
|
||||
@ -141,50 +144,76 @@ impl<'tcx> PassByRefOrValue {
|
||||
}
|
||||
|
||||
let fn_sig = cx.tcx.fn_sig(def_id);
|
||||
let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);
|
||||
|
||||
let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id));
|
||||
|
||||
for (index, (input, &ty)) in iter::zip(decl.inputs, fn_sig.inputs()).enumerate() {
|
||||
// Gather all the lifetimes found in the output type which may affect whether
|
||||
// `TRIVIALLY_COPY_PASS_BY_REF` should be linted.
|
||||
let mut output_regions = FxHashSet::default();
|
||||
for_each_top_level_late_bound_region(fn_sig.skip_binder().output(), |region| -> ControlFlow<!> {
|
||||
output_regions.insert(region);
|
||||
ControlFlow::Continue(())
|
||||
});
|
||||
|
||||
for (index, (input, ty)) in iter::zip(
|
||||
decl.inputs,
|
||||
fn_sig.skip_binder().inputs().iter().map(|&ty| fn_sig.rebind(ty)),
|
||||
)
|
||||
.enumerate()
|
||||
{
|
||||
// All spans generated from a proc-macro invocation are the same...
|
||||
match span {
|
||||
Some(s) if s == input.span => return,
|
||||
Some(s) if s == input.span => continue,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match ty.kind() {
|
||||
ty::Ref(input_lt, ty, Mutability::Not) => {
|
||||
// Use lifetimes to determine if we're returning a reference to the
|
||||
// argument. In that case we can't switch to pass-by-value as the
|
||||
// argument will not live long enough.
|
||||
let output_lts = match *fn_sig.output().kind() {
|
||||
ty::Ref(output_lt, _, _) => vec![output_lt],
|
||||
ty::Adt(_, substs) => substs.regions().collect(),
|
||||
_ => vec![],
|
||||
};
|
||||
match *ty.skip_binder().kind() {
|
||||
ty::Ref(lt, ty, Mutability::Not) => {
|
||||
match lt.kind() {
|
||||
RegionKind::ReLateBound(index, region)
|
||||
if index.as_u32() == 0 && output_regions.contains(®ion) =>
|
||||
{
|
||||
continue;
|
||||
},
|
||||
// Early bound regions on functions are either from the containing item, are bounded by another
|
||||
// lifetime, or are used as a bound for a type or lifetime.
|
||||
RegionKind::ReEarlyBound(..) => continue,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if !output_lts.contains(input_lt);
|
||||
if is_copy(cx, *ty);
|
||||
if let Some(size) = cx.layout_of(*ty).ok().map(|l| l.size.bytes());
|
||||
if size <= self.ref_min_size;
|
||||
if let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind;
|
||||
then {
|
||||
let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
|
||||
"self".into()
|
||||
} else {
|
||||
snippet(cx, decl_ty.span, "_").into()
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TRIVIALLY_COPY_PASS_BY_REF,
|
||||
input.span,
|
||||
&format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
|
||||
"consider passing by value instead",
|
||||
value_type,
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
let ty = cx.tcx.erase_late_bound_regions(fn_sig.rebind(ty));
|
||||
if is_copy(cx, ty)
|
||||
&& let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes())
|
||||
&& size <= self.ref_min_size
|
||||
&& let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind
|
||||
{
|
||||
if let Some(typeck) = cx.maybe_typeck_results() {
|
||||
// Don't lint if an unsafe pointer is created.
|
||||
// TODO: Limit the check only to unsafe pointers to the argument (or part of the argument)
|
||||
// which escape the current function.
|
||||
if typeck.node_types().iter().any(|(_, &ty)| ty.is_unsafe_ptr())
|
||||
|| typeck
|
||||
.adjustments()
|
||||
.iter()
|
||||
.flat_map(|(_, a)| a)
|
||||
.any(|a| matches!(a.kind, Adjust::Pointer(PointerCast::UnsafeFnPointer)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
|
||||
"self".into()
|
||||
} else {
|
||||
snippet(cx, decl_ty.span, "_").into()
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TRIVIALLY_COPY_PASS_BY_REF,
|
||||
input.span,
|
||||
&format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
|
||||
"consider passing by value instead",
|
||||
value_type,
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@ -196,6 +225,7 @@ impl<'tcx> PassByRefOrValue {
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
let ty = cx.tcx.erase_late_bound_regions(ty);
|
||||
|
||||
if_chain! {
|
||||
if is_copy(cx, ty);
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Checks for usage of `&Vec[_]` and `&String`.
|
||||
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::expr_sig;
|
||||
use clippy_utils::visitors::contains_unsafe_block;
|
||||
@ -166,15 +166,14 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
||||
)
|
||||
.filter(|arg| arg.mutability() == Mutability::Not)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
PTR_ARG,
|
||||
arg.span,
|
||||
&arg.build_msg(),
|
||||
"change this to",
|
||||
format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)),
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, &arg.build_msg(), |diag| {
|
||||
diag.span_suggestion(
|
||||
arg.span,
|
||||
"change this to",
|
||||
format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)),
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -221,7 +220,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
||||
let results = check_ptr_arg_usage(cx, body, &lint_args);
|
||||
|
||||
for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
|
||||
span_lint_and_then(cx, PTR_ARG, args.span, &args.build_msg(), |diag| {
|
||||
span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, &args.build_msg(), |diag| {
|
||||
diag.multipart_suggestion(
|
||||
"change this to",
|
||||
iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx))))
|
||||
@ -315,6 +314,7 @@ struct PtrArgReplacement {
|
||||
|
||||
struct PtrArg<'tcx> {
|
||||
idx: usize,
|
||||
emission_id: hir::HirId,
|
||||
span: Span,
|
||||
ty_did: DefId,
|
||||
ty_name: Symbol,
|
||||
@ -419,10 +419,8 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
|
||||
if let [.., name] = path.segments;
|
||||
if cx.tcx.item_name(adt.did()) == name.ident.name;
|
||||
|
||||
if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id);
|
||||
if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id));
|
||||
|
||||
then {
|
||||
let emission_id = params.get(i).map_or(hir_ty.hir_id, |param| param.hir_id);
|
||||
let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) {
|
||||
Some(sym::Vec) => (
|
||||
[("clone", ".to_owned()")].as_slice(),
|
||||
@ -455,14 +453,20 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
|
||||
})
|
||||
.and_then(|arg| snippet_opt(cx, arg.span))
|
||||
.unwrap_or_else(|| substs.type_at(1).to_string());
|
||||
span_lint_and_sugg(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
PTR_ARG,
|
||||
emission_id,
|
||||
hir_ty.span,
|
||||
"using a reference to `Cow` is not recommended",
|
||||
"change this to",
|
||||
format!("&{}{}", mutability.prefix_str(), ty_name),
|
||||
Applicability::Unspecified,
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
hir_ty.span,
|
||||
"change this to",
|
||||
format!("&{}{}", mutability.prefix_str(), ty_name),
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
}
|
||||
);
|
||||
return None;
|
||||
},
|
||||
@ -470,6 +474,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
|
||||
};
|
||||
return Some(PtrArg {
|
||||
idx: i,
|
||||
emission_id,
|
||||
span: hir_ty.span,
|
||||
ty_did: adt.did(),
|
||||
ty_name: name.ident.name,
|
||||
@ -574,14 +579,13 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args:
|
||||
Some((Node::Expr(e), child_id)) => match e.kind {
|
||||
ExprKind::Call(f, expr_args) => {
|
||||
let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
|
||||
if expr_sig(self.cx, f)
|
||||
.map(|sig| sig.input(i).skip_binder().peel_refs())
|
||||
.map_or(true, |ty| match *ty.kind() {
|
||||
if expr_sig(self.cx, f).and_then(|sig| sig.input(i)).map_or(true, |ty| {
|
||||
match *ty.skip_binder().peel_refs().kind() {
|
||||
ty::Param(_) => true,
|
||||
ty::Adt(def, _) => def.did() == args.ty_did,
|
||||
_ => false,
|
||||
})
|
||||
{
|
||||
}
|
||||
}) {
|
||||
// Passed to a function taking the non-dereferenced type.
|
||||
set_skip_flag();
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -87,7 +87,7 @@ impl RedundantStaticLifetimes {
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
self.visit_type(&*borrow_type.ty, cx, reason);
|
||||
self.visit_type(&borrow_type.ty, cx, reason);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::source::{snippet_opt, snippet_with_context};
|
||||
use clippy_utils::{fn_def_id, path_to_local_id};
|
||||
use if_chain::if_chain;
|
||||
@ -94,9 +94,10 @@ impl<'tcx> LateLintPass<'tcx> for Return {
|
||||
if !in_external_macro(cx.sess(), retexpr.span);
|
||||
if !local.span.from_expansion();
|
||||
then {
|
||||
span_lint_and_then(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
LET_AND_RETURN,
|
||||
retexpr.hir_id,
|
||||
retexpr.span,
|
||||
"returning the result of a `let` binding from a block",
|
||||
|err| {
|
||||
@ -185,6 +186,7 @@ fn check_final_expr<'tcx>(
|
||||
if !borrows {
|
||||
emit_return_lint(
|
||||
cx,
|
||||
inner.map_or(expr.hir_id, |inner| inner.hir_id),
|
||||
span.expect("`else return` is not possible"),
|
||||
inner.as_ref().map(|i| i.span),
|
||||
replacement,
|
||||
@ -220,50 +222,81 @@ fn check_final_expr<'tcx>(
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option<Span>, replacement: RetReplacement) {
|
||||
fn emit_return_lint(
|
||||
cx: &LateContext<'_>,
|
||||
emission_place: HirId,
|
||||
ret_span: Span,
|
||||
inner_span: Option<Span>,
|
||||
replacement: RetReplacement,
|
||||
) {
|
||||
if ret_span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
match inner_span {
|
||||
Some(inner_span) => {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
|
||||
let (snippet, _) = snippet_with_context(cx, inner_span, ret_span.ctxt(), "..", &mut applicability);
|
||||
diag.span_suggestion(ret_span, "remove `return`", snippet, applicability);
|
||||
});
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
NEEDLESS_RETURN,
|
||||
emission_place,
|
||||
ret_span,
|
||||
"unneeded `return` statement",
|
||||
|diag| {
|
||||
let (snippet, _) = snippet_with_context(cx, inner_span, ret_span.ctxt(), "..", &mut applicability);
|
||||
diag.span_suggestion(ret_span, "remove `return`", snippet, applicability);
|
||||
},
|
||||
);
|
||||
},
|
||||
None => match replacement {
|
||||
RetReplacement::Empty => {
|
||||
span_lint_and_sugg(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
NEEDLESS_RETURN,
|
||||
emission_place,
|
||||
ret_span,
|
||||
"unneeded `return` statement",
|
||||
"remove `return`",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
ret_span,
|
||||
"remove `return`",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
RetReplacement::Block => {
|
||||
span_lint_and_sugg(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
NEEDLESS_RETURN,
|
||||
emission_place,
|
||||
ret_span,
|
||||
"unneeded `return` statement",
|
||||
"replace `return` with an empty block",
|
||||
"{}".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
ret_span,
|
||||
"replace `return` with an empty block",
|
||||
"{}".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
RetReplacement::Unit => {
|
||||
span_lint_and_sugg(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
NEEDLESS_RETURN,
|
||||
emission_place,
|
||||
ret_span,
|
||||
"unneeded `return` statement",
|
||||
"replace `return` with a unit value",
|
||||
"()".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
ret_span,
|
||||
"replace `return` with a unit value",
|
||||
"()".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -26,6 +26,9 @@ declare_clippy_lint! {
|
||||
/// let mut vec1 = Vec::with_capacity(len);
|
||||
/// vec1.resize(len, 0);
|
||||
///
|
||||
/// let mut vec1 = Vec::with_capacity(len);
|
||||
/// vec1.resize(vec1.capacity(), 0);
|
||||
///
|
||||
/// let mut vec2 = Vec::with_capacity(len);
|
||||
/// vec2.extend(repeat(0).take(len));
|
||||
/// ```
|
||||
@ -211,23 +214,20 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
|
||||
|
||||
/// Checks if the given expression is resizing a vector with 0
|
||||
fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if self.initialization_found;
|
||||
if let ExprKind::MethodCall(path, [self_arg, len_arg, fill_arg], _) = expr.kind;
|
||||
if path_to_local_id(self_arg, self.vec_alloc.local_id);
|
||||
if path.ident.name == sym!(resize);
|
||||
|
||||
if self.initialization_found
|
||||
&& let ExprKind::MethodCall(path, [self_arg, len_arg, fill_arg], _) = expr.kind
|
||||
&& path_to_local_id(self_arg, self.vec_alloc.local_id)
|
||||
&& path.ident.name == sym!(resize)
|
||||
// Check that is filled with 0
|
||||
if let ExprKind::Lit(ref lit) = fill_arg.kind;
|
||||
if let LitKind::Int(0, _) = lit.node;
|
||||
|
||||
// Check that len expression is equals to `with_capacity` expression
|
||||
if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr);
|
||||
|
||||
then {
|
||||
self.slow_expression = Some(InitializationType::Resize(expr));
|
||||
&& let ExprKind::Lit(ref lit) = fill_arg.kind
|
||||
&& let LitKind::Int(0, _) = lit.node {
|
||||
// Check that len expression is equals to `with_capacity` expression
|
||||
if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) {
|
||||
self.slow_expression = Some(InitializationType::Resize(expr));
|
||||
} else if let ExprKind::MethodCall(path, _, _) = len_arg.kind && path.ident.as_str() == "capacity" {
|
||||
self.slow_expression = Some(InitializationType::Resize(expr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if give expression is `repeat(0).take(...)`
|
||||
@ -240,12 +240,15 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
|
||||
if let Some(repeat_expr) = take_args.get(0);
|
||||
if self.is_repeat_zero(repeat_expr);
|
||||
|
||||
// Check that len expression is equals to `with_capacity` expression
|
||||
if let Some(len_arg) = take_args.get(1);
|
||||
if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr);
|
||||
|
||||
then {
|
||||
return true;
|
||||
// Check that len expression is equals to `with_capacity` expression
|
||||
if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) {
|
||||
return true;
|
||||
} else if let ExprKind::MethodCall(path, _, _) = len_arg.kind && path.ident.as_str() == "capacity" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,12 @@ declare_clippy_lint! {
|
||||
/// let x = "Hello".to_owned();
|
||||
/// x + ", World";
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// let mut x = "Hello".to_owned();
|
||||
/// x.push_str(", World");
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub STRING_ADD,
|
||||
restriction,
|
||||
|
@ -16,9 +16,10 @@ mod wrong_transmute;
|
||||
|
||||
use clippy_utils::in_constant;
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_hir::{Expr, ExprKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
@ -385,7 +386,10 @@ declare_clippy_lint! {
|
||||
"transmute to or from a type with an undefined representation"
|
||||
}
|
||||
|
||||
declare_lint_pass!(Transmute => [
|
||||
pub struct Transmute {
|
||||
msrv: Option<RustcVersion>,
|
||||
}
|
||||
impl_lint_pass!(Transmute => [
|
||||
CROSSPOINTER_TRANSMUTE,
|
||||
TRANSMUTE_PTR_TO_REF,
|
||||
TRANSMUTE_PTR_TO_PTR,
|
||||
@ -401,13 +405,18 @@ declare_lint_pass!(Transmute => [
|
||||
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
|
||||
TRANSMUTE_UNDEFINED_REPR,
|
||||
]);
|
||||
|
||||
impl Transmute {
|
||||
#[must_use]
|
||||
pub fn new(msrv: Option<RustcVersion>) -> Self {
|
||||
Self { msrv }
|
||||
}
|
||||
}
|
||||
impl<'tcx> LateLintPass<'tcx> for Transmute {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(path_expr, [arg]) = e.kind;
|
||||
if let ExprKind::Path(ref qpath) = path_expr.kind;
|
||||
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id();
|
||||
if let ExprKind::Path(QPath::Resolved(None, path)) = path_expr.kind;
|
||||
if let Some(def_id) = path.res.opt_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::transmute, def_id);
|
||||
then {
|
||||
// Avoid suggesting non-const operations in const contexts:
|
||||
@ -427,7 +436,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
|
||||
|
||||
let linted = wrong_transmute::check(cx, e, from_ty, to_ty)
|
||||
| crosspointer_transmute::check(cx, e, from_ty, to_ty)
|
||||
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, qpath)
|
||||
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv)
|
||||
| transmute_int_to_char::check(cx, e, from_ty, to_ty, arg, const_context)
|
||||
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
|
||||
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg)
|
||||
@ -446,4 +455,6 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extract_msrv_attr!(LateContext);
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
use super::utils::get_type_snippet;
|
||||
use super::TRANSMUTE_PTR_TO_REF;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{meets_msrv, msrvs, sugg};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, Mutability, QPath};
|
||||
use rustc_hir::{self as hir, Expr, GenericArg, Mutability, Path, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_middle::ty::{self, Ty, TypeFoldable};
|
||||
use rustc_semver::RustcVersion;
|
||||
|
||||
/// Checks for `transmute_ptr_to_ref` lint.
|
||||
/// Returns `true` if it's triggered, otherwise returns `false`.
|
||||
@ -15,7 +16,8 @@ pub(super) fn check<'tcx>(
|
||||
from_ty: Ty<'tcx>,
|
||||
to_ty: Ty<'tcx>,
|
||||
arg: &'tcx Expr<'_>,
|
||||
qpath: &'tcx QPath<'_>,
|
||||
path: &'tcx Path<'_>,
|
||||
msrv: Option<RustcVersion>,
|
||||
) -> bool {
|
||||
match (&from_ty.kind(), &to_ty.kind()) {
|
||||
(ty::RawPtr(from_ptr_ty), ty::Ref(_, to_ref_ty, mutbl)) => {
|
||||
@ -34,19 +36,34 @@ pub(super) fn check<'tcx>(
|
||||
} else {
|
||||
("&*", "*const")
|
||||
};
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
|
||||
let arg = if from_ptr_ty.ty == *to_ref_ty {
|
||||
arg
|
||||
let sugg = if let Some(ty) = get_explicit_type(path) {
|
||||
let ty_snip = snippet_with_applicability(cx, ty.span, "..", &mut app);
|
||||
if meets_msrv(msrv, msrvs::POINTER_CAST) {
|
||||
format!("{}{}.cast::<{}>()", deref, arg.maybe_par(), ty_snip)
|
||||
} else if from_ptr_ty.has_erased_regions() {
|
||||
sugg::make_unop(deref, arg.as_ty(format!("{} () as {} {}", cast, cast, ty_snip)))
|
||||
.to_string()
|
||||
} else {
|
||||
sugg::make_unop(deref, arg.as_ty(format!("{} {}", cast, ty_snip))).to_string()
|
||||
}
|
||||
} else if from_ptr_ty.ty == *to_ref_ty {
|
||||
if from_ptr_ty.has_erased_regions() {
|
||||
if meets_msrv(msrv, msrvs::POINTER_CAST) {
|
||||
format!("{}{}.cast::<{}>()", deref, arg.maybe_par(), to_ref_ty)
|
||||
} else {
|
||||
sugg::make_unop(deref, arg.as_ty(format!("{} () as {} {}", cast, cast, to_ref_ty)))
|
||||
.to_string()
|
||||
}
|
||||
} else {
|
||||
sugg::make_unop(deref, arg).to_string()
|
||||
}
|
||||
} else {
|
||||
arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, *to_ref_ty)))
|
||||
sugg::make_unop(deref, arg.as_ty(format!("{} {}", cast, to_ref_ty))).to_string()
|
||||
};
|
||||
|
||||
diag.span_suggestion(
|
||||
e.span,
|
||||
"try",
|
||||
sugg::make_unop(deref, arg).to_string(),
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
diag.span_suggestion(e.span, "try", sugg, app);
|
||||
},
|
||||
);
|
||||
true
|
||||
@ -54,3 +71,14 @@ pub(super) fn check<'tcx>(
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the type `Bar` in `…::transmute<Foo, &Bar>`.
|
||||
fn get_explicit_type<'tcx>(path: &'tcx Path<'tcx>) -> Option<&'tcx hir::Ty<'tcx>> {
|
||||
if let GenericArg::Type(ty) = path.segments.last()?.args?.args.get(1)?
|
||||
&& let TyKind::Rptr(_, ty) = &ty.kind
|
||||
{
|
||||
Some(ty.ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,9 @@
|
||||
use clippy_utils::last_path_segment;
|
||||
use clippy_utils::source::snippet;
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::{Expr, GenericArg, QPath, TyKind};
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{cast::CastKind, Ty};
|
||||
use rustc_span::DUMMY_SP;
|
||||
use rustc_typeck::check::{cast::CastCheck, FnCtxt, Inherited};
|
||||
|
||||
/// Gets the snippet of `Bar` in `…::transmute<Foo, &Bar>`. If that snippet is
|
||||
/// not available , use
|
||||
/// the type's `ToString` implementation. In weird cases it could lead to types
|
||||
/// with invalid `'_`
|
||||
/// lifetime, but it should be rare.
|
||||
pub(super) fn get_type_snippet(cx: &LateContext<'_>, path: &QPath<'_>, to_ref_ty: Ty<'_>) -> String {
|
||||
let seg = last_path_segment(path);
|
||||
if_chain! {
|
||||
if let Some(params) = seg.args;
|
||||
if !params.parenthesized;
|
||||
if let Some(to_ty) = params.args.iter().filter_map(|arg| match arg {
|
||||
GenericArg::Type(ty) => Some(ty),
|
||||
_ => None,
|
||||
}).nth(1);
|
||||
if let TyKind::Rptr(_, ref to_ty) = to_ty.kind;
|
||||
then {
|
||||
return snippet(cx, to_ty.ty.span, &to_ref_ty.to_string()).to_string();
|
||||
}
|
||||
}
|
||||
|
||||
to_ref_ty.to_string()
|
||||
}
|
||||
|
||||
// check if the component types of the transmuted collection and the result have different ABI,
|
||||
// size or alignment
|
||||
pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
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_middle::hir::nested_filter;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
@ -68,20 +68,18 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
|
||||
span: Span,
|
||||
hir_id: HirId,
|
||||
) {
|
||||
if let FnKind::ItemFn(_, _, FnHeader { asyncness, .. }) = &fn_kind {
|
||||
if matches!(asyncness, IsAsync::Async) {
|
||||
let mut visitor = AsyncFnVisitor { cx, found_await: false };
|
||||
walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), span, hir_id);
|
||||
if !visitor.found_await {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
UNUSED_ASYNC,
|
||||
span,
|
||||
"unused `async` for function with no await statements",
|
||||
None,
|
||||
"consider removing the `async` from this function",
|
||||
);
|
||||
}
|
||||
if !span.from_expansion() && fn_kind.asyncness() == IsAsync::Async {
|
||||
let mut visitor = AsyncFnVisitor { cx, found_await: false };
|
||||
walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), span, hir_id);
|
||||
if !visitor.found_await {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
UNUSED_ASYNC,
|
||||
span,
|
||||
"unused `async` for function with no await statements",
|
||||
None,
|
||||
"consider removing the `async` from this function",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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::ty::is_type_diagnostic_item;
|
||||
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()
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
span_lint_hir_and_then(
|
||||
self.cx,
|
||||
UNNECESSARY_UNWRAP,
|
||||
expr.hir_id,
|
||||
expr.span,
|
||||
&format!(
|
||||
"called `{}` on `{}` after checking its variant with `{}`",
|
||||
@ -283,9 +284,10 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
|
||||
},
|
||||
);
|
||||
} else {
|
||||
span_lint_and_then(
|
||||
span_lint_hir_and_then(
|
||||
self.cx,
|
||||
PANICKING_UNWRAP,
|
||||
expr.hir_id,
|
||||
expr.span,
|
||||
&format!("this call to `{}()` will always panic",
|
||||
method_name.ident.name),
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::utils::internal_lints::metadata_collector::is_deprecated_lint;
|
||||
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::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"
|
||||
}
|
||||
|
||||
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]);
|
||||
|
||||
impl EarlyLintPass for ClippyLintsInternal {
|
||||
@ -375,42 +416,67 @@ pub struct LintWithoutLintPass {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let expr = &cx.tcx.hir().body(body_id).value;
|
||||
if_chain! {
|
||||
if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind;
|
||||
if let ExprKind::Struct(_, fields, _) = inner_exp.kind;
|
||||
let field = fields
|
||||
.iter()
|
||||
.find(|f| f.ident.as_str() == "desc")
|
||||
.expect("lints must have a description field");
|
||||
if let ExprKind::Lit(Spanned {
|
||||
node: LitKind::Str(ref sym, _),
|
||||
..
|
||||
}) = field.expr.kind;
|
||||
if sym.as_str() == "default lint description";
|
||||
let fields;
|
||||
if is_lint_ref_ty {
|
||||
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;
|
||||
}
|
||||
|
||||
then {
|
||||
let field = fields
|
||||
.iter()
|
||||
.find(|f| f.ident.as_str() == "desc")
|
||||
.expect("lints must have a description field");
|
||||
|
||||
if let ExprKind::Lit(Spanned {
|
||||
node: LitKind::Str(ref sym, _),
|
||||
..
|
||||
}) = field.expr.kind
|
||||
{
|
||||
let sym_str = sym.as_str();
|
||||
if is_lint_ref_ty {
|
||||
if sym_str == "default lint description" {
|
||||
span_lint(
|
||||
cx,
|
||||
DEFAULT_LINT,
|
||||
item.span,
|
||||
&format!("the lint `{}` has the default lint description", item.ident.name),
|
||||
);
|
||||
}
|
||||
|
||||
self.declared_lints.insert(item.ident.name, item.span);
|
||||
} else if sym_str == "default deprecation note" {
|
||||
span_lint(
|
||||
cx,
|
||||
DEFAULT_LINT,
|
||||
DEFAULT_DEPRECATION_REASON,
|
||||
item.span,
|
||||
&format!("the lint `{}` has the default lint description", item.ident.name),
|
||||
&format!("the lint `{}` has the default deprecation reason", item.ident.name),
|
||||
);
|
||||
}
|
||||
}
|
||||
self.declared_lints.insert(item.ident.name, item.span);
|
||||
}
|
||||
} else if let Some(macro_call) = root_macro_call_first_node(cx, item) {
|
||||
if !matches!(
|
||||
@ -668,6 +734,7 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
|
||||
let body = cx.tcx.hir().body(*body);
|
||||
let only_expr = peel_blocks_with_stmt(&body.value);
|
||||
if let ExprKind::MethodCall(ps, span_call_args, _) = &only_expr.kind;
|
||||
if let ExprKind::Path(..) = span_call_args[0].kind;
|
||||
then {
|
||||
let and_then_snippets = get_and_then_snippets(cx, and_then_args);
|
||||
let mut sle = SpanlessEq::new(cx).deny_side_effects();
|
||||
|
@ -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_and_help"],
|
||||
&["clippy_utils", "diagnostics", "span_lint_and_note"],
|
||||
@ -190,7 +190,12 @@ impl MetadataCollector {
|
||||
lints: BinaryHeap::<LintMetadata>::default(),
|
||||
applicability_info: FxHashMap::<String, ApplicabilityInfo>::default(),
|
||||
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))
|
||||
}
|
||||
|
||||
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::def::Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, ty.hir_id) {
|
||||
return match_def_path(cx, def_id, &DEPRECATED_LINT_TYPE);
|
||||
|
@ -20,6 +20,11 @@ declare_clippy_lint! {
|
||||
/// ```rust
|
||||
/// vec!(1, 2, 3, 4, 5).resize(0, 5)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// vec!(1, 2, 3, 4, 5).clear()
|
||||
/// ```
|
||||
#[clippy::version = "1.46.0"]
|
||||
pub VEC_RESIZE_TO_ZERO,
|
||||
correctness,
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clippy_utils"
|
||||
version = "0.1.63"
|
||||
version = "0.1.64"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user