diff --git a/Cargo.lock b/Cargo.lock index c0905982467df..ba0098a977534 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -638,7 +638,7 @@ dependencies = [ [[package]] name = "clippy" -version = "0.1.63" +version = "0.1.64" dependencies = [ "clippy_lints", "clippy_utils", @@ -661,6 +661,7 @@ dependencies = [ "termize", "tester", "tokio", + "toml", ] [[package]] @@ -679,10 +680,9 @@ dependencies = [ [[package]] name = "clippy_lints" -version = "0.1.63" +version = "0.1.64" dependencies = [ "cargo_metadata", - "clippy_dev", "clippy_utils", "if_chain", "itertools", @@ -702,7 +702,7 @@ dependencies = [ [[package]] name = "clippy_utils" -version = "0.1.63" +version = "0.1.64" dependencies = [ "arrayvec", "if_chain", diff --git a/src/tools/clippy/.github/workflows/remark.yml b/src/tools/clippy/.github/workflows/remark.yml index ff471207b65a3..81ef072bbb07f 100644 --- a/src/tools/clippy/.github/workflows/remark.yml +++ b/src/tools/clippy/.github/workflows/remark.yml @@ -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 diff --git a/src/tools/clippy/CHANGELOG.md b/src/tools/clippy/CHANGELOG.md index 6aaf12ed9324a..9bc93c1cb42c9 100644 --- a/src/tools/clippy/CHANGELOG.md +++ b/src/tools/clippy/CHANGELOG.md @@ -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 diff --git a/src/tools/clippy/Cargo.toml b/src/tools/clippy/Cargo.toml index e4060ce29a7b5..644ca6318f625 100644 --- a/src/tools/clippy/Cargo.toml +++ b/src/tools/clippy/Cargo.toml @@ -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" diff --git a/src/tools/clippy/README.md b/src/tools/clippy/README.md index edbc626e354b5..2c3defeaa8307 100644 --- a/src/tools/clippy/README.md +++ b/src/tools/clippy/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 diff --git a/src/tools/clippy/book/src/README.md b/src/tools/clippy/book/src/README.md index de1f70d7e9640..d941f8b65e8ee 100644 --- a/src/tools/clippy/book/src/README.md +++ b/src/tools/clippy/book/src/README.md @@ -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 diff --git a/src/tools/clippy/clippy_dev/Cargo.toml b/src/tools/clippy/clippy_dev/Cargo.toml index b0d470a2124d8..2ac3b4fe2ed4f 100644 --- a/src/tools/clippy/clippy_dev/Cargo.toml +++ b/src/tools/clippy/clippy_dev/Cargo.toml @@ -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" diff --git a/src/tools/clippy/clippy_dev/src/main.rs b/src/tools/clippy/clippy_dev/src/main.rs index 2c27a0bcaf95e..243a901503f16 100644 --- a/src/tools/clippy/clippy_dev/src/main.rs +++ b/src/tools/clippy/clippy_dev/src/main.rs @@ -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::("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() } diff --git a/src/tools/clippy/clippy_dev/src/new_lint.rs b/src/tools/clippy/clippy_dev/src/new_lint.rs index 748d73c080126..7d7e760ef446c 100644 --- a/src/tools/clippy/clippy_dev/src/new_lint.rs +++ b/src/tools/clippy/clippy_dev/src/new_lint.rs @@ -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 { let version = contents .lines() diff --git a/src/tools/clippy/clippy_dev/src/update_lints.rs b/src/tools/clippy/clippy_dev/src/update_lints.rs index 1bbd9a45b619e..2e0659f42d7b6 100644 --- a/src/tools/clippy/clippy_dev/src/update_lints.rs +++ b/src/tools/clippy/clippy_dev/src/update_lints.rs @@ -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, Vec, Vec), + 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) -> io::Result { + fn remove_lint(name: &str, lints: &mut Vec) { + 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 { @@ -393,16 +588,18 @@ struct Lint { group: String, desc: String, module: String, + declaration_range: Range, } 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) -> 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, } impl DeprecatedLint { - fn new(name: &str, reason: &str) -> Self { + fn new(name: &str, reason: &str, declaration_range: Range) -> Self { Self { name: name.to_lowercase(), reason: remove_line_splices(reason), + declaration_range, } } } @@ -610,7 +809,11 @@ fn clippy_lints_src_files() -> impl Iterator { 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, +} + /// Parse a source file looking for `declare_clippy_lint` macro invocations. fn parse_contents(contents: &str, module: &str, lints: &mut Vec) { 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) { 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) { 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) { 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> = 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() + &[ diff --git a/src/tools/clippy/clippy_lints/Cargo.toml b/src/tools/clippy/clippy_lints/Cargo.toml index 4d5bf47833fb8..79a56dc405d17 100644 --- a/src/tools/clippy/clippy_lints/Cargo.toml +++ b/src/tools/clippy/clippy_lints/Cargo.toml @@ -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)] diff --git a/src/tools/clippy/clippy_lints/src/assign_ops.rs b/src/tools/clippy/clippy_lints/src/assign_ops.rs deleted file mode 100644 index f81da2d422333..0000000000000 --- a/src/tools/clippy/clippy_lints/src/assign_ops.rs +++ /dev/null @@ -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); - } -} diff --git a/src/tools/clippy/clippy_lints/src/attrs.rs b/src/tools/clippy/clippy_lints/src/attrs.rs index ed12ad9c36790..4bcbeacf9feb5 100644 --- a/src/tools/clippy/clippy_lints/src/attrs.rs +++ b/src/tools/clippy/clippy_lints/src/attrs.rs @@ -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", ) }) { diff --git a/src/tools/clippy/clippy_lints/src/booleans.rs b/src/tools/clippy/clippy_lints/src/booleans.rs index e4e122ba6eb59..526ee2f891a16 100644 --- a/src/tools/clippy/clippy_lints/src/booleans.rs +++ b/src/tools/clippy/clippy_lints/src/booleans.rs @@ -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| { diff --git a/src/tools/clippy/clippy_lints/src/default_instead_of_iter_empty.rs b/src/tools/clippy/clippy_lints/src/default_instead_of_iter_empty.rs new file mode 100644 index 0000000000000..3c996d3d2aeee --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/default_instead_of_iter_empty.rs @@ -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::::default(); + /// let iter: std::iter::Empty = std::iter::Empty::default(); + /// ``` + /// Use instead: + /// ```rust + /// let _ = std::iter::empty::(); + /// let iter: std::iter::Empty = 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() + } +} diff --git a/src/tools/clippy/clippy_lints/src/deprecated_lints.rs b/src/tools/clippy/clippy_lints/src/deprecated_lints.rs index 5d5ea0f49c8c8..9aa5af3190fb2 100644 --- a/src/tools/clippy/clippy_lints/src/deprecated_lints.rs +++ b/src/tools/clippy/clippy_lints/src/deprecated_lints.rs @@ -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 + }; } } diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs index b47441eff3748..59dcc1ebf191f 100644 --- a/src/tools/clippy/clippy_lints/src/dereference.rs +++ b/src/tools/clippy/clippy_lints/src/dereference.rs @@ -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>, 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), } +impl Position { + fn is_deref_stable(self) -> bool { + matches!(self, Self::DerefStable(_)) + } -/// 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>) -> bool { - match parent { - Some(Node::Expr(parent)) => matches!(parent.kind, ExprKind::MethodCall(..) | ExprKind::Call(..)), - Some(Node::Local(_)) => true, - _ => false, + fn is_reborrow_stable(self) -> bool { + matches!(self, Self::DerefStable(_) | Self::ReborrowStable(_)) } -} -/// Checks if the given expression is a position which can auto-borrow. -fn is_auto_borrow_position(parent: Option>, 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, - } - } else { - false + fn can_auto_borrow(self) -> bool { + matches!(self, Self::MethodReceiver | Self::FieldAccess(_) | Self::Callee) } -} -/// 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; + fn lint_explicit_deref(self) -> bool { + matches!(self, Self::Other(_) | Self::DerefStable(_) | Self::ReborrowStable(_)) + } - loop { - match typeck.expr_adjustments(prev) { - [] => (), - a => break a, - }; + 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, + } + } +} - match iter.next().map(|(_, x)| x) { - Some(Node::Block(_)) => { - if let Some((_, Node::Expr(e))) = iter.next() { - prev = e; +/// 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 { - // This shouldn't happen. Blocks are always contained in an expression. - break &[]; - } + Position::Other(precedence) + }) }, - Some(Node::Expr(&Expr { - kind: ExprKind::Break(Destination { target_id: Ok(id), .. }, _), + + 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 let Some(Node::Expr(e)) = map.find(id) { - prev = e; - iter = map.parent_iter(id); + }) 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 { - // This shouldn't happen. The destination should exist. - break &[]; + Position::DerefStable(precedence) } }, - _ => break &[], + 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 } } -#[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 { .. } => (), } } diff --git a/src/tools/clippy/clippy_lints/src/double_comparison.rs b/src/tools/clippy/clippy_lints/src/double_comparison.rs deleted file mode 100644 index ee0440e52ff85..0000000000000 --- a/src/tools/clippy/clippy_lints/src/double_comparison.rs +++ /dev/null @@ -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); - } - } -} diff --git a/src/tools/clippy/clippy_lints/src/duration_subsec.rs b/src/tools/clippy/clippy_lints/src/duration_subsec.rs deleted file mode 100644 index d85ace3a279b3..0000000000000 --- a/src/tools/clippy/clippy_lints/src/duration_subsec.rs +++ /dev/null @@ -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, - ); - } - } - } -} diff --git a/src/tools/clippy/clippy_lints/src/enum_variants.rs b/src/tools/clippy/clippy_lints/src/enum_variants.rs index 23b7510457091..cd36f9fcd729e 100644 --- a/src/tools/clippy/clippy_lints/src/enum_variants.rs +++ b/src/tools/clippy/clippy_lints/src/enum_variants.rs @@ -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(); diff --git a/src/tools/clippy/clippy_lints/src/eq_op.rs b/src/tools/clippy/clippy_lints/src/eq_op.rs deleted file mode 100644 index 2f4c90d07cf66..0000000000000 --- a/src/tools/clippy/clippy_lints/src/eq_op.rs +++ /dev/null @@ -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 - } - } -} diff --git a/src/tools/clippy/clippy_lints/src/erasing_op.rs b/src/tools/clippy/clippy_lints/src/erasing_op.rs deleted file mode 100644 index c1a84973c4211..0000000000000 --- a/src/tools/clippy/clippy_lints/src/erasing_op.rs +++ /dev/null @@ -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", - ); - } -} diff --git a/src/tools/clippy/clippy_lints/src/escape.rs b/src/tools/clippy/clippy_lints/src/escape.rs index 9d21dd71e0e8d..7a65b849a6643 100644 --- a/src/tools/clippy/clippy_lints/src/escape.rs +++ b/src/tools/clippy/clippy_lints/src/escape.rs @@ -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", ); diff --git a/src/tools/clippy/clippy_lints/src/float_equality_without_abs.rs b/src/tools/clippy/clippy_lints/src/float_equality_without_abs.rs deleted file mode 100644 index 98aee7592ae80..0000000000000 --- a/src/tools/clippy/clippy_lints/src/float_equality_without_abs.rs +++ /dev/null @@ -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, - ); - } - ); - } - } - } -} diff --git a/src/tools/clippy/clippy_lints/src/implicit_return.rs b/src/tools/clippy/clippy_lints/src/implicit_return.rs index 647947d5d30d6..a6610ade37e56 100644 --- a/src/tools/clippy/clippy_lints/src/implicit_return.rs +++ b/src/tools/clippy/clippy_lints/src/implicit_return.rs @@ -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 } }, diff --git a/src/tools/clippy/clippy_lints/src/integer_division.rs b/src/tools/clippy/clippy_lints/src/integer_division.rs deleted file mode 100644 index 3effba5682607..0000000000000 --- a/src/tools/clippy/clippy_lints/src/integer_division.rs +++ /dev/null @@ -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 -} diff --git a/src/tools/clippy/clippy_lints/src/large_const_arrays.rs b/src/tools/clippy/clippy_lints/src/large_const_arrays.rs index 289755bfec66f..984c5cd4e37c2 100644 --- a/src/tools/clippy/clippy_lints/src/large_const_arrays.rs +++ b/src/tools/clippy/clippy_lints/src/large_const_arrays.rs @@ -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"] diff --git a/src/tools/clippy/clippy_lints/src/let_underscore.rs b/src/tools/clippy/clippy_lints/src/let_underscore.rs index 26c540e2223b1..176787497ebf2 100644 --- a/src/tools/clippy/clippy_lints/src/let_underscore.rs +++ b/src/tools/clippy/clippy_lints/src/let_underscore.rs @@ -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 { diff --git a/src/tools/clippy/clippy_lints/src/lib.register_all.rs b/src/tools/clippy/clippy_lints/src/lib.register_all.rs index 8a2cfbff953ec..563ad891603a7 100644 --- a/src/tools/clippy/clippy_lints/src/lib.register_all.rs +++ b/src/tools/clippy/clippy_lints/src/lib.register_all.rs @@ -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), diff --git a/src/tools/clippy/clippy_lints/src/lib.register_complexity.rs b/src/tools/clippy/clippy_lints/src/lib.register_complexity.rs index 4f1c3673f853c..3784d3c68dcee 100644 --- a/src/tools/clippy/clippy_lints/src/lib.register_complexity.rs +++ b/src/tools/clippy/clippy_lints/src/lib.register_complexity.rs @@ -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), diff --git a/src/tools/clippy/clippy_lints/src/lib.register_correctness.rs b/src/tools/clippy/clippy_lints/src/lib.register_correctness.rs index 92a3a0aabf1ca..7d5e65cb27a1f 100644 --- a/src/tools/clippy/clippy_lints/src/lib.register_correctness.rs +++ b/src/tools/clippy/clippy_lints/src/lib.register_correctness.rs @@ -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), diff --git a/src/tools/clippy/clippy_lints/src/lib.register_internal.rs b/src/tools/clippy/clippy_lints/src/lib.register_internal.rs index 4778f4fdfa76c..be63646a12f51 100644 --- a/src/tools/clippy/clippy_lints/src/lib.register_internal.rs +++ b/src/tools/clippy/clippy_lints/src/lib.register_internal.rs @@ -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), diff --git a/src/tools/clippy/clippy_lints/src/lib.register_lints.rs b/src/tools/clippy/clippy_lints/src/lib.register_lints.rs index 8ad984c68b8ec..d3c75f8b51910 100644 --- a/src/tools/clippy/clippy_lints/src/lib.register_lints.rs +++ b/src/tools/clippy/clippy_lints/src/lib.register_lints.rs @@ -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, diff --git a/src/tools/clippy/clippy_lints/src/lib.register_pedantic.rs b/src/tools/clippy/clippy_lints/src/lib.register_pedantic.rs index 48de92ae94523..a1b5466581491 100644 --- a/src/tools/clippy/clippy_lints/src/lib.register_pedantic.rs +++ b/src/tools/clippy/clippy_lints/src/lib.register_pedantic.rs @@ -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), diff --git a/src/tools/clippy/clippy_lints/src/lib.register_perf.rs b/src/tools/clippy/clippy_lints/src/lib.register_perf.rs index 82431863e6cfd..6bf519c24e848 100644 --- a/src/tools/clippy/clippy_lints/src/lib.register_perf.rs +++ b/src/tools/clippy/clippy_lints/src/lib.register_perf.rs @@ -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), diff --git a/src/tools/clippy/clippy_lints/src/lib.register_restriction.rs b/src/tools/clippy/clippy_lints/src/lib.register_restriction.rs index 3695012f55238..970e9db4772cb 100644 --- a/src/tools/clippy/clippy_lints/src/lib.register_restriction.rs +++ b/src/tools/clippy/clippy_lints/src/lib.register_restriction.rs @@ -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), diff --git a/src/tools/clippy/clippy_lints/src/lib.register_style.rs b/src/tools/clippy/clippy_lints/src/lib.register_style.rs index b6992ae0ad25f..15a1bc569af23 100644 --- a/src/tools/clippy/clippy_lints/src/lib.register_style.rs +++ b/src/tools/clippy/clippy_lints/src/lib.register_style.rs @@ -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), diff --git a/src/tools/clippy/clippy_lints/src/lib.register_suspicious.rs b/src/tools/clippy/clippy_lints/src/lib.register_suspicious.rs index 7b13713c36e59..f7558f8709810 100644 --- a/src/tools/clippy/clippy_lints/src/lib.register_suspicious.rs +++ b/src/tools/clippy/clippy_lints/src/lib.register_suspicious.rs @@ -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), diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 84898eae05ac4..172fdf8c85269 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -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 { + 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` } diff --git a/src/tools/clippy/clippy_lints/src/lifetimes.rs b/src/tools/clippy/clippy_lints/src/lifetimes.rs index 93f5663312f2e..5c0bd57ac5097 100644 --- a/src/tools/clippy/clippy_lints/src/lifetimes.rs +++ b/src/tools/clippy/clippy_lints/src/lifetimes.rs @@ -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_); + } } } diff --git a/src/tools/clippy/clippy_lints/src/loops/manual_find.rs b/src/tools/clippy/clippy_lints/src/loops/manual_find.rs new file mode 100644 index 0000000000000..33736d6d4e650 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/loops/manual_find.rs @@ -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 { + 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 + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/loops/mod.rs b/src/tools/clippy/clippy_lints/src/loops/mod.rs index 391de922e1e1a..ed270bd490d78 100644 --- a/src/tools/clippy/clippy_lints/src/loops/mod.rs +++ b/src/tools/clippy/clippy_lints/src/loops/mod.rs @@ -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) -> Option { + /// for el in arr { + /// if el == 1 { + /// return Some(el); + /// } + /// } + /// None + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn example(arr: Vec) -> Option { + /// 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<'_>) { diff --git a/src/tools/clippy/clippy_lints/src/loops/while_let_loop.rs b/src/tools/clippy/clippy_lints/src/loops/while_let_loop.rs index 8f57df0be6bd4..45af6be2653f3 100644 --- a/src/tools/clippy/clippy_lints/src/loops/while_let_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/while_let_loop.rs @@ -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); - } - } - } -} + }, + ([], Some(e)) => (e, false), + _ => return, + }; -/// 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; - } + 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); } - 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, - } +/// 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()) } -/// 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, +/// 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; } diff --git a/src/tools/clippy/clippy_lints/src/macro_use.rs b/src/tools/clippy/clippy_lints/src/macro_use.rs index 753469c603d40..d573a1b4fbb5d 100644 --- a/src/tools/clippy/clippy_lints/src/macro_use.rs +++ b/src/tools/clippy/clippy_lints/src/macro_use.rs @@ -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, mac_refs: Vec, @@ -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::>(); - 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, + ); + }, ); } } diff --git a/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs index 14f5faafd7cb9..4278e98dc91fa 100644 --- a/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs +++ b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs @@ -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| { diff --git a/src/tools/clippy/clippy_lints/src/manual_rem_euclid.rs b/src/tools/clippy/clippy_lints/src/manual_rem_euclid.rs new file mode 100644 index 0000000000000..b5698965fc3dd --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/manual_rem_euclid.rs @@ -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, +} + +impl ManualRemEuclid { + #[must_use] + pub fn new(msrv: Option) -> 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 { + 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), + } +} diff --git a/src/tools/clippy/clippy_lints/src/manual_retain.rs b/src/tools/clippy/clippy_lints/src/manual_retain.rs new file mode 100644 index 0000000000000..c35e1e021ef1c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/manual_retain.rs @@ -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); 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, +} + +impl ManualRetain { + #[must_use] + pub fn new(msrv: Option) -> 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, +) { + 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, +) { + 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, +) { + 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 { + 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) -> 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)) + }) +} diff --git a/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs b/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs index 16fefea552019..15513de7d860d 100644 --- a/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs +++ b/src/tools/clippy/clippy_lints/src/matches/match_same_arms.rs @@ -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), diff --git a/src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs b/src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs index 9df2db45dcf83..5ae4a65acaf33 100644 --- a/src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs +++ b/src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs @@ -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, ); diff --git a/src/tools/clippy/clippy_lints/src/matches/match_str_case_mismatch.rs b/src/tools/clippy/clippy_lints/src/matches/match_str_case_mismatch.rs index 8302ce426e570..fa3b8d1fceaac 100644 --- a/src/tools/clippy/clippy_lints/src/matches/match_str_case_mismatch.rs +++ b/src/tools/clippy/clippy_lints/src/matches/match_str_case_mismatch.rs @@ -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, ); diff --git a/src/tools/clippy/clippy_lints/src/matches/mod.rs b/src/tools/clippy/clippy_lints/src/matches/mod.rs index 3e765173fb9f3..b2a873ef5823c 100644 --- a/src/tools/clippy/clippy_lints/src/matches/mod.rs +++ b/src/tools/clippy/clippy_lints/src/matches/mod.rs @@ -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); diff --git a/src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs b/src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs index 0ea3f3b673b7d..8499e050af242 100644 --- a/src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs @@ -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> { } } -// 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; }; diff --git a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index dcaf6f865de36..0704a5af52595 100644 --- a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -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>, + 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>, has_significant_drop: bool, current_sig_drop: Option, sig_drop_spans: Option>, 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, +} + +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::::default(), + } + } +} + +fn has_significant_drop_in_arms<'tcx, 'a>(cx: &'a LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> FxHashSet { + 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); + } +} diff --git a/src/tools/clippy/clippy_lints/src/matches/single_match.rs b/src/tools/clippy/clippy_lints/src/matches/single_match.rs index 0c4cb45d147ca..92091a0c3395f 100644 --- a/src/tools/clippy/clippy_lints/src/matches/single_match.rs +++ b/src/tools/clippy/clippy_lints/src/matches/single_match.rs @@ -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; + // 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); } +} +/// 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(); - if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) { - return; - } + collect_pat_paths(&mut paths_and_types, cx, pat, ty); + paths_and_types.iter().all(|ty| in_candidate_enum(cx, *ty)) +} - 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; - } +/// 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 - }; - if paths_and_types.iter().all(in_candidate_enum) { - report_single_pattern(cx, ex, arms, expr, els); } + false } -/// 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 { +/// Collects types from the given pattern +fn collect_pat_paths<'a>(acc: &mut Vec>, 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::Binding(BindingAnnotation::Unannotated, .., ident, None) => { - acc.push((ident.to_string(), ty)); - true + PatKind::TupleStruct(..) | PatKind::Binding(BindingAnnotation::Unannotated, .., None) | PatKind::Path(_) => { + acc.push(ty); }, - 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, } } diff --git a/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs b/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs index 8ae84dbb3dcf4..13853dec99de8 100644 --- a/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs +++ b/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs @@ -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, ); diff --git a/src/tools/clippy/clippy_lints/src/misc.rs b/src/tools/clippy/clippy_lints/src/misc.rs index 01bf871198a5c..df2430ced6b62 100644 --- a/src/tools/clippy/clippy_lints/src/misc.rs +++ b/src/tools/clippy/clippy_lints/src/misc.rs @@ -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 { - 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, - 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", - ); - } - }; - } -} diff --git a/src/tools/clippy/clippy_lints/src/needless_bitwise_bool.rs b/src/tools/clippy/clippy_lints/src/needless_bitwise_bool.rs deleted file mode 100644 index 623d22bc9bdfe..0000000000000 --- a/src/tools/clippy/clippy_lints/src/needless_bitwise_bool.rs +++ /dev/null @@ -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 { - 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); - } - }, - ); - } - } -} diff --git a/src/tools/clippy/clippy_lints/src/neg_multiply.rs b/src/tools/clippy/clippy_lints/src/neg_multiply.rs index ce6bb38b7c0e9..b087cfb36b116 100644 --- a/src/tools/clippy/clippy_lints/src/neg_multiply.rs +++ b/src/tools/clippy/clippy_lints/src/neg_multiply.rs @@ -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, diff --git a/src/tools/clippy/clippy_lints/src/non_copy_const.rs b/src/tools/clippy/clippy_lints/src/non_copy_const.rs index 1727275a4e06a..a1ef32ae60805 100644 --- a/src/tools/clippy/clippy_lints/src/non_copy_const.rs +++ b/src/tools/clippy/clippy_lints/src/non_copy_const.rs @@ -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 }); } } diff --git a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs index 7f6b535c7b16c..b96af06b8d7c6 100644 --- a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs +++ b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs @@ -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![]); diff --git a/src/tools/clippy/clippy_lints/src/numeric_arithmetic.rs b/src/tools/clippy/clippy_lints/src/numeric_arithmetic.rs deleted file mode 100644 index 5c4de3381496c..0000000000000 --- a/src/tools/clippy/clippy_lints/src/numeric_arithmetic.rs +++ /dev/null @@ -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, - /// This field is used to check whether expressions are constants, such as in enum discriminants - /// and consts - const_span: Option, -} - -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; - } -} diff --git a/src/tools/clippy/clippy_lints/src/absurd_extreme_comparisons.rs b/src/tools/clippy/clippy_lints/src/operators/absurd_extreme_comparisons.rs similarity index 53% rename from src/tools/clippy/clippy_lints/src/absurd_extreme_comparisons.rs rename to src/tools/clippy/clippy_lints/src/operators/absurd_extreme_comparisons.rs index 7665aa8380b3c..1ec4240afefe5 100644 --- a/src/tools/clippy/clippy_lints/src/absurd_extreme_comparisons.rs +++ b/src/tools/clippy/clippy_lints/src/operators/absurd_extreme_comparisons.rs @@ -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 = 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]); - -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 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); - } - } - } +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"; + + 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 + ); + + span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help); } } diff --git a/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs b/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs new file mode 100644 index 0000000000000..979e0a66707dd --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs @@ -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); + } +} diff --git a/src/tools/clippy/clippy_lints/src/bit_mask.rs b/src/tools/clippy/clippy_lints/src/operators/bit_mask.rs similarity index 51% rename from src/tools/clippy/clippy_lints/src/bit_mask.rs rename to src/tools/clippy/clippy_lints/src/operators/bit_mask.rs index dc7e400fdc281..74387fbc87be0 100644 --- a/src/tools/clippy/clippy_lints/src/bit_mask.rs +++ b/src/tools/clippy/clippy_lints/src/operators/bit_mask.rs @@ -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 `_ m - /// c` (where `` is one of {`&`, `|`} and `` 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" -} +use super::{BAD_BIT_MASK, INEFFECTIVE_BIT_MASK}; -#[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); } } } diff --git a/src/tools/clippy/clippy_lints/src/operators/cmp_nan.rs b/src/tools/clippy/clippy_lints/src/operators/cmp_nan.rs new file mode 100644 index 0000000000000..786ae1552ad3d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/cmp_nan.rs @@ -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 + } +} diff --git a/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs b/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs new file mode 100644 index 0000000000000..e1f9b5906f667 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs @@ -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 { + 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 + ); + }, + ); +} diff --git a/src/tools/clippy/clippy_lints/src/operators/double_comparison.rs b/src/tools/clippy/clippy_lints/src/operators/double_comparison.rs new file mode 100644 index 0000000000000..56a86d0ffa212 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/double_comparison.rs @@ -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!(==); + }, + _ => (), + }; +} diff --git a/src/tools/clippy/clippy_lints/src/operators/duration_subsec.rs b/src/tools/clippy/clippy_lints/src/operators/duration_subsec.rs new file mode 100644 index 0000000000000..0d067d1e1968c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/duration_subsec.rs @@ -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, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/operators/eq_op.rs b/src/tools/clippy/clippy_lints/src/operators/eq_op.rs new file mode 100644 index 0000000000000..44cf0bb06120a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/eq_op.rs @@ -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()), + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/operators/erasing_op.rs b/src/tools/clippy/clippy_lints/src/operators/erasing_op.rs new file mode 100644 index 0000000000000..066e08f3bd4ca --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/erasing_op.rs @@ -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", + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/operators/float_cmp.rs b/src/tools/clippy/clippy_lints/src/operators/float_cmp.rs new file mode 100644 index 0000000000000..0ef793443ff45 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/float_cmp.rs @@ -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(_, _)) +} diff --git a/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs b/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs new file mode 100644 index 0000000000000..a0a8b6aabd9e3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs @@ -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, + ); + } + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/identity_op.rs b/src/tools/clippy/clippy_lints/src/operators/identity_op.rs similarity index 60% rename from src/tools/clippy/clippy_lints/src/identity_op.rs rename to src/tools/clippy/clippy_lints/src/operators/identity_op.rs index 419ea5a6811b8..b48d6c4e2e2af 100644 --- a/src/tools/clippy/clippy_lints/src/identity_op.rs +++ b/src/tools/clippy/clippy_lints/src/operators/identity_op.rs @@ -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`" -} - -declare_lint_pass!(IdentityOp => [IDENTITY_OP]); +use super::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), diff --git a/src/tools/clippy/clippy_lints/src/operators/integer_division.rs b/src/tools/clippy/clippy_lints/src/operators/integer_division.rs new file mode 100644 index 0000000000000..631d10f4a72e9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/integer_division.rs @@ -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", + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/operators/misrefactored_assign_op.rs b/src/tools/clippy/clippy_lints/src/operators/misrefactored_assign_op.rs new file mode 100644 index 0000000000000..0024384d9278d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/misrefactored_assign_op.rs @@ -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, + } +} diff --git a/src/tools/clippy/clippy_lints/src/operators/mod.rs b/src/tools/clippy/clippy_lints/src/operators/mod.rs new file mode 100644 index 0000000000000..35fe405bcf14f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/mod.rs @@ -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 = 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 `_ m + /// c` (where `` is one of {`&`, `|`} and `` 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 + } +} diff --git a/src/tools/clippy/clippy_lints/src/modulo_arithmetic.rs b/src/tools/clippy/clippy_lints/src/operators/modulo_arithmetic.rs similarity index 64% rename from src/tools/clippy/clippy_lints/src/modulo_arithmetic.rs rename to src/tools/clippy/clippy_lints/src/operators/modulo_arithmetic.rs index 195b2e5c2ee0a..af4e74947f41d 100644 --- a/src/tools/clippy/clippy_lints/src/modulo_arithmetic.rs +++ b/src/tools/clippy/clippy_lints/src/operators/modulo_arithmetic.rs @@ -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, @@ -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); - } - } - }; - }, - _ => {}, - } - } -} diff --git a/src/tools/clippy/clippy_lints/src/operators/modulo_one.rs b/src/tools/clippy/clippy_lints/src/operators/modulo_one.rs new file mode 100644 index 0000000000000..54eea14833ffe --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/modulo_one.rs @@ -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", + ); + } + }; + } +} diff --git a/src/tools/clippy/clippy_lints/src/operators/needless_bitwise_bool.rs b/src/tools/clippy/clippy_lints/src/operators/needless_bitwise_bool.rs new file mode 100644 index 0000000000000..e902235a014e8 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/needless_bitwise_bool.rs @@ -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); + } + }, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs b/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs new file mode 100644 index 0000000000000..82f454d02f713 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs @@ -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, + /// This field is used to check whether expressions are constants, such as in enum discriminants + /// and consts + const_span: Option, +} +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; + } +} diff --git a/src/tools/clippy/clippy_lints/src/operators/op_ref.rs b/src/tools/clippy/clippy_lints/src/operators/op_ref.rs new file mode 100644 index 0000000000000..1805672e37254 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/op_ref.rs @@ -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 + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/operators/ptr_eq.rs b/src/tools/clippy/clippy_lints/src/operators/ptr_eq.rs new file mode 100644 index 0000000000000..1aefc2741c21c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/ptr_eq.rs @@ -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 +} diff --git a/src/tools/clippy/clippy_lints/src/operators/self_assignment.rs b/src/tools/clippy/clippy_lints/src/operators/self_assignment.rs new file mode 100644 index 0000000000000..9d6bec05bf095 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/self_assignment.rs @@ -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, ""); + let rhs = snippet(cx, rhs.span, ""); + span_lint( + cx, + SELF_ASSIGNMENT, + e.span, + &format!("self-assignment of `{}` to `{}`", rhs, lhs), + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/operators/verbose_bit_mask.rs b/src/tools/clippy/clippy_lints/src/operators/verbose_bit_mask.rs new file mode 100644 index 0000000000000..ff85fd5542988 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/operators/verbose_bit_mask.rs @@ -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, + ); + }, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs index 05ab62786f409..5fa4fd74853f1 100644 --- a/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs +++ b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs @@ -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); diff --git a/src/tools/clippy/clippy_lints/src/ptr.rs b/src/tools/clippy/clippy_lints/src/ptr.rs index b06eba13d2fdb..25b73918c0a25 100644 --- a/src/tools/clippy/clippy_lints/src/ptr.rs +++ b/src/tools/clippy/clippy_lints/src/ptr.rs @@ -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(); } diff --git a/src/tools/clippy/clippy_lints/src/ptr_eq.rs b/src/tools/clippy/clippy_lints/src/ptr_eq.rs deleted file mode 100644 index 2bec93ac60605..0000000000000 --- a/src/tools/clippy/clippy_lints/src/ptr_eq.rs +++ /dev/null @@ -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 -} diff --git a/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs b/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs index 0825f00f421c5..f8801f769e83d 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs @@ -87,7 +87,7 @@ impl RedundantStaticLifetimes { _ => {}, } } - self.visit_type(&*borrow_type.ty, cx, reason); + self.visit_type(&borrow_type.ty, cx, reason); }, _ => {}, } diff --git a/src/tools/clippy/clippy_lints/src/returns.rs b/src/tools/clippy/clippy_lints/src/returns.rs index e525eba53e2aa..5ae04947b82d0 100644 --- a/src/tools/clippy/clippy_lints/src/returns.rs +++ b/src/tools/clippy/clippy_lints/src/returns.rs @@ -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, replacement: RetReplacement) { +fn emit_return_lint( + cx: &LateContext<'_>, + emission_place: HirId, + ret_span: Span, + inner_span: Option, + 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, + ); + }, ); }, }, diff --git a/src/tools/clippy/clippy_lints/src/self_assignment.rs b/src/tools/clippy/clippy_lints/src/self_assignment.rs deleted file mode 100644 index b14f0518bdb77..0000000000000 --- a/src/tools/clippy/clippy_lints/src/self_assignment.rs +++ /dev/null @@ -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, ""); - let rhs = snippet(cx, rhs.span, ""); - span_lint( - cx, - SELF_ASSIGNMENT, - expr.span, - &format!("self-assignment of `{}` to `{}`", rhs, lhs), - ); - } - } - } -} diff --git a/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs b/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs index 975a0a06e3885..2c8aa17e80dbd 100644 --- a/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs +++ b/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs @@ -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; + } } } diff --git a/src/tools/clippy/clippy_lints/src/strings.rs b/src/tools/clippy/clippy_lints/src/strings.rs index 71f3e6b6a6ec6..eb704a07451ca 100644 --- a/src/tools/clippy/clippy_lints/src/strings.rs +++ b/src/tools/clippy/clippy_lints/src/strings.rs @@ -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, diff --git a/src/tools/clippy/clippy_lints/src/transmute/mod.rs b/src/tools/clippy/clippy_lints/src/transmute/mod.rs index cbe1406728bc7..5f3e98144f42d 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/mod.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/mod.rs @@ -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, +} +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) -> 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); } diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ref.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ref.rs index f3653199b3758..3ed5d5c695081 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ref.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ref.rs @@ -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, ) -> 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`. +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 + } +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/utils.rs b/src/tools/clippy/clippy_lints/src/transmute/utils.rs index 0cbf5ccefa6d8..74927570b40eb 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/utils.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/utils.rs @@ -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`. 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 { diff --git a/src/tools/clippy/clippy_lints/src/unused_async.rs b/src/tools/clippy/clippy_lints/src/unused_async.rs index c8ec4442ab1a4..a832dfcccaf36 100644 --- a/src/tools/clippy/clippy_lints/src/unused_async.rs +++ b/src/tools/clippy/clippy_lints/src/unused_async.rs @@ -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", + ); } } } diff --git a/src/tools/clippy/clippy_lints/src/unwrap.rs b/src/tools/clippy/clippy_lints/src/unwrap.rs index 9b9e25326f966..d3f9e5abfd739 100644 --- a/src/tools/clippy/clippy_lints/src/unwrap.rs +++ b/src/tools/clippy/clippy_lints/src/unwrap.rs @@ -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), diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs index b885e5132f1ec..a94f0357977ea 100644 --- a/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs +++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs @@ -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, } -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"; - - then { + 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; + } + + 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(); diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 2564099f4dbca..6518e0a6ea073 100644 --- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -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::::default(), applicability_info: FxHashMap::::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); diff --git a/src/tools/clippy/clippy_lints/src/vec_resize_to_zero.rs b/src/tools/clippy/clippy_lints/src/vec_resize_to_zero.rs index 4d86abd0fa123..0fee3e812d286 100644 --- a/src/tools/clippy/clippy_lints/src/vec_resize_to_zero.rs +++ b/src/tools/clippy/clippy_lints/src/vec_resize_to_zero.rs @@ -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, diff --git a/src/tools/clippy/clippy_utils/Cargo.toml b/src/tools/clippy/clippy_utils/Cargo.toml index c4e0b8448ab3f..bb443bdc1168f 100644 --- a/src/tools/clippy/clippy_utils/Cargo.toml +++ b/src/tools/clippy/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.63" +version = "0.1.64" edition = "2021" publish = false diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index af62c4afd5a51..793e3cc58c21d 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -1,4 +1,5 @@ use crate::consts::constant_simple; +use crate::macros::macro_backtrace; use crate::source::snippet_opt; use rustc_ast::ast::InlineAsmTemplatePiece; use rustc_data_structures::fx::FxHasher; @@ -12,9 +13,13 @@ use rustc_hir::{ use rustc_lexer::{tokenize, TokenKind}; use rustc_lint::LateContext; use rustc_middle::ty::TypeckResults; -use rustc_span::Symbol; +use rustc_span::{sym, Symbol}; use std::hash::{Hash, Hasher}; +/// Callback that is called when two expressions are not equal in the sense of `SpanlessEq`, but +/// other conditions would make them equal. +type SpanlessEqCallback<'a> = dyn FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a; + /// Type used to check whether two ast are the same. This is different from the /// operator `==` on ast types as this operator would compare true equality with /// ID and span. @@ -25,7 +30,7 @@ pub struct SpanlessEq<'a, 'tcx> { cx: &'a LateContext<'tcx>, maybe_typeck_results: Option<(&'tcx TypeckResults<'tcx>, &'tcx TypeckResults<'tcx>)>, allow_side_effects: bool, - expr_fallback: Option, &Expr<'_>) -> bool + 'a>>, + expr_fallback: Option>>, } impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { @@ -121,6 +126,9 @@ impl HirEqInterExpr<'_, '_, '_> { /// Checks whether two blocks are the same. fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool { + if self.cannot_be_compared_block(left) || self.cannot_be_compared_block(right) { + return false; + } match (left.stmts, left.expr, right.stmts, right.expr) { ([], None, [], None) => { // For empty blocks, check to see if the tokens are equal. This will catch the case where a macro @@ -171,6 +179,38 @@ impl HirEqInterExpr<'_, '_, '_> { } } + fn cannot_be_compared_block(&mut self, block: &Block<'_>) -> bool { + if block.stmts.last().map_or(false, |stmt| { + matches!( + stmt.kind, + StmtKind::Semi(semi_expr) if self.should_ignore(semi_expr) + ) + }) { + return true; + } + + if let Some(block_expr) = block.expr + && self.should_ignore(block_expr) + { + return true + } + + false + } + + fn should_ignore(&mut self, expr: &Expr<'_>) -> bool { + if macro_backtrace(expr.span).last().map_or(false, |macro_call| { + matches!( + &self.inner.cx.tcx.get_diagnostic_name(macro_call.def_id), + Some(sym::todo_macro | sym::unimplemented_macro) + ) + }) { + return true; + } + + false + } + pub fn eq_array_length(&mut self, left: ArrayLen, right: ArrayLen) -> bool { match (left, right) { (ArrayLen::Infer(..), ArrayLen::Infer(..)) => true, diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index 5106c39b5c6fc..9fa28e137f974 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -1539,9 +1539,13 @@ pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tc None } -/// Returns `true` if the lint is allowed in the current context +/// Returns `true` if the lint is allowed in the current context. This is useful for +/// skipping long running code when it's unnecessary /// -/// Useful for skipping long running code when it's unnecessary +/// This function should check the lint level for the same node, that the lint will +/// be emitted at. If the information is buffered to be emitted at a later point, please +/// make sure to use `span_lint_hir` functions to emit the lint. This ensures that +/// expectations at the checked nodes will be fulfilled. pub fn is_lint_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool { cx.tcx.lint_level_at_node(lint, id).0 == Level::Allow } @@ -2058,6 +2062,21 @@ pub fn peel_hir_expr_refs<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) { (e, count) } +/// Peels off all references on the type. Returns the underlying type and the number of references +/// removed. +pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize) { + let mut count = 0; + loop { + match &ty.kind { + TyKind::Rptr(_, ref_ty) => { + ty = ref_ty.ty; + count += 1; + }, + _ => break (ty, count), + } + } +} + /// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is /// dereferenced. An overloaded deref such as `Vec` to slice would not be removed. pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> { @@ -2110,7 +2129,7 @@ fn with_test_item_names<'tcx>(tcx: TyCtxt<'tcx>, module: LocalDefId, f: impl Fn( } } names.sort_unstable(); - f(&*entry.insert(names)) + f(entry.insert(names)) }, } } @@ -2168,6 +2187,50 @@ pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool { && item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests") } +/// Walks the HIR tree from the given expression, up to the node where the value produced by the +/// expression is consumed. Calls the function for every node encountered this way until it returns +/// `Some`. +/// +/// This allows walking through `if`, `match`, `break`, block expressions to find where the value +/// produced by the expression is consumed. +pub fn walk_to_expr_usage<'tcx, T>( + cx: &LateContext<'tcx>, + e: &Expr<'tcx>, + mut f: impl FnMut(Node<'tcx>, HirId) -> Option, +) -> Option { + let map = cx.tcx.hir(); + let mut iter = map.parent_iter(e.hir_id); + let mut child_id = e.hir_id; + + while let Some((parent_id, parent)) = iter.next() { + if let Some(x) = f(parent, child_id) { + return Some(x); + } + let parent = match parent { + Node::Expr(e) => e, + Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => { + child_id = parent_id; + continue; + }, + Node::Arm(a) if a.body.hir_id == child_id => { + child_id = parent_id; + continue; + }, + _ => return None, + }; + match parent.kind { + ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id, + ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => { + child_id = id; + iter = map.parent_iter(id); + }, + ExprKind::Block(..) => child_id = parent_id, + _ => return None, + } + } + None +} + macro_rules! op_utils { ($($name:ident $assign:ident)*) => { /// Binary operation traits like `LangItem::Add` diff --git a/src/tools/clippy/clippy_utils/src/msrvs.rs b/src/tools/clippy/clippy_utils/src/msrvs.rs index b9ec2c19fdd31..b09c929f76e22 100644 --- a/src/tools/clippy/clippy_utils/src/msrvs.rs +++ b/src/tools/clippy/clippy_utils/src/msrvs.rs @@ -12,8 +12,8 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { - 1,53,0 { OR_PATTERNS, MANUAL_BITS } - 1,52,0 { STR_SPLIT_ONCE } + 1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN } + 1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST } 1,51,0 { BORROW_AS_PTR, UNSIGNED_ABS } 1,50,0 { BOOL_THEN } 1,47,0 { TAU } @@ -23,14 +23,15 @@ msrv_aliases! { 1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS } 1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE } 1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF } - 1,38,0 { POINTER_CAST } + 1,38,0 { POINTER_CAST, REM_EUCLID } 1,37,0 { TYPE_ALIAS_ENUM_VARIANTS } 1,36,0 { ITERATOR_COPIED } 1,35,0 { OPTION_COPIED, RANGE_CONTAINS } 1,34,0 { TRY_FROM } 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } 1,28,0 { FROM_BOOL } - 1,26,0 { RANGE_INCLUSIVE } + 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN } + 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN } 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR } 1,16,0 { STR_REPEAT } 1,24,0 { IS_ASCII_DIGIT } diff --git a/src/tools/clippy/clippy_utils/src/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs index 89789c3d85135..6542e77113b45 100644 --- a/src/tools/clippy/clippy_utils/src/paths.rs +++ b/src/tools/clippy/clippy_utils/src/paths.rs @@ -21,8 +21,14 @@ pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"]; pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"]; pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"]; pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"]; +pub const BTREESET_ITER: [&str; 6] = ["alloc", "collections", "btree", "set", "BTreeSet", "iter"]; pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"]; pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"]; +pub const CORE_ITER_COLLECT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "collect"]; +pub const CORE_ITER_CLONED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "cloned"]; +pub const CORE_ITER_COPIED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "copied"]; +pub const CORE_ITER_FILTER: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "filter"]; +pub const CORE_ITER_INTO_ITER: [&str; 6] = ["core", "iter", "traits", "collect", "IntoIterator", "into_iter"]; pub const CSTRING_AS_C_STR: [&str; 5] = ["alloc", "ffi", "c_str", "CString", "as_c_str"]; pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"]; pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"]; @@ -50,6 +56,7 @@ pub const FUTURES_IO_ASYNCWRITEEXT: [&str; 3] = ["futures_util", "io", "AsyncWri pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"]; pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"]; pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"]; +pub const HASHSET_ITER: [&str; 6] = ["std", "collections", "hash", "set", "HashSet", "iter"]; #[cfg(feature = "internal")] pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"]; #[cfg(feature = "internal")] @@ -62,6 +69,7 @@ pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"]; pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"]; pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"]; pub const ITER_COUNT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "count"]; +pub const ITER_EMPTY: [&str; 5] = ["core", "iter", "sources", "empty", "Empty"]; pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"]; #[expect(clippy::invalid_paths)] // internal lints do not know about all external crates pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"]; @@ -83,8 +91,6 @@ pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"]; pub const ORD: [&str; 3] = ["core", "cmp", "Ord"]; pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"]; pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"]; -pub const PARKING_LOT_RAWMUTEX: [&str; 3] = ["parking_lot", "raw_mutex", "RawMutex"]; -pub const PARKING_LOT_RAWRWLOCK: [&str; 3] = ["parking_lot", "raw_rwlock", "RawRwLock"]; pub const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard"]; pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockReadGuard"]; pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockWriteGuard"]; @@ -144,6 +150,7 @@ pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_p pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"]; pub const SLICE_GET: [&str; 4] = ["core", "slice", "", "get"]; pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "", "into_vec"]; +pub const SLICE_INTO: [&str; 4] = ["core", "slice", "", "iter"]; pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"]; pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"]; pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"]; @@ -153,6 +160,7 @@ pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_s pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"]; pub const STRING_NEW: [&str; 4] = ["alloc", "string", "String", "new"]; pub const STR_BYTES: [&str; 4] = ["core", "str", "", "bytes"]; +pub const STR_CHARS: [&str; 4] = ["core", "str", "", "chars"]; pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "", "ends_with"]; pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"]; pub const STR_LEN: [&str; 4] = ["core", "str", "", "len"]; @@ -178,6 +186,7 @@ pub const TOKIO_IO_ASYNCWRITEEXT: [&str; 5] = ["tokio", "io", "util", "async_wri pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"]; pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"]; pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"]; +pub const VEC_DEQUE_ITER: [&str; 5] = ["alloc", "collections", "vec_deque", "VecDeque", "iter"]; pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"]; pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"]; pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"]; diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs index 227e97d37ecc3..6ca36eed4e654 100644 --- a/src/tools/clippy/clippy_utils/src/ty.rs +++ b/src/tools/clippy/clippy_utils/src/ty.rs @@ -2,19 +2,20 @@ #![allow(clippy::module_name_repetitions)] +use core::ops::ControlFlow; use rustc_ast::ast::Mutability; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; -use rustc_hir::def::{CtorKind, DefKind, Res}; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{Expr, LangItem, TyKind, Unsafety}; +use rustc_hir::{Expr, FnDecl, LangItem, TyKind, Unsafety}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::mir::interpret::{ConstValue, Scalar}; use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst}; use rustc_middle::ty::{ - self, AdtDef, Binder, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, Ty, TyCtxt, TypeFoldable, UintTy, - VariantDiscr, + self, AdtDef, Binder, BoundRegion, DefIdTree, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, ProjectionTy, + Region, RegionKind, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitor, UintTy, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; @@ -501,16 +502,46 @@ pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator { Sig(Binder<'tcx, FnSig<'tcx>>), - Closure(Binder<'tcx, FnSig<'tcx>>), + Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>), Trait(Binder<'tcx, Ty<'tcx>>, Option>>), } impl<'tcx> ExprFnSig<'tcx> { - /// Gets the argument type at the given offset. - pub fn input(self, i: usize) -> Binder<'tcx, Ty<'tcx>> { + /// Gets the argument type at the given offset. This will return `None` when the index is out of + /// bounds only for variadic functions, otherwise this will panic. + pub fn input(self, i: usize) -> Option>> { match self { - Self::Sig(sig) => sig.input(i), - Self::Closure(sig) => sig.input(0).map_bound(|ty| ty.tuple_fields()[i]), - Self::Trait(inputs, _) => inputs.map_bound(|ty| ty.tuple_fields()[i]), + Self::Sig(sig) => { + if sig.c_variadic() { + sig.inputs().map_bound(|inputs| inputs.get(i).copied()).transpose() + } else { + Some(sig.input(i)) + } + }, + Self::Closure(_, sig) => Some(sig.input(0).map_bound(|ty| ty.tuple_fields()[i])), + Self::Trait(inputs, _) => Some(inputs.map_bound(|ty| ty.tuple_fields()[i])), + } + } + + /// Gets the argument type at the given offset. For closures this will also get the type as + /// written. This will return `None` when the index is out of bounds only for variadic + /// functions, otherwise this will panic. + pub fn input_with_hir(self, i: usize) -> Option<(Option<&'tcx hir::Ty<'tcx>>, Binder<'tcx, Ty<'tcx>>)> { + match self { + Self::Sig(sig) => { + if sig.c_variadic() { + sig.inputs() + .map_bound(|inputs| inputs.get(i).copied()) + .transpose() + .map(|arg| (None, arg)) + } else { + Some((None, sig.input(i))) + } + }, + Self::Closure(decl, sig) => Some(( + decl.and_then(|decl| decl.inputs.get(i)), + sig.input(0).map_bound(|ty| ty.tuple_fields()[i]), + )), + Self::Trait(inputs, _) => Some((None, inputs.map_bound(|ty| ty.tuple_fields()[i]))), } } @@ -518,7 +549,7 @@ impl<'tcx> ExprFnSig<'tcx> { /// specified. pub fn output(self) -> Option>> { match self { - Self::Sig(sig) | Self::Closure(sig) => Some(sig.output()), + Self::Sig(sig) | Self::Closure(_, sig) => Some(sig.output()), Self::Trait(_, output) => output, } } @@ -529,74 +560,123 @@ pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option Some(ExprFnSig::Closure(subs.as_closure().sig())), - ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs))), - ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig)), - ty::Dynamic(bounds, _) => { - let lang_items = cx.tcx.lang_items(); - match bounds.principal() { - Some(bound) - if Some(bound.def_id()) == lang_items.fn_trait() - || Some(bound.def_id()) == lang_items.fn_once_trait() - || Some(bound.def_id()) == lang_items.fn_mut_trait() => - { - let output = bounds - .projection_bounds() - .find(|p| lang_items.fn_once_output().map_or(false, |id| id == p.item_def_id())) - .map(|p| p.map_bound(|p| p.term.ty().expect("return type was a const"))); - Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output)) - }, - _ => None, + ty_sig(cx, cx.typeck_results().expr_ty_adjusted(expr).peel_refs()) + } +} + +fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { + match *ty.kind() { + ty::Closure(id, subs) => { + let decl = id + .as_local() + .and_then(|id| cx.tcx.hir().fn_decl_by_hir_id(cx.tcx.hir().local_def_id_to_hir_id(id))); + Some(ExprFnSig::Closure(decl, subs.as_closure().sig())) + }, + ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs))), + ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig)), + ty::Dynamic(bounds, _) => { + let lang_items = cx.tcx.lang_items(); + match bounds.principal() { + Some(bound) + if Some(bound.def_id()) == lang_items.fn_trait() + || Some(bound.def_id()) == lang_items.fn_once_trait() + || Some(bound.def_id()) == lang_items.fn_mut_trait() => + { + let output = bounds + .projection_bounds() + .find(|p| lang_items.fn_once_output().map_or(false, |id| id == p.item_def_id())) + .map(|p| p.map_bound(|p| p.term.ty().unwrap())); + Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output)) + }, + _ => None, + } + }, + ty::Projection(proj) => match cx.tcx.try_normalize_erasing_regions(cx.param_env, ty) { + Ok(normalized_ty) if normalized_ty != ty => ty_sig(cx, normalized_ty), + _ => sig_for_projection(cx, proj).or_else(|| sig_from_bounds(cx, ty)), + }, + ty::Param(_) => sig_from_bounds(cx, ty), + _ => None, + } +} + +fn sig_from_bounds<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { + let mut inputs = None; + let mut output = None; + let lang_items = cx.tcx.lang_items(); + + for (pred, _) in all_predicates_of(cx.tcx, cx.typeck_results().hir_owner.to_def_id()) { + match pred.kind().skip_binder() { + PredicateKind::Trait(p) + if (lang_items.fn_trait() == Some(p.def_id()) + || lang_items.fn_mut_trait() == Some(p.def_id()) + || lang_items.fn_once_trait() == Some(p.def_id())) + && p.self_ty() == ty => + { + if inputs.is_some() { + // Multiple different fn trait impls. Is this even allowed? + return None; } + inputs = Some(pred.kind().rebind(p.trait_ref.substs.type_at(1))); }, - ty::Param(_) | ty::Projection(..) => { - let mut inputs = None; - let mut output = None; - let lang_items = cx.tcx.lang_items(); - - for (pred, _) in all_predicates_of(cx.tcx, cx.typeck_results().hir_owner.to_def_id()) { - let mut is_input = false; - if let Some(ty) = pred - .kind() - .map_bound(|pred| match pred { - PredicateKind::Trait(p) - if (lang_items.fn_trait() == Some(p.def_id()) - || lang_items.fn_mut_trait() == Some(p.def_id()) - || lang_items.fn_once_trait() == Some(p.def_id())) - && p.self_ty() == ty => - { - is_input = true; - Some(p.trait_ref.substs.type_at(1)) - }, - PredicateKind::Projection(p) - if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() - && p.projection_ty.self_ty() == ty => - { - is_input = false; - p.term.ty() - }, - _ => None, - }) - .transpose() - { - if is_input && inputs.is_none() { - inputs = Some(ty); - } else if !is_input && output.is_none() { - output = Some(ty); - } else { - // Multiple different fn trait impls. Is this even allowed? - return None; - } - } + PredicateKind::Projection(p) + if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() + && p.projection_ty.self_ty() == ty => + { + if output.is_some() { + // Multiple different fn trait impls. Is this even allowed? + return None; } + output = Some(pred.kind().rebind(p.term.ty().unwrap())); + }, + _ => (), + } + } + + inputs.map(|ty| ExprFnSig::Trait(ty, output)) +} + +fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: ProjectionTy<'tcx>) -> Option> { + let mut inputs = None; + let mut output = None; + let lang_items = cx.tcx.lang_items(); - inputs.map(|ty| ExprFnSig::Trait(ty, output)) + for pred in cx + .tcx + .bound_explicit_item_bounds(ty.item_def_id) + .transpose_iter() + .map(|x| x.map_bound(|(p, _)| p)) + { + match pred.0.kind().skip_binder() { + PredicateKind::Trait(p) + if (lang_items.fn_trait() == Some(p.def_id()) + || lang_items.fn_mut_trait() == Some(p.def_id()) + || lang_items.fn_once_trait() == Some(p.def_id())) => + { + if inputs.is_some() { + // Multiple different fn trait impls. Is this even allowed? + return None; + } + inputs = Some( + pred.map_bound(|pred| pred.kind().rebind(p.trait_ref.substs.type_at(1))) + .subst(cx.tcx, ty.substs), + ); }, - _ => None, + PredicateKind::Projection(p) if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() => { + if output.is_some() { + // Multiple different fn trait impls. Is this even allowed? + return None; + } + output = Some( + pred.map_bound(|pred| pred.kind().rebind(p.term.ty().unwrap())) + .subst(cx.tcx, ty.substs), + ); + }, + _ => (), } } + + inputs.map(|ty| ExprFnSig::Trait(ty, output)) } #[derive(Clone, Copy)] @@ -667,3 +747,45 @@ pub fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { false } } + +pub fn for_each_top_level_late_bound_region( + ty: Ty<'_>, + f: impl FnMut(BoundRegion) -> ControlFlow, +) -> ControlFlow { + struct V { + index: u32, + f: F, + } + impl<'tcx, B, F: FnMut(BoundRegion) -> ControlFlow> TypeVisitor<'tcx> for V { + type BreakTy = B; + fn visit_region(&mut self, r: Region<'tcx>) -> ControlFlow { + if let RegionKind::ReLateBound(idx, bound) = r.kind() && idx.as_u32() == self.index { + (self.f)(bound) + } else { + ControlFlow::Continue(()) + } + } + fn visit_binder>(&mut self, t: &Binder<'tcx, T>) -> ControlFlow { + self.index += 1; + let res = t.super_visit_with(self); + self.index -= 1; + res + } + } + ty.visit_with(&mut V { index: 0, f }) +} + +/// Gets the struct or enum variant from the given `Res` +pub fn variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<&'tcx VariantDef> { + match res { + Res::Def(DefKind::Struct, id) => Some(cx.tcx.adt_def(id).non_enum_variant()), + Res::Def(DefKind::Variant, id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).variant_with_id(id)), + Res::Def(DefKind::Ctor(CtorOf::Struct, _), id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).non_enum_variant()), + Res::Def(DefKind::Ctor(CtorOf::Variant, _), id) => { + let var_id = cx.tcx.parent(id); + Some(cx.tcx.adt_def(cx.tcx.parent(var_id)).variant_with_id(var_id)) + }, + Res::SelfCtor(id) => Some(cx.tcx.type_of(id).ty_adt_def().unwrap().non_enum_variant()), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs index b6c8f1d516e55..68cfa8c1aa8ec 100644 --- a/src/tools/clippy/clippy_utils/src/visitors.rs +++ b/src/tools/clippy/clippy_utils/src/visitors.rs @@ -1,16 +1,18 @@ +use crate::ty::needs_ordered_drop; use crate::{get_enclosing_block, path_to_local_id}; use core::ops::ControlFlow; use rustc_hir as hir; -use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor}; use rustc_hir::{ - Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Stmt, UnOp, UnsafeSource, - Unsafety, + Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, QPath, Stmt, UnOp, + UnsafeSource, Unsafety, }; use rustc_lint::LateContext; use rustc_middle::hir::map::Map; use rustc_middle::hir::nested_filter; -use rustc_middle::ty; +use rustc_middle::ty::adjustment::Adjust; +use rustc_middle::ty::{self, Ty, TypeckResults}; /// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested /// bodies (i.e. closures) are visited. @@ -494,3 +496,124 @@ pub fn for_each_local_use_after_expr<'tcx, B>( ControlFlow::Continue(()) } } + +// Calls the given function for every unconsumed temporary created by the expression. Note the +// function is only guaranteed to be called for types which need to be dropped, but it may be called +// for other types. +pub fn for_each_unconsumed_temporary<'tcx, B>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + mut f: impl FnMut(Ty<'tcx>) -> ControlFlow, +) -> ControlFlow { + // Todo: Handle partially consumed values. + fn helper<'tcx, B>( + typeck: &'tcx TypeckResults<'tcx>, + consume: bool, + e: &'tcx Expr<'tcx>, + f: &mut impl FnMut(Ty<'tcx>) -> ControlFlow, + ) -> ControlFlow { + if !consume + || matches!( + typeck.expr_adjustments(e), + [adjust, ..] if matches!(adjust.kind, Adjust::Borrow(_) | Adjust::Deref(_)) + ) + { + match e.kind { + ExprKind::Path(QPath::Resolved(None, p)) + if matches!(p.res, Res::Def(DefKind::Ctor(_, CtorKind::Const), _)) => + { + f(typeck.expr_ty(e))?; + }, + ExprKind::Path(_) + | ExprKind::Unary(UnOp::Deref, _) + | ExprKind::Index(..) + | ExprKind::Field(..) + | ExprKind::AddrOf(..) => (), + _ => f(typeck.expr_ty(e))?, + } + } + match e.kind { + ExprKind::AddrOf(_, _, e) + | ExprKind::Field(e, _) + | ExprKind::Unary(UnOp::Deref, e) + | ExprKind::Match(e, ..) + | ExprKind::Let(&Let { init: e, .. }) => { + helper(typeck, false, e, f)?; + }, + ExprKind::Block(&Block { expr: Some(e), .. }, _) + | ExprKind::Box(e) + | ExprKind::Cast(e, _) + | ExprKind::Unary(_, e) => { + helper(typeck, true, e, f)?; + }, + ExprKind::Call(callee, args) => { + helper(typeck, true, callee, f)?; + for arg in args { + helper(typeck, true, arg, f)?; + } + }, + ExprKind::MethodCall(_, args, _) | ExprKind::Tup(args) | ExprKind::Array(args) => { + for arg in args { + helper(typeck, true, arg, f)?; + } + }, + ExprKind::Index(borrowed, consumed) + | ExprKind::Assign(borrowed, consumed, _) + | ExprKind::AssignOp(_, borrowed, consumed) => { + helper(typeck, false, borrowed, f)?; + helper(typeck, true, consumed, f)?; + }, + ExprKind::Binary(_, lhs, rhs) => { + helper(typeck, true, lhs, f)?; + helper(typeck, true, rhs, f)?; + }, + ExprKind::Struct(_, fields, default) => { + for field in fields { + helper(typeck, true, field.expr, f)?; + } + if let Some(default) = default { + helper(typeck, false, default, f)?; + } + }, + ExprKind::If(cond, then, else_expr) => { + helper(typeck, true, cond, f)?; + helper(typeck, true, then, f)?; + if let Some(else_expr) = else_expr { + helper(typeck, true, else_expr, f)?; + } + }, + ExprKind::Type(e, _) => { + helper(typeck, consume, e, f)?; + }, + + // Either drops temporaries, jumps out of the current expression, or has no sub expression. + ExprKind::DropTemps(_) + | ExprKind::Ret(_) + | ExprKind::Break(..) + | ExprKind::Yield(..) + | ExprKind::Block(..) + | ExprKind::Loop(..) + | ExprKind::Repeat(..) + | ExprKind::Lit(_) + | ExprKind::ConstBlock(_) + | ExprKind::Closure { .. } + | ExprKind::Path(_) + | ExprKind::Continue(_) + | ExprKind::InlineAsm(_) + | ExprKind::Err => (), + } + ControlFlow::Continue(()) + } + helper(cx.typeck_results(), true, e, &mut f) +} + +pub fn any_temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool { + for_each_unconsumed_temporary(cx, e, |ty| { + if needs_ordered_drop(cx, ty) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_break() +} diff --git a/src/tools/clippy/rust-toolchain b/src/tools/clippy/rust-toolchain index 6ad56aacf8c95..6cc6d5036b379 100644 --- a/src/tools/clippy/rust-toolchain +++ b/src/tools/clippy/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-06-16" +channel = "nightly-2022-06-30" components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/src/tools/clippy/src/driver.rs b/src/tools/clippy/src/driver.rs index 67467f89b475f..96d542cfe1052 100644 --- a/src/tools/clippy/src/driver.rs +++ b/src/tools/clippy/src/driver.rs @@ -60,6 +60,7 @@ fn test_arg_value() { assert_eq!(arg_value(args, "--bar", |p| p == "foo"), None); assert_eq!(arg_value(args, "--foobar", |p| p == "foo"), None); assert_eq!(arg_value(args, "--foobar", |p| p == "123"), Some("123")); + assert_eq!(arg_value(args, "--foobar", |p| p.contains("12")), Some("123")); assert_eq!(arg_value(args, "--foo", |_| true), None); } @@ -152,7 +153,8 @@ You can use tool lints to allow or deny lints from your code, eg.: const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new"; -static ICE_HOOK: LazyLock) + Sync + Send + 'static>> = LazyLock::new(|| { +type PanicCallback = dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static; +static ICE_HOOK: LazyLock> = LazyLock::new(|| { let hook = panic::take_hook(); panic::set_hook(Box::new(|info| report_clippy_ice(info, BUG_REPORT_URL))); hook @@ -334,15 +336,13 @@ pub fn main() { // - IF Clippy is run on the main crate, not on deps (`!cap_lints_allow`) THEN // - IF `--no-deps` is not set (`!no_deps`) OR // - IF `--no-deps` is set and Clippy is run on the specified primary package - let cap_lints_allow = arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_some(); + let cap_lints_allow = arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_some() + && arg_value(&orig_args, "--force-warn", |val| val.contains("clippy::")).is_none(); let in_primary_package = env::var("CARGO_PRIMARY_PACKAGE").is_ok(); let clippy_enabled = !cap_lints_allow && (!no_deps || in_primary_package); if clippy_enabled { args.extend(clippy_args); - } - - if clippy_enabled { rustc_driver::RunCompiler::new(&args, &mut ClippyCallbacks { clippy_args_var }).run() } else { rustc_driver::RunCompiler::new(&args, &mut RustcCallbacks { clippy_args_var }).run() diff --git a/src/tools/clippy/tests/compile-test.rs b/src/tools/clippy/tests/compile-test.rs index 061cda7e01e5b..bf7a39edf4c91 100644 --- a/src/tools/clippy/tests/compile-test.rs +++ b/src/tools/clippy/tests/compile-test.rs @@ -23,6 +23,7 @@ const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal"); /// All crates used in UI tests are listed here static TEST_DEPENDENCIES: &[&str] = &[ + "clippy_lints", "clippy_utils", "derive_new", "futures", @@ -41,6 +42,8 @@ static TEST_DEPENDENCIES: &[&str] = &[ // Test dependencies may need an `extern crate` here to ensure that they show up // in the depinfo file (otherwise cargo thinks they are unused) #[allow(unused_extern_crates)] +extern crate clippy_lints; +#[allow(unused_extern_crates)] extern crate clippy_utils; #[allow(unused_extern_crates)] extern crate derive_new; @@ -124,7 +127,7 @@ fn base_config(test_dir: &str) -> compiletest::Config { let mut config = compiletest::Config { edition: Some("2021".into()), mode: TestMode::Ui, - ..compiletest::Config::default() + ..Default::default() }; if let Ok(filters) = env::var("TESTNAME") { @@ -280,6 +283,24 @@ fn run_ui_cargo() { } env::set_current_dir(&src_path)?; + + let cargo_toml_path = case.path().join("Cargo.toml"); + let cargo_content = fs::read(&cargo_toml_path)?; + let cargo_parsed: toml::Value = toml::from_str( + std::str::from_utf8(&cargo_content).expect("`Cargo.toml` is not a valid utf-8 file!"), + ) + .expect("Can't parse `Cargo.toml`"); + + let _g = VarGuard::set("CARGO_MANIFEST_DIR", case.path()); + let _h = VarGuard::set( + "CARGO_PKG_RUST_VERSION", + cargo_parsed + .get("package") + .and_then(|p| p.get("rust-version")) + .and_then(toml::Value::as_str) + .unwrap_or(""), + ); + for file in fs::read_dir(&src_path)? { let file = file?; if file.file_type()?.is_dir() { diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_diff/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_diff/Cargo.toml new file mode 100644 index 0000000000000..73ec29c5803bd --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_diff/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-both-diff" +version = "0.1.0" +rust-version = "1.56" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_diff/clippy.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_diff/clippy.toml new file mode 100644 index 0000000000000..abe19b3a00710 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_diff/clippy.toml @@ -0,0 +1 @@ +msrv = "1.59" diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.rs new file mode 100644 index 0000000000000..5b91d5508678f --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.stderr b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.stderr new file mode 100644 index 0000000000000..9a7d802dc6d3a --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_diff/src/main.stderr @@ -0,0 +1,16 @@ +warning: the MSRV in `clippy.toml` and `Cargo.toml` differ; using `1.59.0` from `clippy.toml` + +error: unnecessary structure name repetition + --> $DIR/main.rs:6:21 + | +LL | pub fn bar() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + | +note: the lint level is defined here + --> $DIR/main.rs:1:9 + | +LL | #![deny(clippy::use_self)] + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error; 1 warning emitted + diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_same/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_same/Cargo.toml new file mode 100644 index 0000000000000..2d6d547e4fe3a --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_same/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-both-same" +version = "0.1.0" +rust-version = "1.57.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_same/clippy.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_same/clippy.toml new file mode 100644 index 0000000000000..5cccb362c1421 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_same/clippy.toml @@ -0,0 +1 @@ +msrv = "1.57" diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.rs new file mode 100644 index 0000000000000..5b91d5508678f --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.stderr b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.stderr new file mode 100644 index 0000000000000..a280e1bacdfdc --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_both_same/src/main.stderr @@ -0,0 +1,14 @@ +error: unnecessary structure name repetition + --> $DIR/main.rs:6:21 + | +LL | pub fn bar() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + | +note: the lint level is defined here + --> $DIR/main.rs:1:9 + | +LL | #![deny(clippy::use_self)] + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_cargo/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_cargo/Cargo.toml new file mode 100644 index 0000000000000..36a53bd829d9e --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_cargo/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-cargo" +version = "0.1.0" +rust-version = "1.56.1" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.rs new file mode 100644 index 0000000000000..5b91d5508678f --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.stderr b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.stderr new file mode 100644 index 0000000000000..a280e1bacdfdc --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_cargo/src/main.stderr @@ -0,0 +1,14 @@ +error: unnecessary structure name repetition + --> $DIR/main.rs:6:21 + | +LL | pub fn bar() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + | +note: the lint level is defined here + --> $DIR/main.rs:1:9 + | +LL | #![deny(clippy::use_self)] + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_clippy/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_clippy/Cargo.toml new file mode 100644 index 0000000000000..9f644a1a39a50 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_clippy/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "fail-clippy" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_clippy/clippy.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_clippy/clippy.toml new file mode 100644 index 0000000000000..ddbdbc1fa2590 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_clippy/clippy.toml @@ -0,0 +1 @@ +msrv = "1.58" diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.rs new file mode 100644 index 0000000000000..5b91d5508678f --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.stderr b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.stderr new file mode 100644 index 0000000000000..a280e1bacdfdc --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_clippy/src/main.stderr @@ -0,0 +1,14 @@ +error: unnecessary structure name repetition + --> $DIR/main.rs:6:21 + | +LL | pub fn bar() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + | +note: the lint level is defined here + --> $DIR/main.rs:1:9 + | +LL | #![deny(clippy::use_self)] + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_file_attr/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_file_attr/Cargo.toml new file mode 100644 index 0000000000000..5380e993b2936 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_file_attr/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-file-attr" +version = "0.1.0" +rust-version = "1.13" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_file_attr/clippy.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_file_attr/clippy.toml new file mode 100644 index 0000000000000..ea5d806594b5e --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_file_attr/clippy.toml @@ -0,0 +1 @@ +msrv = "1.13.0" diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.rs new file mode 100644 index 0000000000000..bcbffa82a5417 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.rs @@ -0,0 +1,16 @@ +// FIXME: this should produce a warning, because the attribute says 1.58 and the cargo.toml file +// says 1.13 + +#![feature(custom_inner_attributes)] +#![clippy::msrv = "1.58.0"] +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.stderr b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.stderr new file mode 100644 index 0000000000000..88f6e00922bc4 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/fail_file_attr/src/main.stderr @@ -0,0 +1,14 @@ +error: unnecessary structure name repetition + --> $DIR/main.rs:11:21 + | +LL | pub fn bar() -> Foo { + | ^^^ help: use the applicable keyword: `Self` + | +note: the lint level is defined here + --> $DIR/main.rs:6:9 + | +LL | #![deny(clippy::use_self)] + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_both_same/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_both_same/Cargo.toml new file mode 100644 index 0000000000000..1f9bd8f9a84e3 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_both_same/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-both-same" +version = "0.1.0" +rust-version = "1.13.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_both_same/clippy.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_both_same/clippy.toml new file mode 100644 index 0000000000000..5e8e48b636b63 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_both_same/clippy.toml @@ -0,0 +1 @@ +msrv = "1.13" diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_both_same/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_both_same/src/main.rs new file mode 100644 index 0000000000000..5b91d5508678f --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_both_same/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_cargo/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_cargo/Cargo.toml new file mode 100644 index 0000000000000..77538027c0f87 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_cargo/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-cargo" +version = "0.1.0" +rust-version = "1.13.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_cargo/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_cargo/src/main.rs new file mode 100644 index 0000000000000..5b91d5508678f --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_cargo/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_clippy/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_clippy/Cargo.toml new file mode 100644 index 0000000000000..9f644a1a39a50 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_clippy/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "fail-clippy" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_clippy/clippy.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_clippy/clippy.toml new file mode 100644 index 0000000000000..5e8e48b636b63 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_clippy/clippy.toml @@ -0,0 +1 @@ +msrv = "1.13" diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_clippy/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_clippy/src/main.rs new file mode 100644 index 0000000000000..5b91d5508678f --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_clippy/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_file_attr/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_file_attr/Cargo.toml new file mode 100644 index 0000000000000..f0387cd90b80f --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_file_attr/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fail-file-attr" +version = "0.1.0" +rust-version = "1.59" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_file_attr/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_file_attr/src/main.rs new file mode 100644 index 0000000000000..27fe4771d2d6b --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/pass_file_attr/src/main.rs @@ -0,0 +1,13 @@ +#![feature(custom_inner_attributes)] +#![clippy::msrv = "1.13.0"] +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/warn_both_diff/Cargo.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/warn_both_diff/Cargo.toml new file mode 100644 index 0000000000000..a19d5b33fe563 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/warn_both_diff/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "warn-both-diff" +version = "0.1.0" +rust-version = "1.56.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/warn_both_diff/clippy.toml b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/warn_both_diff/clippy.toml new file mode 100644 index 0000000000000..5e8e48b636b63 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/warn_both_diff/clippy.toml @@ -0,0 +1 @@ +msrv = "1.13" diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.rs b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.rs new file mode 100644 index 0000000000000..5b91d5508678f --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.rs @@ -0,0 +1,11 @@ +#![deny(clippy::use_self)] + +pub struct Foo; + +impl Foo { + pub fn bar() -> Foo { + Foo + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.stderr b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.stderr new file mode 100644 index 0000000000000..eeae5b7b275e9 --- /dev/null +++ b/src/tools/clippy/tests/ui-cargo/cargo_rust_version/warn_both_diff/src/main.stderr @@ -0,0 +1,4 @@ +warning: the MSRV in `clippy.toml` and `Cargo.toml` differ; using `1.13.0` from `clippy.toml` + +warning: 1 warning emitted + diff --git a/src/tools/clippy/tests/ui-cargo/multiple_config_files/warn/src/main.stderr b/src/tools/clippy/tests/ui-cargo/multiple_config_files/warn/src/main.stderr index 2abb4e3e06e64..98697e001f99a 100644 --- a/src/tools/clippy/tests/ui-cargo/multiple_config_files/warn/src/main.stderr +++ b/src/tools/clippy/tests/ui-cargo/multiple_config_files/warn/src/main.stderr @@ -1,2 +1,2 @@ -Using config file `$SRC_DIR/tests/ui-cargo/multiple_config_files/warn/.clippy.toml` -Warning: `$SRC_DIR/tests/ui-cargo/multiple_config_files/warn/clippy.toml` will be ignored. +Using config file `$SRC_DIR/.clippy.toml` +Warning: `$SRC_DIR/clippy.toml` will be ignored. diff --git a/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.fixed b/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.fixed index a5a6f20ddd54a..9f299d7dec720 100644 --- a/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.fixed +++ b/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.fixed @@ -45,7 +45,12 @@ impl EarlyLintPass for Pass { if predicate { db.note(note_msg); } - }) + }); + + // Issue #8798 + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.help(help_msg).help(help_msg); + }); } } diff --git a/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.rs b/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.rs index 6d783aa0786bd..2b113f555e46b 100644 --- a/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.rs +++ b/src/tools/clippy/tests/ui-internal/collapsible_span_lint_calls.rs @@ -55,7 +55,12 @@ impl EarlyLintPass for Pass { if predicate { db.note(note_msg); } - }) + }); + + // Issue #8798 + span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { + db.help(help_msg).help(help_msg); + }); } } diff --git a/src/tools/clippy/tests/ui-internal/default_deprecation_reason.rs b/src/tools/clippy/tests/ui-internal/default_deprecation_reason.rs new file mode 100644 index 0000000000000..c8961d5e1f0bd --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/default_deprecation_reason.rs @@ -0,0 +1,30 @@ +#![deny(clippy::internal)] +#![feature(rustc_private)] + +#[macro_use] +extern crate clippy_lints; +use clippy_lints::deprecated_lints::ClippyDeprecatedLint; + +declare_deprecated_lint! { + /// ### What it does + /// Nothing. This lint has been deprecated. + /// + /// ### Deprecation reason + /// TODO + #[clippy::version = "1.63.0"] + pub COOL_LINT_DEFAULT, + "default deprecation note" +} + +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`" +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui-internal/default_deprecation_reason.stderr b/src/tools/clippy/tests/ui-internal/default_deprecation_reason.stderr new file mode 100644 index 0000000000000..ca26b649f986a --- /dev/null +++ b/src/tools/clippy/tests/ui-internal/default_deprecation_reason.stderr @@ -0,0 +1,22 @@ +error: the lint `COOL_LINT_DEFAULT` has the default deprecation reason + --> $DIR/default_deprecation_reason.rs:8:1 + | +LL | / declare_deprecated_lint! { +LL | | /// ### What it does +LL | | /// Nothing. This lint has been deprecated. +LL | | /// +... | +LL | | "default deprecation note" +LL | | } + | |_^ + | +note: the lint level is defined here + --> $DIR/default_deprecation_reason.rs:1:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::default_deprecation_reason)]` implied by `#[deny(clippy::internal)]` + = note: this error originates in the macro `declare_deprecated_lint` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs b/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs index 9f283337c7e13..5bd2c2799f036 100644 --- a/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs +++ b/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs @@ -127,3 +127,11 @@ macro_rules! ptr_as_ptr_cast { $ptr as *const i32 }; } + +#[macro_export] +macro_rules! manual_rem_euclid { + () => { + let value: i32 = 5; + let _: i32 = ((value % 4) + 4) % 4; + }; +} diff --git a/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs b/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs index ed7b17651e675..a89a06308d093 100644 --- a/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs +++ b/src/tools/clippy/tests/ui/auxiliary/proc_macro_derive.rs @@ -72,3 +72,17 @@ pub fn mini_macro(_: TokenStream) -> TokenStream { } ) } + +#[proc_macro_derive(ExtraLifetimeDerive)] +#[allow(unused)] +pub fn extra_lifetime(_input: TokenStream) -> TokenStream { + quote!( + pub struct ExtraLifetime; + + impl<'b> ExtraLifetime { + pub fn something<'c>() -> Self { + Self + } + } + ) +} diff --git a/src/tools/clippy/tests/ui/escape_analysis.rs b/src/tools/clippy/tests/ui/boxed_local.rs similarity index 97% rename from src/tools/clippy/tests/ui/escape_analysis.rs rename to src/tools/clippy/tests/ui/boxed_local.rs index 13e2b6c7a2e76..4639f00a8d830 100644 --- a/src/tools/clippy/tests/ui/escape_analysis.rs +++ b/src/tools/clippy/tests/ui/boxed_local.rs @@ -1,4 +1,5 @@ #![feature(box_syntax)] +#![feature(lint_reasons)] #![allow( clippy::borrowed_box, clippy::needless_pass_by_value, @@ -202,3 +203,7 @@ mod issue4804 { fn foo(x: Box) {} } } + +fn check_expect(#[expect(clippy::boxed_local)] x: Box) { + x.foo(); +} diff --git a/src/tools/clippy/tests/ui/escape_analysis.stderr b/src/tools/clippy/tests/ui/boxed_local.stderr similarity index 81% rename from src/tools/clippy/tests/ui/escape_analysis.stderr rename to src/tools/clippy/tests/ui/boxed_local.stderr index 4a82b4419f997..9036529f39c51 100644 --- a/src/tools/clippy/tests/ui/escape_analysis.stderr +++ b/src/tools/clippy/tests/ui/boxed_local.stderr @@ -1,5 +1,5 @@ error: local variable doesn't need to be boxed here - --> $DIR/escape_analysis.rs:40:13 + --> $DIR/boxed_local.rs:41:13 | LL | fn warn_arg(x: Box) { | ^ @@ -7,19 +7,19 @@ LL | fn warn_arg(x: Box) { = note: `-D clippy::boxed-local` implied by `-D warnings` error: local variable doesn't need to be boxed here - --> $DIR/escape_analysis.rs:131:12 + --> $DIR/boxed_local.rs:132:12 | LL | pub fn new(_needs_name: Box>) -> () {} | ^^^^^^^^^^^ error: local variable doesn't need to be boxed here - --> $DIR/escape_analysis.rs:195:44 + --> $DIR/boxed_local.rs:196:44 | LL | fn default_impl_x(self: Box, x: Box) -> u32 { | ^ error: local variable doesn't need to be boxed here - --> $DIR/escape_analysis.rs:202:16 + --> $DIR/boxed_local.rs:203:16 | LL | fn foo(x: Box) {} | ^ diff --git a/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.rs b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.rs index ee3fdfabe9d8a..82dce81979fd9 100644 --- a/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.rs +++ b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] #![allow(clippy::if_same_then_else, clippy::branches_sharing_code)] @@ -84,3 +85,18 @@ fn main() { assert!(x.is_ok(), "{:?}", x.unwrap_err()); // ok, it's a common test pattern } + +fn check_expect() { + let x = Some(()); + if x.is_some() { + #[expect(clippy::unnecessary_unwrap)] + x.unwrap(); // unnecessary + #[expect(clippy::unnecessary_unwrap)] + x.expect("an error message"); // unnecessary + } else { + #[expect(clippy::panicking_unwrap)] + x.unwrap(); // will panic + #[expect(clippy::panicking_unwrap)] + x.expect("an error message"); // will panic + } +} diff --git a/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr index 3413159280217..ef68827422233 100644 --- a/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr +++ b/src/tools/clippy/tests/ui/checked_unwrap/simple_conditionals.stderr @@ -1,5 +1,5 @@ error: called `unwrap` on `x` after checking its variant with `is_some` - --> $DIR/simple_conditionals.rs:39:9 + --> $DIR/simple_conditionals.rs:40:9 | LL | if x.is_some() { | -------------- help: try: `if let Some(..) = x` @@ -7,13 +7,13 @@ LL | x.unwrap(); // unnecessary | ^^^^^^^^^^ | note: the lint level is defined here - --> $DIR/simple_conditionals.rs:1:35 + --> $DIR/simple_conditionals.rs:2:35 | LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: called `expect` on `x` after checking its variant with `is_some` - --> $DIR/simple_conditionals.rs:40:9 + --> $DIR/simple_conditionals.rs:41:9 | LL | if x.is_some() { | -------------- help: try: `if let Some(..) = x` @@ -22,7 +22,7 @@ LL | x.expect("an error message"); // unnecessary | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> $DIR/simple_conditionals.rs:42:9 + --> $DIR/simple_conditionals.rs:43:9 | LL | if x.is_some() { | ----------- because of this check @@ -31,13 +31,13 @@ LL | x.unwrap(); // will panic | ^^^^^^^^^^ | note: the lint level is defined here - --> $DIR/simple_conditionals.rs:1:9 + --> $DIR/simple_conditionals.rs:2:9 | LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `expect()` will always panic - --> $DIR/simple_conditionals.rs:43:9 + --> $DIR/simple_conditionals.rs:44:9 | LL | if x.is_some() { | ----------- because of this check @@ -46,7 +46,7 @@ LL | x.expect("an error message"); // will panic | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> $DIR/simple_conditionals.rs:46:9 + --> $DIR/simple_conditionals.rs:47:9 | LL | if x.is_none() { | ----------- because of this check @@ -54,7 +54,7 @@ LL | x.unwrap(); // will panic | ^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_none` - --> $DIR/simple_conditionals.rs:48:9 + --> $DIR/simple_conditionals.rs:49:9 | LL | if x.is_none() { | -------------- help: try: `if let Some(..) = x` @@ -63,7 +63,7 @@ LL | x.unwrap(); // unnecessary | ^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_some` - --> $DIR/simple_conditionals.rs:7:13 + --> $DIR/simple_conditionals.rs:8:13 | LL | if $a.is_some() { | --------------- help: try: `if let Some(..) = x` @@ -76,7 +76,7 @@ LL | m!(x); = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: called `unwrap` on `x` after checking its variant with `is_ok` - --> $DIR/simple_conditionals.rs:56:9 + --> $DIR/simple_conditionals.rs:57:9 | LL | if x.is_ok() { | ------------ help: try: `if let Ok(..) = x` @@ -84,7 +84,7 @@ LL | x.unwrap(); // unnecessary | ^^^^^^^^^^ error: called `expect` on `x` after checking its variant with `is_ok` - --> $DIR/simple_conditionals.rs:57:9 + --> $DIR/simple_conditionals.rs:58:9 | LL | if x.is_ok() { | ------------ help: try: `if let Ok(..) = x` @@ -93,7 +93,7 @@ LL | x.expect("an error message"); // unnecessary | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap_err()` will always panic - --> $DIR/simple_conditionals.rs:58:9 + --> $DIR/simple_conditionals.rs:59:9 | LL | if x.is_ok() { | --------- because of this check @@ -102,7 +102,7 @@ LL | x.unwrap_err(); // will panic | ^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> $DIR/simple_conditionals.rs:60:9 + --> $DIR/simple_conditionals.rs:61:9 | LL | if x.is_ok() { | --------- because of this check @@ -111,7 +111,7 @@ LL | x.unwrap(); // will panic | ^^^^^^^^^^ error: this call to `expect()` will always panic - --> $DIR/simple_conditionals.rs:61:9 + --> $DIR/simple_conditionals.rs:62:9 | LL | if x.is_ok() { | --------- because of this check @@ -120,7 +120,7 @@ LL | x.expect("an error message"); // will panic | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap_err` on `x` after checking its variant with `is_ok` - --> $DIR/simple_conditionals.rs:62:9 + --> $DIR/simple_conditionals.rs:63:9 | LL | if x.is_ok() { | ------------ help: try: `if let Err(..) = x` @@ -129,7 +129,7 @@ LL | x.unwrap_err(); // unnecessary | ^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> $DIR/simple_conditionals.rs:65:9 + --> $DIR/simple_conditionals.rs:66:9 | LL | if x.is_err() { | ---------- because of this check @@ -137,7 +137,7 @@ LL | x.unwrap(); // will panic | ^^^^^^^^^^ error: called `unwrap_err` on `x` after checking its variant with `is_err` - --> $DIR/simple_conditionals.rs:66:9 + --> $DIR/simple_conditionals.rs:67:9 | LL | if x.is_err() { | ------------- help: try: `if let Err(..) = x` @@ -146,7 +146,7 @@ LL | x.unwrap_err(); // unnecessary | ^^^^^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_err` - --> $DIR/simple_conditionals.rs:68:9 + --> $DIR/simple_conditionals.rs:69:9 | LL | if x.is_err() { | ------------- help: try: `if let Ok(..) = x` @@ -155,7 +155,7 @@ LL | x.unwrap(); // unnecessary | ^^^^^^^^^^ error: this call to `unwrap_err()` will always panic - --> $DIR/simple_conditionals.rs:69:9 + --> $DIR/simple_conditionals.rs:70:9 | LL | if x.is_err() { | ---------- because of this check diff --git a/src/tools/clippy/tests/ui/declare_interior_mutable_const/others.rs b/src/tools/clippy/tests/ui/declare_interior_mutable_const/others.rs index 48c5e9537d6d0..62af545db503b 100644 --- a/src/tools/clippy/tests/ui/declare_interior_mutable_const/others.rs +++ b/src/tools/clippy/tests/ui/declare_interior_mutable_const/others.rs @@ -31,4 +31,9 @@ const NO_ANN: &dyn Display = &70; static STATIC_TUPLE: (AtomicUsize, String) = (ATOMIC, STRING); //^ there should be no lints on this line +// issue #8493 +thread_local! { + static THREAD_LOCAL: Cell = const { Cell::new(0) }; +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/default_instead_of_iter_empty.fixed b/src/tools/clippy/tests/ui/default_instead_of_iter_empty.fixed new file mode 100644 index 0000000000000..f1abfdcd6ce69 --- /dev/null +++ b/src/tools/clippy/tests/ui/default_instead_of_iter_empty.fixed @@ -0,0 +1,21 @@ +// run-rustfix +#![warn(clippy::default_instead_of_iter_empty)] +#![allow(dead_code)] +use std::collections::HashMap; + +#[derive(Default)] +struct Iter { + iter: std::iter::Empty, +} + +fn main() { + // Do lint. + let _ = std::iter::empty::(); + let _ = std::iter::empty::>(); + let _foo: std::iter::Empty = std::iter::empty(); + + // Do not lint. + let _ = Vec::::default(); + let _ = String::default(); + let _ = Iter::default(); +} diff --git a/src/tools/clippy/tests/ui/default_instead_of_iter_empty.rs b/src/tools/clippy/tests/ui/default_instead_of_iter_empty.rs new file mode 100644 index 0000000000000..2630519c46da4 --- /dev/null +++ b/src/tools/clippy/tests/ui/default_instead_of_iter_empty.rs @@ -0,0 +1,21 @@ +// run-rustfix +#![warn(clippy::default_instead_of_iter_empty)] +#![allow(dead_code)] +use std::collections::HashMap; + +#[derive(Default)] +struct Iter { + iter: std::iter::Empty, +} + +fn main() { + // Do lint. + let _ = std::iter::Empty::::default(); + let _ = std::iter::Empty::>::default(); + let _foo: std::iter::Empty = std::iter::Empty::default(); + + // Do not lint. + let _ = Vec::::default(); + let _ = String::default(); + let _ = Iter::default(); +} diff --git a/src/tools/clippy/tests/ui/default_instead_of_iter_empty.stderr b/src/tools/clippy/tests/ui/default_instead_of_iter_empty.stderr new file mode 100644 index 0000000000000..460fc84def8a5 --- /dev/null +++ b/src/tools/clippy/tests/ui/default_instead_of_iter_empty.stderr @@ -0,0 +1,22 @@ +error: `std::iter::empty()` is the more idiomatic way + --> $DIR/default_instead_of_iter_empty.rs:13:13 + | +LL | let _ = std::iter::Empty::::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::iter::empty::()` + | + = note: `-D clippy::default-instead-of-iter-empty` implied by `-D warnings` + +error: `std::iter::empty()` is the more idiomatic way + --> $DIR/default_instead_of_iter_empty.rs:14:13 + | +LL | let _ = std::iter::Empty::>::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::iter::empty::>()` + +error: `std::iter::empty()` is the more idiomatic way + --> $DIR/default_instead_of_iter_empty.rs:15:41 + | +LL | let _foo: std::iter::Empty = std::iter::Empty::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::iter::empty()` + +error: aborting due to 3 previous errors + diff --git a/src/tools/clippy/tests/ui/enum_variants.rs b/src/tools/clippy/tests/ui/enum_variants.rs index b2bf7c4e360a0..efed12ee2ef6f 100644 --- a/src/tools/clippy/tests/ui/enum_variants.rs +++ b/src/tools/clippy/tests/ui/enum_variants.rs @@ -158,4 +158,25 @@ enum Phase { PostLookup, } +mod issue9018 { + enum DoLint { + _TypeCreate, + _TypeRead, + _TypeUpdate, + _TypeDestroy, + } + + enum DoLintToo { + _CreateType, + _UpdateType, + _DeleteType, + } + + enum DoNotLint { + _Foo, + _Bar, + _Baz, + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/enum_variants.stderr b/src/tools/clippy/tests/ui/enum_variants.stderr index 8a3265086e84f..7342aff80f016 100644 --- a/src/tools/clippy/tests/ui/enum_variants.stderr +++ b/src/tools/clippy/tests/ui/enum_variants.stderr @@ -120,5 +120,30 @@ LL | | } | = help: remove the postfixes and use full paths to the variants instead of glob imports -error: aborting due to 12 previous errors +error: all variants have the same prefix: `_Type` + --> $DIR/enum_variants.rs:162:5 + | +LL | / enum DoLint { +LL | | _TypeCreate, +LL | | _TypeRead, +LL | | _TypeUpdate, +LL | | _TypeDestroy, +LL | | } + | |_____^ + | + = help: remove the prefixes and use full paths to the variants instead of glob imports + +error: all variants have the same postfix: `Type` + --> $DIR/enum_variants.rs:169:5 + | +LL | / enum DoLintToo { +LL | | _CreateType, +LL | | _UpdateType, +LL | | _DeleteType, +LL | | } + | |_____^ + | + = help: remove the postfixes and use full paths to the variants instead of glob imports + +error: aborting due to 14 previous errors diff --git a/src/tools/clippy/tests/ui/explicit_auto_deref.fixed b/src/tools/clippy/tests/ui/explicit_auto_deref.fixed new file mode 100644 index 0000000000000..d4ff1b1566dc6 --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_auto_deref.fixed @@ -0,0 +1,214 @@ +// run-rustfix + +#![warn(clippy::explicit_auto_deref)] +#![allow( + dead_code, + unused_braces, + clippy::borrowed_box, + clippy::needless_borrow, + clippy::needless_return, + clippy::ptr_arg, + clippy::redundant_field_names, + clippy::too_many_arguments, + clippy::borrow_deref_ref, + clippy::let_unit_value +)] + +trait CallableStr { + type T: Fn(&str); + fn callable_str(&self) -> Self::T; +} +impl CallableStr for () { + type T = fn(&str); + fn callable_str(&self) -> Self::T { + fn f(_: &str) {} + f + } +} +impl CallableStr for i32 { + type T = <() as CallableStr>::T; + fn callable_str(&self) -> Self::T { + ().callable_str() + } +} + +trait CallableT { + type T: Fn(&U); + fn callable_t(&self) -> Self::T; +} +impl CallableT for () { + type T = fn(&U); + fn callable_t(&self) -> Self::T { + fn f(_: &U) {} + f:: + } +} +impl CallableT for i32 { + type T = <() as CallableT>::T; + fn callable_t(&self) -> Self::T { + ().callable_t() + } +} + +fn f_str(_: &str) {} +fn f_string(_: &String) {} +fn f_t(_: T) {} +fn f_ref_t(_: &T) {} + +fn f_str_t(_: &str, _: T) {} + +fn f_box_t(_: &Box) {} + +extern "C" { + fn var(_: u32, ...); +} + +fn main() { + let s = String::new(); + + let _: &str = &s; + let _ = &*s; // Don't lint. Inferred type would change. + let _: &_ = &*s; // Don't lint. Inferred type would change. + + f_str(&s); + f_t(&*s); // Don't lint. Inferred type would change. + f_ref_t(&*s); // Don't lint. Inferred type would change. + + f_str_t(&s, &*s); // Don't lint second param. + + let b = Box::new(Box::new(Box::new(5))); + let _: &Box = &b; + let _: &Box<_> = &**b; // Don't lint. Inferred type would change. + + f_box_t(&**b); // Don't lint. Inferred type would change. + + let c = |_x: &str| (); + c(&s); + + let c = |_x| (); + c(&*s); // Don't lint. Inferred type would change. + + fn _f(x: &String) -> &str { + x + } + + fn _f1(x: &String) -> &str { + { x } + } + + fn _f2(x: &String) -> &str { + { x } + } + + fn _f3(x: &Box>>) -> &Box { + x + } + + fn _f4( + x: String, + f1: impl Fn(&str), + f2: &dyn Fn(&str), + f3: fn(&str), + f4: impl CallableStr, + f5: <() as CallableStr>::T, + f6: ::T, + f7: &dyn CallableStr, + f8: impl CallableT, + f9: <() as CallableT>::T, + f10: >::T, + f11: &dyn CallableT, + ) { + f1(&x); + f2(&x); + f3(&x); + f4.callable_str()(&x); + f5(&x); + f6(&x); + f7.callable_str()(&x); + f8.callable_t()(&x); + f9(&x); + f10(&x); + f11.callable_t()(&x); + } + + struct S1<'a>(&'a str); + let _ = S1(&s); + + struct S2<'a> { + s: &'a str, + } + let _ = S2 { s: &s }; + + struct S3<'a, T: ?Sized>(&'a T); + let _ = S3(&*s); // Don't lint. Inferred type would change. + + struct S4<'a, T: ?Sized> { + s: &'a T, + } + let _ = S4 { s: &*s }; // Don't lint. Inferred type would change. + + enum E1<'a> { + S1(&'a str), + S2 { s: &'a str }, + } + impl<'a> E1<'a> { + fn m1(s: &'a String) { + let _ = Self::S1(s); + let _ = Self::S2 { s: s }; + } + } + let _ = E1::S1(&s); + let _ = E1::S2 { s: &s }; + + enum E2<'a, T: ?Sized> { + S1(&'a T), + S2 { s: &'a T }, + } + let _ = E2::S1(&*s); // Don't lint. Inferred type would change. + let _ = E2::S2 { s: &*s }; // Don't lint. Inferred type would change. + + let ref_s = &s; + let _: &String = &*ref_s; // Don't lint reborrow. + f_string(&*ref_s); // Don't lint reborrow. + + struct S5 { + foo: u32, + } + let b = Box::new(Box::new(S5 { foo: 5 })); + let _ = b.foo; + let _ = b.foo; + let _ = b.foo; + + struct S6 { + foo: S5, + } + impl core::ops::Deref for S6 { + type Target = S5; + fn deref(&self) -> &Self::Target { + &self.foo + } + } + let s6 = S6 { foo: S5 { foo: 5 } }; + let _ = (*s6).foo; // Don't lint. `S6` also has a field named `foo` + + let ref_str = &"foo"; + let _ = f_str(ref_str); + let ref_ref_str = &ref_str; + let _ = f_str(ref_ref_str); + + fn _f5(x: &u32) -> u32 { + if true { + *x + } else { + return *x; + } + } + + f_str(&&ref_str); // `needless_borrow` will suggest removing both references + f_str(&ref_str); // `needless_borrow` will suggest removing only one reference + + let x = &&40; + unsafe { + var(0, &**x); + } +} diff --git a/src/tools/clippy/tests/ui/explicit_auto_deref.rs b/src/tools/clippy/tests/ui/explicit_auto_deref.rs new file mode 100644 index 0000000000000..99294a7947bf1 --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_auto_deref.rs @@ -0,0 +1,214 @@ +// run-rustfix + +#![warn(clippy::explicit_auto_deref)] +#![allow( + dead_code, + unused_braces, + clippy::borrowed_box, + clippy::needless_borrow, + clippy::needless_return, + clippy::ptr_arg, + clippy::redundant_field_names, + clippy::too_many_arguments, + clippy::borrow_deref_ref, + clippy::let_unit_value +)] + +trait CallableStr { + type T: Fn(&str); + fn callable_str(&self) -> Self::T; +} +impl CallableStr for () { + type T = fn(&str); + fn callable_str(&self) -> Self::T { + fn f(_: &str) {} + f + } +} +impl CallableStr for i32 { + type T = <() as CallableStr>::T; + fn callable_str(&self) -> Self::T { + ().callable_str() + } +} + +trait CallableT { + type T: Fn(&U); + fn callable_t(&self) -> Self::T; +} +impl CallableT for () { + type T = fn(&U); + fn callable_t(&self) -> Self::T { + fn f(_: &U) {} + f:: + } +} +impl CallableT for i32 { + type T = <() as CallableT>::T; + fn callable_t(&self) -> Self::T { + ().callable_t() + } +} + +fn f_str(_: &str) {} +fn f_string(_: &String) {} +fn f_t(_: T) {} +fn f_ref_t(_: &T) {} + +fn f_str_t(_: &str, _: T) {} + +fn f_box_t(_: &Box) {} + +extern "C" { + fn var(_: u32, ...); +} + +fn main() { + let s = String::new(); + + let _: &str = &*s; + let _ = &*s; // Don't lint. Inferred type would change. + let _: &_ = &*s; // Don't lint. Inferred type would change. + + f_str(&*s); + f_t(&*s); // Don't lint. Inferred type would change. + f_ref_t(&*s); // Don't lint. Inferred type would change. + + f_str_t(&*s, &*s); // Don't lint second param. + + let b = Box::new(Box::new(Box::new(5))); + let _: &Box = &**b; + let _: &Box<_> = &**b; // Don't lint. Inferred type would change. + + f_box_t(&**b); // Don't lint. Inferred type would change. + + let c = |_x: &str| (); + c(&*s); + + let c = |_x| (); + c(&*s); // Don't lint. Inferred type would change. + + fn _f(x: &String) -> &str { + &**x + } + + fn _f1(x: &String) -> &str { + { &**x } + } + + fn _f2(x: &String) -> &str { + &**{ x } + } + + fn _f3(x: &Box>>) -> &Box { + &***x + } + + fn _f4( + x: String, + f1: impl Fn(&str), + f2: &dyn Fn(&str), + f3: fn(&str), + f4: impl CallableStr, + f5: <() as CallableStr>::T, + f6: ::T, + f7: &dyn CallableStr, + f8: impl CallableT, + f9: <() as CallableT>::T, + f10: >::T, + f11: &dyn CallableT, + ) { + f1(&*x); + f2(&*x); + f3(&*x); + f4.callable_str()(&*x); + f5(&*x); + f6(&*x); + f7.callable_str()(&*x); + f8.callable_t()(&*x); + f9(&*x); + f10(&*x); + f11.callable_t()(&*x); + } + + struct S1<'a>(&'a str); + let _ = S1(&*s); + + struct S2<'a> { + s: &'a str, + } + let _ = S2 { s: &*s }; + + struct S3<'a, T: ?Sized>(&'a T); + let _ = S3(&*s); // Don't lint. Inferred type would change. + + struct S4<'a, T: ?Sized> { + s: &'a T, + } + let _ = S4 { s: &*s }; // Don't lint. Inferred type would change. + + enum E1<'a> { + S1(&'a str), + S2 { s: &'a str }, + } + impl<'a> E1<'a> { + fn m1(s: &'a String) { + let _ = Self::S1(&**s); + let _ = Self::S2 { s: &**s }; + } + } + let _ = E1::S1(&*s); + let _ = E1::S2 { s: &*s }; + + enum E2<'a, T: ?Sized> { + S1(&'a T), + S2 { s: &'a T }, + } + let _ = E2::S1(&*s); // Don't lint. Inferred type would change. + let _ = E2::S2 { s: &*s }; // Don't lint. Inferred type would change. + + let ref_s = &s; + let _: &String = &*ref_s; // Don't lint reborrow. + f_string(&*ref_s); // Don't lint reborrow. + + struct S5 { + foo: u32, + } + let b = Box::new(Box::new(S5 { foo: 5 })); + let _ = b.foo; + let _ = (*b).foo; + let _ = (**b).foo; + + struct S6 { + foo: S5, + } + impl core::ops::Deref for S6 { + type Target = S5; + fn deref(&self) -> &Self::Target { + &self.foo + } + } + let s6 = S6 { foo: S5 { foo: 5 } }; + let _ = (*s6).foo; // Don't lint. `S6` also has a field named `foo` + + let ref_str = &"foo"; + let _ = f_str(*ref_str); + let ref_ref_str = &ref_str; + let _ = f_str(**ref_ref_str); + + fn _f5(x: &u32) -> u32 { + if true { + *x + } else { + return *x; + } + } + + f_str(&&*ref_str); // `needless_borrow` will suggest removing both references + f_str(&&**ref_str); // `needless_borrow` will suggest removing only one reference + + let x = &&40; + unsafe { + var(0, &**x); + } +} diff --git a/src/tools/clippy/tests/ui/explicit_auto_deref.stderr b/src/tools/clippy/tests/ui/explicit_auto_deref.stderr new file mode 100644 index 0000000000000..55f956e37aed7 --- /dev/null +++ b/src/tools/clippy/tests/ui/explicit_auto_deref.stderr @@ -0,0 +1,196 @@ +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:69:20 + | +LL | let _: &str = &*s; + | ^^ help: try this: `s` + | + = note: `-D clippy::explicit-auto-deref` implied by `-D warnings` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:73:12 + | +LL | f_str(&*s); + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:77:14 + | +LL | f_str_t(&*s, &*s); // Don't lint second param. + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:80:25 + | +LL | let _: &Box = &**b; + | ^^^ help: try this: `b` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:86:8 + | +LL | c(&*s); + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:92:9 + | +LL | &**x + | ^^^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:96:11 + | +LL | { &**x } + | ^^^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:100:9 + | +LL | &**{ x } + | ^^^^^^^^ help: try this: `{ x }` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:104:9 + | +LL | &***x + | ^^^^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:121:13 + | +LL | f1(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:122:13 + | +LL | f2(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:123:13 + | +LL | f3(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:124:28 + | +LL | f4.callable_str()(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:125:13 + | +LL | f5(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:126:13 + | +LL | f6(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:127:28 + | +LL | f7.callable_str()(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:128:26 + | +LL | f8.callable_t()(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:129:13 + | +LL | f9(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:130:14 + | +LL | f10(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:131:27 + | +LL | f11.callable_t()(&*x); + | ^^ help: try this: `x` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:135:17 + | +LL | let _ = S1(&*s); + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:140:22 + | +LL | let _ = S2 { s: &*s }; + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:156:30 + | +LL | let _ = Self::S1(&**s); + | ^^^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:157:35 + | +LL | let _ = Self::S2 { s: &**s }; + | ^^^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:160:21 + | +LL | let _ = E1::S1(&*s); + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:161:26 + | +LL | let _ = E1::S2 { s: &*s }; + | ^^ help: try this: `s` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:179:13 + | +LL | let _ = (*b).foo; + | ^^^^ help: try this: `b` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:180:13 + | +LL | let _ = (**b).foo; + | ^^^^^ help: try this: `b` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:195:19 + | +LL | let _ = f_str(*ref_str); + | ^^^^^^^^ help: try this: `ref_str` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:197:19 + | +LL | let _ = f_str(**ref_ref_str); + | ^^^^^^^^^^^^^ help: try this: `ref_ref_str` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:207:13 + | +LL | f_str(&&*ref_str); // `needless_borrow` will suggest removing both references + | ^^^^^^^^ help: try this: `ref_str` + +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:208:12 + | +LL | f_str(&&**ref_str); // `needless_borrow` will suggest removing only one reference + | ^^^^^^^^^^ help: try this: `ref_str` + +error: aborting due to 32 previous errors + diff --git a/src/tools/clippy/tests/ui/explicit_deref_methods.fixed b/src/tools/clippy/tests/ui/explicit_deref_methods.fixed index 92f27e68549a3..523cae183ee6e 100644 --- a/src/tools/clippy/tests/ui/explicit_deref_methods.fixed +++ b/src/tools/clippy/tests/ui/explicit_deref_methods.fixed @@ -4,7 +4,8 @@ unused_variables, clippy::clone_double_ref, clippy::needless_borrow, - clippy::borrow_deref_ref + clippy::borrow_deref_ref, + clippy::explicit_auto_deref )] #![warn(clippy::explicit_deref_methods)] diff --git a/src/tools/clippy/tests/ui/explicit_deref_methods.rs b/src/tools/clippy/tests/ui/explicit_deref_methods.rs index d118607f992b9..0bbc1ae57cdf6 100644 --- a/src/tools/clippy/tests/ui/explicit_deref_methods.rs +++ b/src/tools/clippy/tests/ui/explicit_deref_methods.rs @@ -4,7 +4,8 @@ unused_variables, clippy::clone_double_ref, clippy::needless_borrow, - clippy::borrow_deref_ref + clippy::borrow_deref_ref, + clippy::explicit_auto_deref )] #![warn(clippy::explicit_deref_methods)] diff --git a/src/tools/clippy/tests/ui/explicit_deref_methods.stderr b/src/tools/clippy/tests/ui/explicit_deref_methods.stderr index 8e8b358972be7..4b10ed1377b0d 100644 --- a/src/tools/clippy/tests/ui/explicit_deref_methods.stderr +++ b/src/tools/clippy/tests/ui/explicit_deref_methods.stderr @@ -1,5 +1,5 @@ error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:35:19 + --> $DIR/explicit_deref_methods.rs:36:19 | LL | let b: &str = a.deref(); | ^^^^^^^^^ help: try this: `&*a` @@ -7,67 +7,67 @@ LL | let b: &str = a.deref(); = note: `-D clippy::explicit-deref-methods` implied by `-D warnings` error: explicit `deref_mut` method call - --> $DIR/explicit_deref_methods.rs:37:23 + --> $DIR/explicit_deref_methods.rs:38:23 | LL | let b: &mut str = a.deref_mut(); | ^^^^^^^^^^^^^ help: try this: `&mut **a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:40:39 + --> $DIR/explicit_deref_methods.rs:41:39 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try this: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:40:50 + --> $DIR/explicit_deref_methods.rs:41:50 | LL | let b: String = format!("{}, {}", a.deref(), a.deref()); | ^^^^^^^^^ help: try this: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:42:20 + --> $DIR/explicit_deref_methods.rs:43:20 | LL | println!("{}", a.deref()); | ^^^^^^^^^ help: try this: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:45:11 + --> $DIR/explicit_deref_methods.rs:46:11 | LL | match a.deref() { | ^^^^^^^^^ help: try this: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:49:28 + --> $DIR/explicit_deref_methods.rs:50:28 | LL | let b: String = concat(a.deref()); | ^^^^^^^^^ help: try this: `&*a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:51:13 + --> $DIR/explicit_deref_methods.rs:52:13 | LL | let b = just_return(a).deref(); | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `just_return(a)` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:53:28 + --> $DIR/explicit_deref_methods.rs:54:28 | LL | let b: String = concat(just_return(a).deref()); | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `just_return(a)` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:55:19 + --> $DIR/explicit_deref_methods.rs:56:19 | LL | let b: &str = a.deref().deref(); | ^^^^^^^^^^^^^^^^^ help: try this: `&**a` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:58:13 + --> $DIR/explicit_deref_methods.rs:59:13 | LL | let b = opt_a.unwrap().deref(); | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&*opt_a.unwrap()` error: explicit `deref` method call - --> $DIR/explicit_deref_methods.rs:84:31 + --> $DIR/explicit_deref_methods.rs:85:31 | LL | let b: &str = expr_deref!(a.deref()); | ^^^^^^^^^ help: try this: `&*a` diff --git a/src/tools/clippy/tests/ui/extra_unused_lifetimes.rs b/src/tools/clippy/tests/ui/extra_unused_lifetimes.rs index f76127a7105fd..d6631e0129000 100644 --- a/src/tools/clippy/tests/ui/extra_unused_lifetimes.rs +++ b/src/tools/clippy/tests/ui/extra_unused_lifetimes.rs @@ -1,3 +1,5 @@ +// aux-build:proc_macro_derive.rs + #![allow( unused, dead_code, @@ -7,6 +9,9 @@ )] #![warn(clippy::extra_unused_lifetimes)] +#[macro_use] +extern crate proc_macro_derive; + fn empty() {} fn used_lt<'a>(x: &'a u8) {} @@ -114,4 +119,11 @@ mod second_case { } } +// Should not lint +#[derive(ExtraLifetimeDerive)] +struct Human<'a> { + pub bones: i32, + pub name: &'a str, +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/extra_unused_lifetimes.stderr b/src/tools/clippy/tests/ui/extra_unused_lifetimes.stderr index fcc12d4ce14b9..26ebc3976dfca 100644 --- a/src/tools/clippy/tests/ui/extra_unused_lifetimes.stderr +++ b/src/tools/clippy/tests/ui/extra_unused_lifetimes.stderr @@ -1,5 +1,5 @@ error: this lifetime isn't used in the function definition - --> $DIR/extra_unused_lifetimes.rs:14:14 + --> $DIR/extra_unused_lifetimes.rs:19:14 | LL | fn unused_lt<'a>(x: u8) {} | ^^ @@ -7,31 +7,31 @@ LL | fn unused_lt<'a>(x: u8) {} = note: `-D clippy::extra-unused-lifetimes` implied by `-D warnings` error: this lifetime isn't used in the function definition - --> $DIR/extra_unused_lifetimes.rs:41:10 + --> $DIR/extra_unused_lifetimes.rs:46:10 | LL | fn x<'a>(&self) {} | ^^ error: this lifetime isn't used in the function definition - --> $DIR/extra_unused_lifetimes.rs:67:22 + --> $DIR/extra_unused_lifetimes.rs:72:22 | LL | fn unused_lt<'a>(x: u8) {} | ^^ error: this lifetime isn't used in the impl - --> $DIR/extra_unused_lifetimes.rs:78:10 + --> $DIR/extra_unused_lifetimes.rs:83:10 | LL | impl<'a> std::ops::AddAssign<&Scalar> for &mut Scalar { | ^^ error: this lifetime isn't used in the impl - --> $DIR/extra_unused_lifetimes.rs:84:10 + --> $DIR/extra_unused_lifetimes.rs:89:10 | LL | impl<'b> Scalar { | ^^ error: this lifetime isn't used in the function definition - --> $DIR/extra_unused_lifetimes.rs:85:26 + --> $DIR/extra_unused_lifetimes.rs:90:26 | LL | pub fn something<'c>() -> Self { | ^^ diff --git a/src/tools/clippy/tests/ui/if_same_then_else.rs b/src/tools/clippy/tests/ui/if_same_then_else.rs index ef9567455008b..2598c2ab426d3 100644 --- a/src/tools/clippy/tests/ui/if_same_then_else.rs +++ b/src/tools/clippy/tests/ui/if_same_then_else.rs @@ -6,7 +6,9 @@ clippy::no_effect, clippy::unused_unit, clippy::zero_divided_by_zero, - clippy::branches_sharing_code + clippy::branches_sharing_code, + dead_code, + unreachable_code )] struct Foo { @@ -155,4 +157,61 @@ mod issue_5698 { } } +mod issue_8836 { + fn do_not_lint() { + if true { + todo!() + } else { + todo!() + } + if true { + todo!(); + } else { + todo!(); + } + if true { + unimplemented!() + } else { + unimplemented!() + } + if true { + unimplemented!(); + } else { + unimplemented!(); + } + + if true { + println!("FOO"); + todo!(); + } else { + println!("FOO"); + todo!(); + } + + if true { + println!("FOO"); + unimplemented!(); + } else { + println!("FOO"); + unimplemented!(); + } + + if true { + println!("FOO"); + todo!() + } else { + println!("FOO"); + todo!() + } + + if true { + println!("FOO"); + unimplemented!() + } else { + println!("FOO"); + unimplemented!() + } + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/if_same_then_else.stderr b/src/tools/clippy/tests/ui/if_same_then_else.stderr index 2f38052fc209e..2cdf442486a30 100644 --- a/src/tools/clippy/tests/ui/if_same_then_else.stderr +++ b/src/tools/clippy/tests/ui/if_same_then_else.stderr @@ -1,5 +1,5 @@ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:21:13 + --> $DIR/if_same_then_else.rs:23:13 | LL | if true { | _____________^ @@ -13,7 +13,7 @@ LL | | } else { | = note: `-D clippy::if-same-then-else` implied by `-D warnings` note: same as this - --> $DIR/if_same_then_else.rs:29:12 + --> $DIR/if_same_then_else.rs:31:12 | LL | } else { | ____________^ @@ -26,7 +26,7 @@ LL | | } | |_____^ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:65:21 + --> $DIR/if_same_then_else.rs:67:21 | LL | let _ = if true { | _____________________^ @@ -35,7 +35,7 @@ LL | | } else { | |_____^ | note: same as this - --> $DIR/if_same_then_else.rs:67:12 + --> $DIR/if_same_then_else.rs:69:12 | LL | } else { | ____________^ @@ -45,7 +45,7 @@ LL | | }; | |_____^ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:72:21 + --> $DIR/if_same_then_else.rs:74:21 | LL | let _ = if true { | _____________________^ @@ -54,7 +54,7 @@ LL | | } else { | |_____^ | note: same as this - --> $DIR/if_same_then_else.rs:74:12 + --> $DIR/if_same_then_else.rs:76:12 | LL | } else { | ____________^ @@ -64,7 +64,7 @@ LL | | }; | |_____^ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:88:21 + --> $DIR/if_same_then_else.rs:90:21 | LL | let _ = if true { | _____________________^ @@ -73,7 +73,7 @@ LL | | } else { | |_____^ | note: same as this - --> $DIR/if_same_then_else.rs:90:12 + --> $DIR/if_same_then_else.rs:92:12 | LL | } else { | ____________^ @@ -83,7 +83,7 @@ LL | | }; | |_____^ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:95:13 + --> $DIR/if_same_then_else.rs:97:13 | LL | if true { | _____________^ @@ -96,7 +96,7 @@ LL | | } else { | |_____^ | note: same as this - --> $DIR/if_same_then_else.rs:102:12 + --> $DIR/if_same_then_else.rs:104:12 | LL | } else { | ____________^ diff --git a/src/tools/clippy/tests/ui/implicit_return.fixed b/src/tools/clippy/tests/ui/implicit_return.fixed index a51f7bc6a29ff..5e55b8b673919 100644 --- a/src/tools/clippy/tests/ui/implicit_return.fixed +++ b/src/tools/clippy/tests/ui/implicit_return.fixed @@ -1,5 +1,5 @@ // run-rustfix - +#![feature(lint_reasons)] #![warn(clippy::implicit_return)] #![allow(clippy::needless_return, clippy::needless_bool, unused, clippy::never_loop)] @@ -128,3 +128,13 @@ async fn foo() -> bool { } fn main() {} + +fn check_expect() -> bool { + if true { + // no error! + return true; + } + + #[expect(clippy::implicit_return)] + true +} diff --git a/src/tools/clippy/tests/ui/implicit_return.rs b/src/tools/clippy/tests/ui/implicit_return.rs index 03f8ec49d51e5..76f0a98035209 100644 --- a/src/tools/clippy/tests/ui/implicit_return.rs +++ b/src/tools/clippy/tests/ui/implicit_return.rs @@ -1,5 +1,5 @@ // run-rustfix - +#![feature(lint_reasons)] #![warn(clippy::implicit_return)] #![allow(clippy::needless_return, clippy::needless_bool, unused, clippy::never_loop)] @@ -128,3 +128,13 @@ async fn foo() -> bool { } fn main() {} + +fn check_expect() -> bool { + if true { + // no error! + return true; + } + + #[expect(clippy::implicit_return)] + true +} diff --git a/src/tools/clippy/tests/ui/let_underscore_lock.rs b/src/tools/clippy/tests/ui/let_underscore_lock.rs index 539d74d1d4c9a..7a7c4e9249952 100644 --- a/src/tools/clippy/tests/ui/let_underscore_lock.rs +++ b/src/tools/clippy/tests/ui/let_underscore_lock.rs @@ -13,6 +13,10 @@ fn main() { let _ = rw.try_read(); let _ = rw.try_write(); + // These shouldn't throw an error. + let _ = m; + let _ = rw; + use parking_lot::{lock_api::RawMutex, Mutex, RwLock}; let p_m: Mutex<()> = Mutex::const_new(RawMutex::INIT, ()); @@ -24,4 +28,9 @@ fn main() { let p_rw = RwLock::new(0); let _ = p_rw.read(); let _ = p_rw.write(); + + // These shouldn't throw an error. + let _ = p_m; + let _ = p_m1; + let _ = p_rw; } diff --git a/src/tools/clippy/tests/ui/let_underscore_lock.stderr b/src/tools/clippy/tests/ui/let_underscore_lock.stderr index 3a2bc17bf7b20..4365b48fabb9c 100644 --- a/src/tools/clippy/tests/ui/let_underscore_lock.stderr +++ b/src/tools/clippy/tests/ui/let_underscore_lock.stderr @@ -48,7 +48,7 @@ LL | let _ = rw.try_write(); = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` error: non-binding let on a synchronization lock - --> $DIR/let_underscore_lock.rs:19:5 + --> $DIR/let_underscore_lock.rs:23:5 | LL | let _ = p_m.lock(); | ^^^^^^^^^^^^^^^^^^^ @@ -56,7 +56,7 @@ LL | let _ = p_m.lock(); = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` error: non-binding let on a synchronization lock - --> $DIR/let_underscore_lock.rs:22:5 + --> $DIR/let_underscore_lock.rs:26:5 | LL | let _ = p_m1.lock(); | ^^^^^^^^^^^^^^^^^^^^ @@ -64,7 +64,7 @@ LL | let _ = p_m1.lock(); = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` error: non-binding let on a synchronization lock - --> $DIR/let_underscore_lock.rs:25:5 + --> $DIR/let_underscore_lock.rs:29:5 | LL | let _ = p_rw.read(); | ^^^^^^^^^^^^^^^^^^^^ @@ -72,7 +72,7 @@ LL | let _ = p_rw.read(); = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` error: non-binding let on a synchronization lock - --> $DIR/let_underscore_lock.rs:26:5 + --> $DIR/let_underscore_lock.rs:30:5 | LL | let _ = p_rw.write(); | ^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/logic_bug.rs b/src/tools/clippy/tests/ui/logic_bug.rs index 4eaa2dd98eb2e..dd6b1db5f70a0 100644 --- a/src/tools/clippy/tests/ui/logic_bug.rs +++ b/src/tools/clippy/tests/ui/logic_bug.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![allow(unused, clippy::diverging_sub_expression)] #![warn(clippy::logic_bug)] @@ -24,3 +25,10 @@ fn equality_stuff() { let _ = a > b && a <= b; let _ = a > b && a == b; } + +fn check_expect() { + let a: i32 = unimplemented!(); + let b: i32 = unimplemented!(); + #[expect(clippy::logic_bug)] + let _ = a < b && a >= b; +} diff --git a/src/tools/clippy/tests/ui/logic_bug.stderr b/src/tools/clippy/tests/ui/logic_bug.stderr index 8f55e1c8ad859..4021fbf457053 100644 --- a/src/tools/clippy/tests/ui/logic_bug.stderr +++ b/src/tools/clippy/tests/ui/logic_bug.stderr @@ -1,60 +1,60 @@ error: this boolean expression contains a logic bug - --> $DIR/logic_bug.rs:10:13 + --> $DIR/logic_bug.rs:11:13 | LL | let _ = a && b || a; | ^^^^^^^^^^^ help: it would look like the following: `a` | = note: `-D clippy::logic-bug` implied by `-D warnings` help: this expression can be optimized out by applying boolean operations to the outer expression - --> $DIR/logic_bug.rs:10:18 + --> $DIR/logic_bug.rs:11:18 | LL | let _ = a && b || a; | ^ error: this boolean expression contains a logic bug - --> $DIR/logic_bug.rs:12:13 + --> $DIR/logic_bug.rs:13:13 | LL | let _ = false && a; | ^^^^^^^^^^ help: it would look like the following: `false` | help: this expression can be optimized out by applying boolean operations to the outer expression - --> $DIR/logic_bug.rs:12:22 + --> $DIR/logic_bug.rs:13:22 | LL | let _ = false && a; | ^ error: this boolean expression contains a logic bug - --> $DIR/logic_bug.rs:22:13 + --> $DIR/logic_bug.rs:23:13 | LL | let _ = a == b && a != b; | ^^^^^^^^^^^^^^^^ help: it would look like the following: `false` | help: this expression can be optimized out by applying boolean operations to the outer expression - --> $DIR/logic_bug.rs:22:13 + --> $DIR/logic_bug.rs:23:13 | LL | let _ = a == b && a != b; | ^^^^^^ error: this boolean expression contains a logic bug - --> $DIR/logic_bug.rs:23:13 + --> $DIR/logic_bug.rs:24:13 | LL | let _ = a < b && a >= b; | ^^^^^^^^^^^^^^^ help: it would look like the following: `false` | help: this expression can be optimized out by applying boolean operations to the outer expression - --> $DIR/logic_bug.rs:23:13 + --> $DIR/logic_bug.rs:24:13 | LL | let _ = a < b && a >= b; | ^^^^^ error: this boolean expression contains a logic bug - --> $DIR/logic_bug.rs:24:13 + --> $DIR/logic_bug.rs:25:13 | LL | let _ = a > b && a <= b; | ^^^^^^^^^^^^^^^ help: it would look like the following: `false` | help: this expression can be optimized out by applying boolean operations to the outer expression - --> $DIR/logic_bug.rs:24:13 + --> $DIR/logic_bug.rs:25:13 | LL | let _ = a > b && a <= b; | ^^^^^ diff --git a/src/tools/clippy/tests/ui/macro_use_imports.fixed b/src/tools/clippy/tests/ui/macro_use_imports.fixed index a83c8ba0b6428..e612480d264bd 100644 --- a/src/tools/clippy/tests/ui/macro_use_imports.fixed +++ b/src/tools/clippy/tests/ui/macro_use_imports.fixed @@ -4,6 +4,7 @@ // run-rustfix // ignore-32bit +#![feature(lint_reasons)] #![allow(unused_imports, unreachable_code, unused_variables, dead_code, unused_attributes)] #![allow(clippy::single_component_path_imports)] #![warn(clippy::macro_use_imports)] diff --git a/src/tools/clippy/tests/ui/macro_use_imports.rs b/src/tools/clippy/tests/ui/macro_use_imports.rs index e26a7545ea6f8..b34817cc3b269 100644 --- a/src/tools/clippy/tests/ui/macro_use_imports.rs +++ b/src/tools/clippy/tests/ui/macro_use_imports.rs @@ -4,6 +4,7 @@ // run-rustfix // ignore-32bit +#![feature(lint_reasons)] #![allow(unused_imports, unreachable_code, unused_variables, dead_code, unused_attributes)] #![allow(clippy::single_component_path_imports)] #![warn(clippy::macro_use_imports)] diff --git a/src/tools/clippy/tests/ui/macro_use_imports.stderr b/src/tools/clippy/tests/ui/macro_use_imports.stderr index 9028a636e7f7a..bf7b6edd0e314 100644 --- a/src/tools/clippy/tests/ui/macro_use_imports.stderr +++ b/src/tools/clippy/tests/ui/macro_use_imports.stderr @@ -1,28 +1,28 @@ error: `macro_use` attributes are no longer needed in the Rust 2018 edition - --> $DIR/macro_use_imports.rs:18:5 + --> $DIR/macro_use_imports.rs:23:5 | LL | #[macro_use] - | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{pub_macro, function_macro, ty_macro, inner_mod_macro, pub_in_private_macro};` + | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{inner::foofoo, inner::try_err};` | = note: `-D clippy::macro-use-imports` implied by `-D warnings` error: `macro_use` attributes are no longer needed in the Rust 2018 edition - --> $DIR/macro_use_imports.rs:20:5 + --> $DIR/macro_use_imports.rs:21:5 | LL | #[macro_use] | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mini_mac::ClippyMiniMacroTest;` error: `macro_use` attributes are no longer needed in the Rust 2018 edition - --> $DIR/macro_use_imports.rs:22:5 + --> $DIR/macro_use_imports.rs:25:5 | LL | #[macro_use] - | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{inner::foofoo, inner::try_err};` + | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::inner::nested::string_add;` error: `macro_use` attributes are no longer needed in the Rust 2018 edition - --> $DIR/macro_use_imports.rs:24:5 + --> $DIR/macro_use_imports.rs:19:5 | LL | #[macro_use] - | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::inner::nested::string_add;` + | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{pub_macro, function_macro, ty_macro, inner_mod_macro, pub_in_private_macro};` error: aborting due to 4 previous errors diff --git a/src/tools/clippy/tests/ui/macro_use_imports_expect.rs b/src/tools/clippy/tests/ui/macro_use_imports_expect.rs new file mode 100644 index 0000000000000..8a1b05da9efc7 --- /dev/null +++ b/src/tools/clippy/tests/ui/macro_use_imports_expect.rs @@ -0,0 +1,51 @@ +// aux-build:macro_rules.rs +// aux-build:macro_use_helper.rs +// aux-build:proc_macro_derive.rs +// ignore-32bit + +#![feature(lint_reasons)] +#![allow(unused_imports, unreachable_code, unused_variables, dead_code, unused_attributes)] +#![allow(clippy::single_component_path_imports)] +#![warn(clippy::macro_use_imports)] + +#[macro_use] +extern crate macro_use_helper as mac; + +#[macro_use] +extern crate proc_macro_derive as mini_mac; + +mod a { + #[expect(clippy::macro_use_imports)] + #[macro_use] + use mac; + #[expect(clippy::macro_use_imports)] + #[macro_use] + use mini_mac; + #[expect(clippy::macro_use_imports)] + #[macro_use] + use mac::inner; + #[expect(clippy::macro_use_imports)] + #[macro_use] + use mac::inner::nested; + + #[derive(ClippyMiniMacroTest)] + struct Test; + + fn test() { + pub_macro!(); + inner_mod_macro!(); + pub_in_private_macro!(_var); + function_macro!(); + let v: ty_macro!() = Vec::default(); + + inner::try_err!(); + inner::foofoo!(); + nested::string_add!(); + } +} + +// issue #7015, ICE due to calling `module_children` with local `DefId` +#[macro_use] +use a as b; + +fn main() {} diff --git a/src/tools/clippy/tests/ui/manual_find.rs b/src/tools/clippy/tests/ui/manual_find.rs new file mode 100644 index 0000000000000..257fe045f78a1 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_find.rs @@ -0,0 +1,22 @@ +#![allow(unused)] +#![warn(clippy::manual_find)] + +fn vec_string(strings: Vec) -> Option { + for s in strings { + if s == String::new() { + return Some(s); + } + } + None +} + +fn tuple(arr: Vec<(String, i32)>) -> Option { + for (s, _) in arr { + if s == String::new() { + return Some(s); + } + } + None +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/manual_find.stderr b/src/tools/clippy/tests/ui/manual_find.stderr new file mode 100644 index 0000000000000..da0fd4aaef7d6 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_find.stderr @@ -0,0 +1,29 @@ +error: manual implementation of `Iterator::find` + --> $DIR/manual_find.rs:5:5 + | +LL | / for s in strings { +LL | | if s == String::new() { +LL | | return Some(s); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `strings.into_iter().find(|s| s == String::new())` + | + = note: `-D clippy::manual-find` implied by `-D warnings` + = note: you may need to dereference some variables + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find.rs:14:5 + | +LL | / for (s, _) in arr { +LL | | if s == String::new() { +LL | | return Some(s); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().map(|(s, _)| s).find(|s| s == String::new())` + | + = note: you may need to dereference some variables + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_find_fixable.fixed b/src/tools/clippy/tests/ui/manual_find_fixable.fixed new file mode 100644 index 0000000000000..36d1644c22bda --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_find_fixable.fixed @@ -0,0 +1,182 @@ +// run-rustfix + +#![allow(unused, clippy::needless_return)] +#![warn(clippy::manual_find)] + +use std::collections::HashMap; + +const ARRAY: &[u32; 5] = &[2, 7, 1, 9, 3]; + +fn lookup(n: u32) -> Option { + ARRAY.iter().find(|&&v| v == n).copied() +} + +fn with_pat(arr: Vec<(u32, u32)>) -> Option { + arr.into_iter().map(|(a, _)| a).find(|&a| a % 2 == 0) +} + +struct Data { + name: String, + is_true: bool, +} +fn with_struct(arr: Vec) -> Option { + arr.into_iter().find(|el| el.name.len() == 10) +} + +struct Tuple(usize, usize); +fn with_tuple_struct(arr: Vec) -> Option { + arr.into_iter().map(|Tuple(a, _)| a).find(|&a| a >= 3) +} + +struct A; +impl A { + fn should_keep(&self) -> bool { + true + } +} +fn with_method_call(arr: Vec) -> Option { + arr.into_iter().find(|el| el.should_keep()) +} + +fn with_closure(arr: Vec) -> Option { + let f = |el: u32| -> u32 { el + 10 }; + arr.into_iter().find(|&el| f(el) == 20) +} + +fn with_closure2(arr: HashMap) -> Option { + let f = |el: i32| -> bool { el == 10 }; + arr.values().find(|&&el| f(el)).copied() +} + +fn with_bool(arr: Vec) -> Option { + arr.into_iter().find(|el| el.is_true) +} + +fn with_side_effects(arr: Vec) -> Option { + for v in arr { + if v == 1 { + println!("side effect"); + return Some(v); + } + } + None +} + +fn with_else(arr: Vec) -> Option { + for el in arr { + if el % 2 == 0 { + return Some(el); + } else { + println!("{}", el); + } + } + None +} + +fn tuple_with_ref(v: [(u8, &u8); 3]) -> Option { + v.into_iter().map(|(_, &x)| x).find(|&x| x > 10) +} + +fn ref_to_tuple_with_ref(v: &[(u8, &u8)]) -> Option { + v.iter().map(|&(_, &x)| x).find(|&x| x > 10) +} + +fn explicit_ret(arr: Vec) -> Option { + arr.into_iter().find(|&x| x >= 5) +} + +fn plus_one(a: i32) -> Option { + Some(a + 1) +} +fn fn_instead_of_some(a: &[i32]) -> Option { + for &x in a { + if x == 1 { + return plus_one(x); + } + } + None +} + +fn for_in_condition(a: &[i32], b: bool) -> Option { + if b { + for &x in a { + if x == 1 { + return Some(x); + } + } + } + None +} + +fn intermediate_statements(a: &[i32]) -> Option { + for &x in a { + if x == 1 { + return Some(x); + } + } + + println!("side effect"); + + None +} + +fn mixed_binding_modes(arr: Vec<(i32, String)>) -> Option { + for (x, mut s) in arr { + if x == 1 && s.as_mut_str().len() == 2 { + return Some(x); + } + } + None +} + +fn as_closure() { + #[rustfmt::skip] + let f = |arr: Vec| -> Option { + arr.into_iter().find(|&x| x < 1) + }; +} + +fn in_block(a: &[i32]) -> Option { + let should_be_none = { + for &x in a { + if x == 1 { + return Some(x); + } + } + None + }; + + assert!(should_be_none.is_none()); + + should_be_none +} + +// Not handled yet +fn mut_binding(v: Vec) -> Option { + for mut s in v { + if s.as_mut_str().len() > 1 { + return Some(s); + } + } + None +} + +fn subpattern(v: Vec<[u32; 32]>) -> Option<[u32; 32]> { + for a @ [first, ..] in v { + if a[12] == first { + return Some(a); + } + } + None +} + +fn two_bindings(v: Vec<(u8, u8)>) -> Option { + for (a, n) in v { + if a == n { + return Some(a); + } + } + None +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/manual_find_fixable.rs b/src/tools/clippy/tests/ui/manual_find_fixable.rs new file mode 100644 index 0000000000000..ed277ddaa722d --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_find_fixable.rs @@ -0,0 +1,242 @@ +// run-rustfix + +#![allow(unused, clippy::needless_return)] +#![warn(clippy::manual_find)] + +use std::collections::HashMap; + +const ARRAY: &[u32; 5] = &[2, 7, 1, 9, 3]; + +fn lookup(n: u32) -> Option { + for &v in ARRAY { + if v == n { + return Some(v); + } + } + None +} + +fn with_pat(arr: Vec<(u32, u32)>) -> Option { + for (a, _) in arr { + if a % 2 == 0 { + return Some(a); + } + } + None +} + +struct Data { + name: String, + is_true: bool, +} +fn with_struct(arr: Vec) -> Option { + for el in arr { + if el.name.len() == 10 { + return Some(el); + } + } + None +} + +struct Tuple(usize, usize); +fn with_tuple_struct(arr: Vec) -> Option { + for Tuple(a, _) in arr { + if a >= 3 { + return Some(a); + } + } + None +} + +struct A; +impl A { + fn should_keep(&self) -> bool { + true + } +} +fn with_method_call(arr: Vec) -> Option { + for el in arr { + if el.should_keep() { + return Some(el); + } + } + None +} + +fn with_closure(arr: Vec) -> Option { + let f = |el: u32| -> u32 { el + 10 }; + for el in arr { + if f(el) == 20 { + return Some(el); + } + } + None +} + +fn with_closure2(arr: HashMap) -> Option { + let f = |el: i32| -> bool { el == 10 }; + for &el in arr.values() { + if f(el) { + return Some(el); + } + } + None +} + +fn with_bool(arr: Vec) -> Option { + for el in arr { + if el.is_true { + return Some(el); + } + } + None +} + +fn with_side_effects(arr: Vec) -> Option { + for v in arr { + if v == 1 { + println!("side effect"); + return Some(v); + } + } + None +} + +fn with_else(arr: Vec) -> Option { + for el in arr { + if el % 2 == 0 { + return Some(el); + } else { + println!("{}", el); + } + } + None +} + +fn tuple_with_ref(v: [(u8, &u8); 3]) -> Option { + for (_, &x) in v { + if x > 10 { + return Some(x); + } + } + None +} + +fn ref_to_tuple_with_ref(v: &[(u8, &u8)]) -> Option { + for &(_, &x) in v { + if x > 10 { + return Some(x); + } + } + None +} + +fn explicit_ret(arr: Vec) -> Option { + for x in arr { + if x >= 5 { + return Some(x); + } + } + return None; +} + +fn plus_one(a: i32) -> Option { + Some(a + 1) +} +fn fn_instead_of_some(a: &[i32]) -> Option { + for &x in a { + if x == 1 { + return plus_one(x); + } + } + None +} + +fn for_in_condition(a: &[i32], b: bool) -> Option { + if b { + for &x in a { + if x == 1 { + return Some(x); + } + } + } + None +} + +fn intermediate_statements(a: &[i32]) -> Option { + for &x in a { + if x == 1 { + return Some(x); + } + } + + println!("side effect"); + + None +} + +fn mixed_binding_modes(arr: Vec<(i32, String)>) -> Option { + for (x, mut s) in arr { + if x == 1 && s.as_mut_str().len() == 2 { + return Some(x); + } + } + None +} + +fn as_closure() { + #[rustfmt::skip] + let f = |arr: Vec| -> Option { + for x in arr { + if x < 1 { + return Some(x); + } + } + None + }; +} + +fn in_block(a: &[i32]) -> Option { + let should_be_none = { + for &x in a { + if x == 1 { + return Some(x); + } + } + None + }; + + assert!(should_be_none.is_none()); + + should_be_none +} + +// Not handled yet +fn mut_binding(v: Vec) -> Option { + for mut s in v { + if s.as_mut_str().len() > 1 { + return Some(s); + } + } + None +} + +fn subpattern(v: Vec<[u32; 32]>) -> Option<[u32; 32]> { + for a @ [first, ..] in v { + if a[12] == first { + return Some(a); + } + } + None +} + +fn two_bindings(v: Vec<(u8, u8)>) -> Option { + for (a, n) in v { + if a == n { + return Some(a); + } + } + None +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/manual_find_fixable.stderr b/src/tools/clippy/tests/ui/manual_find_fixable.stderr new file mode 100644 index 0000000000000..dbc4ff69a740b --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_find_fixable.stderr @@ -0,0 +1,142 @@ +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:11:5 + | +LL | / for &v in ARRAY { +LL | | if v == n { +LL | | return Some(v); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `ARRAY.iter().find(|&&v| v == n).copied()` + | + = note: `-D clippy::manual-find` implied by `-D warnings` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:20:5 + | +LL | / for (a, _) in arr { +LL | | if a % 2 == 0 { +LL | | return Some(a); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().map(|(a, _)| a).find(|&a| a % 2 == 0)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:33:5 + | +LL | / for el in arr { +LL | | if el.name.len() == 10 { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().find(|el| el.name.len() == 10)` + | + = note: you may need to dereference some variables + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:43:5 + | +LL | / for Tuple(a, _) in arr { +LL | | if a >= 3 { +LL | | return Some(a); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().map(|Tuple(a, _)| a).find(|&a| a >= 3)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:58:5 + | +LL | / for el in arr { +LL | | if el.should_keep() { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().find(|el| el.should_keep())` + | + = note: you may need to dereference some variables + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:68:5 + | +LL | / for el in arr { +LL | | if f(el) == 20 { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().find(|&el| f(el) == 20)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:78:5 + | +LL | / for &el in arr.values() { +LL | | if f(el) { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.values().find(|&&el| f(el)).copied()` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:87:5 + | +LL | / for el in arr { +LL | | if el.is_true { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().find(|el| el.is_true)` + | + = note: you may need to dereference some variables + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:117:5 + | +LL | / for (_, &x) in v { +LL | | if x > 10 { +LL | | return Some(x); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `v.into_iter().map(|(_, &x)| x).find(|&x| x > 10)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:126:5 + | +LL | / for &(_, &x) in v { +LL | | if x > 10 { +LL | | return Some(x); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `v.iter().map(|&(_, &x)| x).find(|&x| x > 10)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:135:5 + | +LL | / for x in arr { +LL | | if x >= 5 { +LL | | return Some(x); +LL | | } +LL | | } +LL | | return None; + | |________________^ help: replace with an iterator: `arr.into_iter().find(|&x| x >= 5)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:190:9 + | +LL | / for x in arr { +LL | | if x < 1 { +LL | | return Some(x); +LL | | } +LL | | } +LL | | None + | |____________^ help: replace with an iterator: `arr.into_iter().find(|&x| x < 1)` + +error: aborting due to 12 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_non_exhaustive_enum.rs b/src/tools/clippy/tests/ui/manual_non_exhaustive_enum.rs index f23c6d69b4c6a..03b2433f6666b 100644 --- a/src/tools/clippy/tests/ui/manual_non_exhaustive_enum.rs +++ b/src/tools/clippy/tests/ui/manual_non_exhaustive_enum.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![warn(clippy::manual_non_exhaustive)] #![allow(unused)] @@ -75,4 +76,12 @@ fn foo(x: &mut UsedHidden) { } } +#[expect(clippy::manual_non_exhaustive)] +enum ExpectLint { + A, + B, + #[doc(hidden)] + _C, +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/manual_non_exhaustive_enum.stderr b/src/tools/clippy/tests/ui/manual_non_exhaustive_enum.stderr index 317a45d2cbd59..144fe86df554b 100644 --- a/src/tools/clippy/tests/ui/manual_non_exhaustive_enum.stderr +++ b/src/tools/clippy/tests/ui/manual_non_exhaustive_enum.stderr @@ -1,5 +1,5 @@ error: this seems like a manual implementation of the non-exhaustive pattern - --> $DIR/manual_non_exhaustive_enum.rs:4:1 + --> $DIR/manual_non_exhaustive_enum.rs:5:1 | LL | enum E { | ^----- @@ -15,13 +15,13 @@ LL | | } | = note: `-D clippy::manual-non-exhaustive` implied by `-D warnings` help: remove this variant - --> $DIR/manual_non_exhaustive_enum.rs:8:5 + --> $DIR/manual_non_exhaustive_enum.rs:9:5 | LL | _C, | ^^ error: this seems like a manual implementation of the non-exhaustive pattern - --> $DIR/manual_non_exhaustive_enum.rs:13:1 + --> $DIR/manual_non_exhaustive_enum.rs:14:1 | LL | / enum Ep { LL | | A, @@ -32,7 +32,7 @@ LL | | } | |_^ | help: remove this variant - --> $DIR/manual_non_exhaustive_enum.rs:17:5 + --> $DIR/manual_non_exhaustive_enum.rs:18:5 | LL | _C, | ^^ diff --git a/src/tools/clippy/tests/ui/manual_rem_euclid.fixed b/src/tools/clippy/tests/ui/manual_rem_euclid.fixed new file mode 100644 index 0000000000000..5601c96c10b28 --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_rem_euclid.fixed @@ -0,0 +1,55 @@ +// run-rustfix +// aux-build:macro_rules.rs + +#![warn(clippy::manual_rem_euclid)] + +#[macro_use] +extern crate macro_rules; + +macro_rules! internal_rem_euclid { + () => { + let value: i32 = 5; + let _: i32 = value.rem_euclid(4); + }; +} + +fn main() { + let value: i32 = 5; + + let _: i32 = value.rem_euclid(4); + let _: i32 = value.rem_euclid(4); + let _: i32 = value.rem_euclid(4); + let _: i32 = value.rem_euclid(4); + let _: i32 = 1 + value.rem_euclid(4); + + let _: i32 = (3 + value % 4) % 4; + let _: i32 = (-4 + value % -4) % -4; + let _: i32 = ((5 % 4) + 4) % 4; + + // Make sure the lint does not trigger if it would cause an error, like with an ambiguous + // integer type + let not_annotated = 24; + let _ = ((not_annotated % 4) + 4) % 4; + let inferred: _ = 24; + let _ = ((inferred % 4) + 4) % 4; + + // For lint to apply the constant must always be on the RHS of the previous value for % + let _: i32 = 4 % ((value % 4) + 4); + let _: i32 = ((4 % value) + 4) % 4; + + // Lint in internal macros + internal_rem_euclid!(); + + // Do not lint in external macros + manual_rem_euclid!(); +} + +// Should lint for params too +pub fn rem_euclid_4(num: i32) -> i32 { + num.rem_euclid(4) +} + +// Constant version came later, should still lint +pub const fn const_rem_euclid_4(num: i32) -> i32 { + num.rem_euclid(4) +} diff --git a/src/tools/clippy/tests/ui/manual_rem_euclid.rs b/src/tools/clippy/tests/ui/manual_rem_euclid.rs new file mode 100644 index 0000000000000..52135be26b73c --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_rem_euclid.rs @@ -0,0 +1,55 @@ +// run-rustfix +// aux-build:macro_rules.rs + +#![warn(clippy::manual_rem_euclid)] + +#[macro_use] +extern crate macro_rules; + +macro_rules! internal_rem_euclid { + () => { + let value: i32 = 5; + let _: i32 = ((value % 4) + 4) % 4; + }; +} + +fn main() { + let value: i32 = 5; + + let _: i32 = ((value % 4) + 4) % 4; + let _: i32 = (4 + (value % 4)) % 4; + let _: i32 = (value % 4 + 4) % 4; + let _: i32 = (4 + value % 4) % 4; + let _: i32 = 1 + (4 + value % 4) % 4; + + let _: i32 = (3 + value % 4) % 4; + let _: i32 = (-4 + value % -4) % -4; + let _: i32 = ((5 % 4) + 4) % 4; + + // Make sure the lint does not trigger if it would cause an error, like with an ambiguous + // integer type + let not_annotated = 24; + let _ = ((not_annotated % 4) + 4) % 4; + let inferred: _ = 24; + let _ = ((inferred % 4) + 4) % 4; + + // For lint to apply the constant must always be on the RHS of the previous value for % + let _: i32 = 4 % ((value % 4) + 4); + let _: i32 = ((4 % value) + 4) % 4; + + // Lint in internal macros + internal_rem_euclid!(); + + // Do not lint in external macros + manual_rem_euclid!(); +} + +// Should lint for params too +pub fn rem_euclid_4(num: i32) -> i32 { + ((num % 4) + 4) % 4 +} + +// Constant version came later, should still lint +pub const fn const_rem_euclid_4(num: i32) -> i32 { + ((num % 4) + 4) % 4 +} diff --git a/src/tools/clippy/tests/ui/manual_rem_euclid.stderr b/src/tools/clippy/tests/ui/manual_rem_euclid.stderr new file mode 100644 index 0000000000000..a237fd0213c1e --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_rem_euclid.stderr @@ -0,0 +1,57 @@ +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:19:18 + | +LL | let _: i32 = ((value % 4) + 4) % 4; + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` + | + = note: `-D clippy::manual-rem-euclid` implied by `-D warnings` + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:20:18 + | +LL | let _: i32 = (4 + (value % 4)) % 4; + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:21:18 + | +LL | let _: i32 = (value % 4 + 4) % 4; + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:22:18 + | +LL | let _: i32 = (4 + value % 4) % 4; + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:23:22 + | +LL | let _: i32 = 1 + (4 + value % 4) % 4; + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:12:22 + | +LL | let _: i32 = ((value % 4) + 4) % 4; + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `value.rem_euclid(4)` +... +LL | internal_rem_euclid!(); + | ---------------------- in this macro invocation + | + = note: this error originates in the macro `internal_rem_euclid` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:49:5 + | +LL | ((num % 4) + 4) % 4 + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `num.rem_euclid(4)` + +error: manual `rem_euclid` implementation + --> $DIR/manual_rem_euclid.rs:54:5 + | +LL | ((num % 4) + 4) % 4 + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `num.rem_euclid(4)` + +error: aborting due to 8 previous errors + diff --git a/src/tools/clippy/tests/ui/manual_retain.fixed b/src/tools/clippy/tests/ui/manual_retain.fixed new file mode 100644 index 0000000000000..fba503a20667a --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_retain.fixed @@ -0,0 +1,240 @@ +// run-rustfix +#![feature(custom_inner_attributes)] +#![warn(clippy::manual_retain)] +#![allow(unused)] +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::collections::BinaryHeap; +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::VecDeque; + +fn main() { + binary_heap_retain(); + btree_set_retain(); + btree_map_retain(); + hash_set_retain(); + hash_map_retain(); + string_retain(); + vec_deque_retain(); + vec_retain(); + _msrv_153(); + _msrv_126(); + _msrv_118(); +} + +fn binary_heap_retain() { + // NOTE: Do not lint now, because binary_heap_retain is nighyly API. + // And we need to add a test case for msrv if we update this implmention. + // https://github.com/rust-lang/rust/issues/71503 + let mut heap = BinaryHeap::from([1, 2, 3]); + heap = heap.into_iter().filter(|x| x % 2 == 0).collect(); + heap = heap.iter().filter(|&x| x % 2 == 0).copied().collect(); + heap = heap.iter().filter(|&x| x % 2 == 0).cloned().collect(); + + // Do not lint, because type conversion is performed + heap = heap.into_iter().filter(|x| x % 2 == 0).collect::>(); + heap = heap.iter().filter(|&x| x % 2 == 0).copied().collect::>(); + heap = heap.iter().filter(|&x| x % 2 == 0).cloned().collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: BinaryHeap = heap.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: BinaryHeap = heap.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn btree_map_retain() { + let mut btree_map: BTreeMap = (0..8).map(|x| (x, x * 10)).collect(); + // Do lint. + btree_map.retain(|k, _| k % 2 == 0); + btree_map.retain(|_, &mut v| v % 2 == 0); + btree_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0)); + + // Do not lint. + btree_map = btree_map + .into_iter() + .filter(|(x, _)| x % 2 == 0) + .collect::>(); + + // Do not lint, because this expression is not assign. + let mut foobar: BTreeMap = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + btree_map = foobar.into_iter().filter(|(k, _)| k % 2 == 0).collect(); +} + +fn btree_set_retain() { + let mut btree_set = BTreeSet::from([1, 2, 3, 4, 5, 6]); + + // Do lint. + btree_set.retain(|x| x % 2 == 0); + btree_set.retain(|x| x % 2 == 0); + btree_set.retain(|x| x % 2 == 0); + + // Do not lint, because type conversion is performed + btree_set = btree_set + .iter() + .filter(|&x| x % 2 == 0) + .copied() + .collect::>(); + + btree_set = btree_set + .iter() + .filter(|&x| x % 2 == 0) + .cloned() + .collect::>(); + + btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect::>(); + + // Do not lint, because this expression is not assign. + let mut foobar: BTreeSet = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut bar: BTreeSet = btree_set.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn hash_map_retain() { + let mut hash_map: HashMap = (0..8).map(|x| (x, x * 10)).collect(); + // Do lint. + hash_map.retain(|k, _| k % 2 == 0); + hash_map.retain(|_, &mut v| v % 2 == 0); + hash_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0)); + + // Do not lint. + hash_map = hash_map + .into_iter() + .filter(|(x, _)| x % 2 == 0) + .collect::>(); + + // Do not lint, because this expression is not assign. + let mut foobar: HashMap = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + hash_map = foobar.into_iter().filter(|(k, _)| k % 2 == 0).collect(); +} + +fn hash_set_retain() { + let mut hash_set = HashSet::from([1, 2, 3, 4, 5, 6]); + // Do lint. + hash_set.retain(|x| x % 2 == 0); + hash_set.retain(|x| x % 2 == 0); + hash_set.retain(|x| x % 2 == 0); + + // Do not lint, because type conversion is performed + hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect::>(); + hash_set = hash_set + .iter() + .filter(|&x| x % 2 == 0) + .copied() + .collect::>(); + + hash_set = hash_set + .iter() + .filter(|&x| x % 2 == 0) + .cloned() + .collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: HashSet = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: HashSet = hash_set.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|&x| x % 2 == 0).collect(); +} + +fn string_retain() { + let mut s = String::from("foobar"); + // Do lint. + s.retain(|c| c != 'o'); + + // Do not lint, because this expression is not assign. + let mut bar: String = s.chars().filter(|&c| c != 'o').to_owned().collect(); + + // Do not lint, because it is an assignment to a different variable. + s = bar.chars().filter(|&c| c != 'o').to_owned().collect(); +} + +fn vec_retain() { + let mut vec = vec![0, 1, 2]; + // Do lint. + vec.retain(|x| x % 2 == 0); + vec.retain(|x| x % 2 == 0); + vec.retain(|x| x % 2 == 0); + + // Do not lint, because type conversion is performed + vec = vec.into_iter().filter(|x| x % 2 == 0).collect::>(); + vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect::>(); + vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: Vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: Vec = vec.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn vec_deque_retain() { + let mut vec_deque = VecDeque::new(); + vec_deque.extend(1..5); + + // Do lint. + vec_deque.retain(|x| x % 2 == 0); + vec_deque.retain(|x| x % 2 == 0); + vec_deque.retain(|x| x % 2 == 0); + + // Do not lint, because type conversion is performed + vec_deque = vec_deque + .iter() + .filter(|&x| x % 2 == 0) + .copied() + .collect::>(); + vec_deque = vec_deque + .iter() + .filter(|&x| x % 2 == 0) + .cloned() + .collect::>(); + vec_deque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: VecDeque = vec_deque.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: VecDeque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn _msrv_153() { + #![clippy::msrv = "1.52"] + let mut btree_map: BTreeMap = (0..8).map(|x| (x, x * 10)).collect(); + btree_map = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + + let mut btree_set = BTreeSet::from([1, 2, 3, 4, 5, 6]); + btree_set = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect(); +} + +fn _msrv_126() { + #![clippy::msrv = "1.25"] + let mut s = String::from("foobar"); + s = s.chars().filter(|&c| c != 'o').to_owned().collect(); +} + +fn _msrv_118() { + #![clippy::msrv = "1.17"] + let mut hash_set = HashSet::from([1, 2, 3, 4, 5, 6]); + hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect(); + let mut hash_map: HashMap = (0..8).map(|x| (x, x * 10)).collect(); + hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); +} diff --git a/src/tools/clippy/tests/ui/manual_retain.rs b/src/tools/clippy/tests/ui/manual_retain.rs new file mode 100644 index 0000000000000..81a849fe7684c --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_retain.rs @@ -0,0 +1,246 @@ +// run-rustfix +#![feature(custom_inner_attributes)] +#![warn(clippy::manual_retain)] +#![allow(unused)] +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::collections::BinaryHeap; +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::VecDeque; + +fn main() { + binary_heap_retain(); + btree_set_retain(); + btree_map_retain(); + hash_set_retain(); + hash_map_retain(); + string_retain(); + vec_deque_retain(); + vec_retain(); + _msrv_153(); + _msrv_126(); + _msrv_118(); +} + +fn binary_heap_retain() { + // NOTE: Do not lint now, because binary_heap_retain is nighyly API. + // And we need to add a test case for msrv if we update this implmention. + // https://github.com/rust-lang/rust/issues/71503 + let mut heap = BinaryHeap::from([1, 2, 3]); + heap = heap.into_iter().filter(|x| x % 2 == 0).collect(); + heap = heap.iter().filter(|&x| x % 2 == 0).copied().collect(); + heap = heap.iter().filter(|&x| x % 2 == 0).cloned().collect(); + + // Do not lint, because type conversion is performed + heap = heap.into_iter().filter(|x| x % 2 == 0).collect::>(); + heap = heap.iter().filter(|&x| x % 2 == 0).copied().collect::>(); + heap = heap.iter().filter(|&x| x % 2 == 0).cloned().collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: BinaryHeap = heap.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: BinaryHeap = heap.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn btree_map_retain() { + let mut btree_map: BTreeMap = (0..8).map(|x| (x, x * 10)).collect(); + // Do lint. + btree_map = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + btree_map = btree_map.into_iter().filter(|(_, v)| v % 2 == 0).collect(); + btree_map = btree_map + .into_iter() + .filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0)) + .collect(); + + // Do not lint. + btree_map = btree_map + .into_iter() + .filter(|(x, _)| x % 2 == 0) + .collect::>(); + + // Do not lint, because this expression is not assign. + let mut foobar: BTreeMap = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + btree_map = foobar.into_iter().filter(|(k, _)| k % 2 == 0).collect(); +} + +fn btree_set_retain() { + let mut btree_set = BTreeSet::from([1, 2, 3, 4, 5, 6]); + + // Do lint. + btree_set = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + btree_set = btree_set.iter().filter(|&x| x % 2 == 0).cloned().collect(); + btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because type conversion is performed + btree_set = btree_set + .iter() + .filter(|&x| x % 2 == 0) + .copied() + .collect::>(); + + btree_set = btree_set + .iter() + .filter(|&x| x % 2 == 0) + .cloned() + .collect::>(); + + btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect::>(); + + // Do not lint, because this expression is not assign. + let mut foobar: BTreeSet = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut bar: BTreeSet = btree_set.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn hash_map_retain() { + let mut hash_map: HashMap = (0..8).map(|x| (x, x * 10)).collect(); + // Do lint. + hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + hash_map = hash_map.into_iter().filter(|(_, v)| v % 2 == 0).collect(); + hash_map = hash_map + .into_iter() + .filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0)) + .collect(); + + // Do not lint. + hash_map = hash_map + .into_iter() + .filter(|(x, _)| x % 2 == 0) + .collect::>(); + + // Do not lint, because this expression is not assign. + let mut foobar: HashMap = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + hash_map = foobar.into_iter().filter(|(k, _)| k % 2 == 0).collect(); +} + +fn hash_set_retain() { + let mut hash_set = HashSet::from([1, 2, 3, 4, 5, 6]); + // Do lint. + hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect(); + hash_set = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + hash_set = hash_set.iter().filter(|&x| x % 2 == 0).cloned().collect(); + + // Do not lint, because type conversion is performed + hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect::>(); + hash_set = hash_set + .iter() + .filter(|&x| x % 2 == 0) + .copied() + .collect::>(); + + hash_set = hash_set + .iter() + .filter(|&x| x % 2 == 0) + .cloned() + .collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: HashSet = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: HashSet = hash_set.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|&x| x % 2 == 0).collect(); +} + +fn string_retain() { + let mut s = String::from("foobar"); + // Do lint. + s = s.chars().filter(|&c| c != 'o').to_owned().collect(); + + // Do not lint, because this expression is not assign. + let mut bar: String = s.chars().filter(|&c| c != 'o').to_owned().collect(); + + // Do not lint, because it is an assignment to a different variable. + s = bar.chars().filter(|&c| c != 'o').to_owned().collect(); +} + +fn vec_retain() { + let mut vec = vec![0, 1, 2]; + // Do lint. + vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect(); + vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect(); + vec = vec.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because type conversion is performed + vec = vec.into_iter().filter(|x| x % 2 == 0).collect::>(); + vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect::>(); + vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: Vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: Vec = vec.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn vec_deque_retain() { + let mut vec_deque = VecDeque::new(); + vec_deque.extend(1..5); + + // Do lint. + vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).copied().collect(); + vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).cloned().collect(); + vec_deque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because type conversion is performed + vec_deque = vec_deque + .iter() + .filter(|&x| x % 2 == 0) + .copied() + .collect::>(); + vec_deque = vec_deque + .iter() + .filter(|&x| x % 2 == 0) + .cloned() + .collect::>(); + vec_deque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect::>(); + + // Do not lint, because this expression is not assign. + let mut bar: VecDeque = vec_deque.iter().filter(|&x| x % 2 == 0).copied().collect(); + let mut foobar: VecDeque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect(); + + // Do not lint, because it is an assignment to a different variable. + bar = foobar.iter().filter(|&x| x % 2 == 0).copied().collect(); + bar = foobar.iter().filter(|&x| x % 2 == 0).cloned().collect(); + bar = foobar.into_iter().filter(|x| x % 2 == 0).collect(); +} + +fn _msrv_153() { + #![clippy::msrv = "1.52"] + let mut btree_map: BTreeMap = (0..8).map(|x| (x, x * 10)).collect(); + btree_map = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + + let mut btree_set = BTreeSet::from([1, 2, 3, 4, 5, 6]); + btree_set = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect(); +} + +fn _msrv_126() { + #![clippy::msrv = "1.25"] + let mut s = String::from("foobar"); + s = s.chars().filter(|&c| c != 'o').to_owned().collect(); +} + +fn _msrv_118() { + #![clippy::msrv = "1.17"] + let mut hash_set = HashSet::from([1, 2, 3, 4, 5, 6]); + hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect(); + let mut hash_map: HashMap = (0..8).map(|x| (x, x * 10)).collect(); + hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); +} diff --git a/src/tools/clippy/tests/ui/manual_retain.stderr b/src/tools/clippy/tests/ui/manual_retain.stderr new file mode 100644 index 0000000000000..ec635919b48fb --- /dev/null +++ b/src/tools/clippy/tests/ui/manual_retain.stderr @@ -0,0 +1,124 @@ +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:52:5 + | +LL | btree_map = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_map.retain(|k, _| k % 2 == 0)` + | + = note: `-D clippy::manual-retain` implied by `-D warnings` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:53:5 + | +LL | btree_map = btree_map.into_iter().filter(|(_, v)| v % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_map.retain(|_, &mut v| v % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:54:5 + | +LL | / btree_map = btree_map +LL | | .into_iter() +LL | | .filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0)) +LL | | .collect(); + | |__________________^ help: consider calling `.retain()` instead: `btree_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0))` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:76:5 + | +LL | btree_set = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:77:5 + | +LL | btree_set = btree_set.iter().filter(|&x| x % 2 == 0).cloned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:78:5 + | +LL | btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:108:5 + | +LL | hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_map.retain(|k, _| k % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:109:5 + | +LL | hash_map = hash_map.into_iter().filter(|(_, v)| v % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_map.retain(|_, &mut v| v % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:110:5 + | +LL | / hash_map = hash_map +LL | | .into_iter() +LL | | .filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0)) +LL | | .collect(); + | |__________________^ help: consider calling `.retain()` instead: `hash_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0))` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:131:5 + | +LL | hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:132:5 + | +LL | hash_set = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:133:5 + | +LL | hash_set = hash_set.iter().filter(|&x| x % 2 == 0).cloned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:162:5 + | +LL | s = s.chars().filter(|&c| c != 'o').to_owned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `s.retain(|c| c != 'o')` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:174:5 + | +LL | vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:175:5 + | +LL | vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:176:5 + | +LL | vec = vec.into_iter().filter(|x| x % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:198:5 + | +LL | vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).copied().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:199:5 + | +LL | vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).cloned().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)` + +error: this expression can be written more simply using `.retain()` + --> $DIR/manual_retain.rs:200:5 + | +LL | vec_deque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)` + +error: aborting due to 19 previous errors + diff --git a/src/tools/clippy/tests/ui/methods.rs b/src/tools/clippy/tests/ui/methods.rs index 9805097084d30..1970c2eae5314 100644 --- a/src/tools/clippy/tests/ui/methods.rs +++ b/src/tools/clippy/tests/ui/methods.rs @@ -15,6 +15,7 @@ clippy::use_self, clippy::useless_format, clippy::wrong_self_convention, + clippy::unused_async, clippy::unused_self, unused )] diff --git a/src/tools/clippy/tests/ui/methods.stderr b/src/tools/clippy/tests/ui/methods.stderr index 6be38b24fbda2..b63672dd6fdbb 100644 --- a/src/tools/clippy/tests/ui/methods.stderr +++ b/src/tools/clippy/tests/ui/methods.stderr @@ -1,5 +1,5 @@ error: methods called `new` usually return `Self` - --> $DIR/methods.rs:103:5 + --> $DIR/methods.rs:104:5 | LL | / fn new() -> i32 { LL | | 0 @@ -9,7 +9,7 @@ LL | | } = note: `-D clippy::new-ret-no-self` implied by `-D warnings` error: called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling `.find(..)` instead - --> $DIR/methods.rs:124:13 + --> $DIR/methods.rs:125:13 | LL | let _ = v.iter().filter(|&x| { | _____________^ diff --git a/src/tools/clippy/tests/ui/min_rust_version_attr.rs b/src/tools/clippy/tests/ui/min_rust_version_attr.rs index f83c3e0e281ca..44e407bd1ab2b 100644 --- a/src/tools/clippy/tests/ui/min_rust_version_attr.rs +++ b/src/tools/clippy/tests/ui/min_rust_version_attr.rs @@ -155,6 +155,11 @@ fn cast_abs_to_unsigned() { assert_eq!(10u32, x.abs() as u32); } +fn manual_rem_euclid() { + let x: i32 = 10; + let _: i32 = ((x % 4) + 4) % 4; +} + fn main() { filter_map_next(); checked_conversion(); @@ -174,6 +179,7 @@ fn main() { int_from_bool(); err_expect(); cast_abs_to_unsigned(); + manual_rem_euclid(); } mod just_under_msrv { @@ -211,3 +217,12 @@ mod just_above_msrv { } } } + +mod const_rem_euclid { + #![feature(custom_inner_attributes)] + #![clippy::msrv = "1.50.0"] + + pub const fn const_rem_euclid_4(num: i32) -> i32 { + ((num % 4) + 4) % 4 + } +} diff --git a/src/tools/clippy/tests/ui/min_rust_version_attr.stderr b/src/tools/clippy/tests/ui/min_rust_version_attr.stderr index de225eb740d03..b1c23b539ffde 100644 --- a/src/tools/clippy/tests/ui/min_rust_version_attr.stderr +++ b/src/tools/clippy/tests/ui/min_rust_version_attr.stderr @@ -1,12 +1,12 @@ error: stripping a prefix manually - --> $DIR/min_rust_version_attr.rs:198:24 + --> $DIR/min_rust_version_attr.rs:204:24 | LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); | ^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::manual-strip` implied by `-D warnings` note: the prefix was tested here - --> $DIR/min_rust_version_attr.rs:197:9 + --> $DIR/min_rust_version_attr.rs:203:9 | LL | if s.starts_with("hello, ") { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -17,13 +17,13 @@ LL ~ assert_eq!(.to_uppercase(), "WORLD!"); | error: stripping a prefix manually - --> $DIR/min_rust_version_attr.rs:210:24 + --> $DIR/min_rust_version_attr.rs:216:24 | LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); | ^^^^^^^^^^^^^^^^^^^^ | note: the prefix was tested here - --> $DIR/min_rust_version_attr.rs:209:9 + --> $DIR/min_rust_version_attr.rs:215:9 | LL | if s.starts_with("hello, ") { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/needless_borrow.fixed b/src/tools/clippy/tests/ui/needless_borrow.fixed index e7a483c058295..cb00512243601 100644 --- a/src/tools/clippy/tests/ui/needless_borrow.fixed +++ b/src/tools/clippy/tests/ui/needless_borrow.fixed @@ -62,7 +62,18 @@ fn main() { 0 => &mut x, _ => &mut *x, }; - + let y: &mut i32 = match 0 { + // Lint here. The type given above triggers auto-borrow. + 0 => x, + _ => &mut *x, + }; + fn ref_mut_i32(_: &mut i32) {} + ref_mut_i32(match 0 { + // Lint here. The type given above triggers auto-borrow. + 0 => x, + _ => &mut *x, + }); + // use 'x' after to make sure it's still usable in the fixed code. *x = 5; let s = String::new(); @@ -74,6 +85,36 @@ fn main() { let _ = x.0; let x = &x as *const (i32, i32); let _ = unsafe { (*x).0 }; + + // Issue #8367 + trait Foo { + fn foo(self); + } + impl Foo for &'_ () { + fn foo(self) {} + } + (&()).foo(); // Don't lint. `()` doesn't implement `Foo` + (&()).foo(); + + impl Foo for i32 { + fn foo(self) {} + } + impl Foo for &'_ i32 { + fn foo(self) {} + } + (&5).foo(); // Don't lint. `5` will call `::foo` + (&5).foo(); + + trait FooRef { + fn foo_ref(&self); + } + impl FooRef for () { + fn foo_ref(&self) {} + } + impl FooRef for &'_ () { + fn foo_ref(&self) {} + } + (&&()).foo_ref(); // Don't lint. `&()` will call `<() as FooRef>::foo_ref` } #[allow(clippy::needless_borrowed_reference)] diff --git a/src/tools/clippy/tests/ui/needless_borrow.rs b/src/tools/clippy/tests/ui/needless_borrow.rs index 1d6bf46405a2f..d636a40100378 100644 --- a/src/tools/clippy/tests/ui/needless_borrow.rs +++ b/src/tools/clippy/tests/ui/needless_borrow.rs @@ -62,7 +62,18 @@ fn main() { 0 => &mut x, _ => &mut *x, }; - + let y: &mut i32 = match 0 { + // Lint here. The type given above triggers auto-borrow. + 0 => &mut x, + _ => &mut *x, + }; + fn ref_mut_i32(_: &mut i32) {} + ref_mut_i32(match 0 { + // Lint here. The type given above triggers auto-borrow. + 0 => &mut x, + _ => &mut *x, + }); + // use 'x' after to make sure it's still usable in the fixed code. *x = 5; let s = String::new(); @@ -74,6 +85,36 @@ fn main() { let _ = (&x).0; let x = &x as *const (i32, i32); let _ = unsafe { (&*x).0 }; + + // Issue #8367 + trait Foo { + fn foo(self); + } + impl Foo for &'_ () { + fn foo(self) {} + } + (&()).foo(); // Don't lint. `()` doesn't implement `Foo` + (&&()).foo(); + + impl Foo for i32 { + fn foo(self) {} + } + impl Foo for &'_ i32 { + fn foo(self) {} + } + (&5).foo(); // Don't lint. `5` will call `::foo` + (&&5).foo(); + + trait FooRef { + fn foo_ref(&self); + } + impl FooRef for () { + fn foo_ref(&self) {} + } + impl FooRef for &'_ () { + fn foo_ref(&self) {} + } + (&&()).foo_ref(); // Don't lint. `&()` will call `<() as FooRef>::foo_ref` } #[allow(clippy::needless_borrowed_reference)] diff --git a/src/tools/clippy/tests/ui/needless_borrow.stderr b/src/tools/clippy/tests/ui/needless_borrow.stderr index be59d8f546d23..8a2e2b9895908 100644 --- a/src/tools/clippy/tests/ui/needless_borrow.stderr +++ b/src/tools/clippy/tests/ui/needless_borrow.stderr @@ -84,17 +84,41 @@ error: this expression creates a reference which is immediately dereferenced by LL | let y: &mut i32 = &mut &mut x; | ^^^^^^^^^^^ help: change this to: `x` +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:67:14 + | +LL | 0 => &mut x, + | ^^^^^^ help: change this to: `x` + +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:73:14 + | +LL | 0 => &mut x, + | ^^^^^^ help: change this to: `x` + error: this expression borrows a value the compiler would automatically borrow - --> $DIR/needless_borrow.rs:74:13 + --> $DIR/needless_borrow.rs:85:13 | LL | let _ = (&x).0; | ^^^^ help: change this to: `x` error: this expression borrows a value the compiler would automatically borrow - --> $DIR/needless_borrow.rs:76:22 + --> $DIR/needless_borrow.rs:87:22 | LL | let _ = unsafe { (&*x).0 }; | ^^^^^ help: change this to: `(*x)` -error: aborting due to 16 previous errors +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:97:5 + | +LL | (&&()).foo(); + | ^^^^^^ help: change this to: `(&())` + +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:106:5 + | +LL | (&&5).foo(); + | ^^^^^ help: change this to: `(&5)` + +error: aborting due to 20 previous errors diff --git a/src/tools/clippy/tests/ui/needless_borrow_pat.rs b/src/tools/clippy/tests/ui/needless_borrow_pat.rs index 04b6283da3c3b..222e8e617995d 100644 --- a/src/tools/clippy/tests/ui/needless_borrow_pat.rs +++ b/src/tools/clippy/tests/ui/needless_borrow_pat.rs @@ -1,7 +1,7 @@ // FIXME: run-rustfix waiting on multi-span suggestions #![warn(clippy::needless_borrow)] -#![allow(clippy::needless_borrowed_reference)] +#![allow(clippy::needless_borrowed_reference, clippy::explicit_auto_deref)] fn f1(_: &str) {} macro_rules! m1 { diff --git a/src/tools/clippy/tests/ui/needless_return.fixed b/src/tools/clippy/tests/ui/needless_return.fixed index 7c828430b7853..0bc0d0011efe0 100644 --- a/src/tools/clippy/tests/ui/needless_return.fixed +++ b/src/tools/clippy/tests/ui/needless_return.fixed @@ -1,5 +1,6 @@ // run-rustfix +#![feature(lint_reasons)] #![feature(let_else)] #![allow(unused)] #![allow( @@ -10,6 +11,8 @@ )] #![warn(clippy::needless_return)] +use std::cell::RefCell; + macro_rules! the_answer { () => { 42 @@ -86,17 +89,15 @@ fn test_nested_match(x: u32) { } } -fn read_line() -> String { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - return stdin.lock().lines().next().unwrap().unwrap(); +fn temporary_outlives_local() -> String { + let x = RefCell::::default(); + return x.borrow().clone(); } fn borrows_but_not_last(value: bool) -> String { if value { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - let _a = stdin.lock().lines().next().unwrap().unwrap(); + let x = RefCell::::default(); + let _a = x.borrow().clone(); String::from("test") } else { String::new() @@ -197,17 +198,15 @@ async fn async_test_void_match(x: u32) { } } -async fn async_read_line() -> String { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - return stdin.lock().lines().next().unwrap().unwrap(); +async fn async_temporary_outlives_local() -> String { + let x = RefCell::::default(); + return x.borrow().clone(); } async fn async_borrows_but_not_last(value: bool) -> String { if value { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - let _a = stdin.lock().lines().next().unwrap().unwrap(); + let x = RefCell::::default(); + let _a = x.borrow().clone(); String::from("test") } else { String::new() @@ -229,4 +228,13 @@ fn needless_return_macro() -> String { format!("Hello {}", "world!") } +fn check_expect() -> bool { + if true { + // no error! + return true; + } + #[expect(clippy::needless_return)] + return true; +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/needless_return.rs b/src/tools/clippy/tests/ui/needless_return.rs index fe82af00e6750..eb9f72e8e7822 100644 --- a/src/tools/clippy/tests/ui/needless_return.rs +++ b/src/tools/clippy/tests/ui/needless_return.rs @@ -1,5 +1,6 @@ // run-rustfix +#![feature(lint_reasons)] #![feature(let_else)] #![allow(unused)] #![allow( @@ -10,6 +11,8 @@ )] #![warn(clippy::needless_return)] +use std::cell::RefCell; + macro_rules! the_answer { () => { 42 @@ -86,17 +89,15 @@ fn test_nested_match(x: u32) { } } -fn read_line() -> String { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - return stdin.lock().lines().next().unwrap().unwrap(); +fn temporary_outlives_local() -> String { + let x = RefCell::::default(); + return x.borrow().clone(); } fn borrows_but_not_last(value: bool) -> String { if value { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - let _a = stdin.lock().lines().next().unwrap().unwrap(); + let x = RefCell::::default(); + let _a = x.borrow().clone(); return String::from("test"); } else { return String::new(); @@ -197,17 +198,15 @@ async fn async_test_void_match(x: u32) { } } -async fn async_read_line() -> String { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - return stdin.lock().lines().next().unwrap().unwrap(); +async fn async_temporary_outlives_local() -> String { + let x = RefCell::::default(); + return x.borrow().clone(); } async fn async_borrows_but_not_last(value: bool) -> String { if value { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - let _a = stdin.lock().lines().next().unwrap().unwrap(); + let x = RefCell::::default(); + let _a = x.borrow().clone(); return String::from("test"); } else { return String::new(); @@ -229,4 +228,13 @@ fn needless_return_macro() -> String { return format!("Hello {}", "world!"); } +fn check_expect() -> bool { + if true { + // no error! + return true; + } + #[expect(clippy::needless_return)] + return true; +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/needless_return.stderr b/src/tools/clippy/tests/ui/needless_return.stderr index 4c8be47b025e2..83ff07638693c 100644 --- a/src/tools/clippy/tests/ui/needless_return.stderr +++ b/src/tools/clippy/tests/ui/needless_return.stderr @@ -1,5 +1,5 @@ error: unneeded `return` statement - --> $DIR/needless_return.rs:24:5 + --> $DIR/needless_return.rs:27:5 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` @@ -7,217 +7,217 @@ LL | return true; = note: `-D clippy::needless-return` implied by `-D warnings` error: unneeded `return` statement - --> $DIR/needless_return.rs:28:5 + --> $DIR/needless_return.rs:31:5 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:33:9 + --> $DIR/needless_return.rs:36:9 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:35:9 + --> $DIR/needless_return.rs:38:9 | LL | return false; | ^^^^^^^^^^^^^ help: remove `return`: `false` error: unneeded `return` statement - --> $DIR/needless_return.rs:41:17 + --> $DIR/needless_return.rs:44:17 | LL | true => return false, | ^^^^^^^^^^^^ help: remove `return`: `false` error: unneeded `return` statement - --> $DIR/needless_return.rs:43:13 + --> $DIR/needless_return.rs:46:13 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:50:9 + --> $DIR/needless_return.rs:53:9 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:52:16 + --> $DIR/needless_return.rs:55:16 | LL | let _ = || return true; | ^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:56:5 + --> $DIR/needless_return.rs:59:5 | LL | return the_answer!(); | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `the_answer!()` error: unneeded `return` statement - --> $DIR/needless_return.rs:60:5 + --> $DIR/needless_return.rs:63:5 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:65:9 + --> $DIR/needless_return.rs:68:9 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:67:9 + --> $DIR/needless_return.rs:70:9 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:74:14 + --> $DIR/needless_return.rs:77:14 | LL | _ => return, | ^^^^^^ help: replace `return` with a unit value: `()` error: unneeded `return` statement - --> $DIR/needless_return.rs:83:13 + --> $DIR/needless_return.rs:86:13 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:85:14 + --> $DIR/needless_return.rs:88:14 | LL | _ => return, | ^^^^^^ help: replace `return` with a unit value: `()` error: unneeded `return` statement - --> $DIR/needless_return.rs:100:9 + --> $DIR/needless_return.rs:101:9 | LL | return String::from("test"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::from("test")` error: unneeded `return` statement - --> $DIR/needless_return.rs:102:9 + --> $DIR/needless_return.rs:103:9 | LL | return String::new(); | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::new()` error: unneeded `return` statement - --> $DIR/needless_return.rs:124:32 + --> $DIR/needless_return.rs:125:32 | LL | bar.unwrap_or_else(|_| return) | ^^^^^^ help: replace `return` with an empty block: `{}` error: unneeded `return` statement - --> $DIR/needless_return.rs:129:13 + --> $DIR/needless_return.rs:130:13 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:131:20 + --> $DIR/needless_return.rs:132:20 | LL | let _ = || return; | ^^^^^^ help: replace `return` with an empty block: `{}` error: unneeded `return` statement - --> $DIR/needless_return.rs:137:32 + --> $DIR/needless_return.rs:138:32 | LL | res.unwrap_or_else(|_| return Foo) | ^^^^^^^^^^ help: remove `return`: `Foo` error: unneeded `return` statement - --> $DIR/needless_return.rs:146:5 + --> $DIR/needless_return.rs:147:5 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:150:5 + --> $DIR/needless_return.rs:151:5 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:155:9 + --> $DIR/needless_return.rs:156:9 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:157:9 + --> $DIR/needless_return.rs:158:9 | LL | return false; | ^^^^^^^^^^^^^ help: remove `return`: `false` error: unneeded `return` statement - --> $DIR/needless_return.rs:163:17 + --> $DIR/needless_return.rs:164:17 | LL | true => return false, | ^^^^^^^^^^^^ help: remove `return`: `false` error: unneeded `return` statement - --> $DIR/needless_return.rs:165:13 + --> $DIR/needless_return.rs:166:13 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:172:9 + --> $DIR/needless_return.rs:173:9 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:174:16 + --> $DIR/needless_return.rs:175:16 | LL | let _ = || return true; | ^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:178:5 + --> $DIR/needless_return.rs:179:5 | LL | return the_answer!(); | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `the_answer!()` error: unneeded `return` statement - --> $DIR/needless_return.rs:182:5 + --> $DIR/needless_return.rs:183:5 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:187:9 + --> $DIR/needless_return.rs:188:9 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:189:9 + --> $DIR/needless_return.rs:190:9 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:196:14 + --> $DIR/needless_return.rs:197:14 | LL | _ => return, | ^^^^^^ help: replace `return` with a unit value: `()` error: unneeded `return` statement - --> $DIR/needless_return.rs:211:9 + --> $DIR/needless_return.rs:210:9 | LL | return String::from("test"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::from("test")` error: unneeded `return` statement - --> $DIR/needless_return.rs:213:9 + --> $DIR/needless_return.rs:212:9 | LL | return String::new(); | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::new()` error: unneeded `return` statement - --> $DIR/needless_return.rs:229:5 + --> $DIR/needless_return.rs:228:5 | LL | return format!("Hello {}", "world!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `format!("Hello {}", "world!")` diff --git a/src/tools/clippy/tests/ui/neg_multiply.fixed b/src/tools/clippy/tests/ui/neg_multiply.fixed index 35af9d6ae317f..58ab9e8567838 100644 --- a/src/tools/clippy/tests/ui/neg_multiply.fixed +++ b/src/tools/clippy/tests/ui/neg_multiply.fixed @@ -38,6 +38,9 @@ fn main() { 0xcafe | -0xff00; + -(3_usize as i32); + -(3_usize as i32); + -1 * -1; // should be ok X * -1; // should be ok diff --git a/src/tools/clippy/tests/ui/neg_multiply.rs b/src/tools/clippy/tests/ui/neg_multiply.rs index 7dbdb0906ceeb..581290dc72e43 100644 --- a/src/tools/clippy/tests/ui/neg_multiply.rs +++ b/src/tools/clippy/tests/ui/neg_multiply.rs @@ -38,6 +38,9 @@ fn main() { 0xcafe | 0xff00 * -1; + 3_usize as i32 * -1; + (3_usize as i32) * -1; + -1 * -1; // should be ok X * -1; // should be ok diff --git a/src/tools/clippy/tests/ui/neg_multiply.stderr b/src/tools/clippy/tests/ui/neg_multiply.stderr index dbf8fb36938cb..388ef29eb8567 100644 --- a/src/tools/clippy/tests/ui/neg_multiply.stderr +++ b/src/tools/clippy/tests/ui/neg_multiply.stderr @@ -36,5 +36,17 @@ error: this multiplication by -1 can be written more succinctly LL | 0xcafe | 0xff00 * -1; | ^^^^^^^^^^^ help: consider using: `-0xff00` -error: aborting due to 6 previous errors +error: this multiplication by -1 can be written more succinctly + --> $DIR/neg_multiply.rs:41:5 + | +LL | 3_usize as i32 * -1; + | ^^^^^^^^^^^^^^^^^^^ help: consider using: `-(3_usize as i32)` + +error: this multiplication by -1 can be written more succinctly + --> $DIR/neg_multiply.rs:42:5 + | +LL | (3_usize as i32) * -1; + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `-(3_usize as i32)` + +error: aborting due to 8 previous errors diff --git a/src/tools/clippy/tests/ui/nonminimal_bool.rs b/src/tools/clippy/tests/ui/nonminimal_bool.rs index fa5743c115577..24ae62bb05884 100644 --- a/src/tools/clippy/tests/ui/nonminimal_bool.rs +++ b/src/tools/clippy/tests/ui/nonminimal_bool.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![allow(unused, clippy::diverging_sub_expression)] #![warn(clippy::nonminimal_bool)] @@ -50,3 +51,9 @@ fn issue4548() { if i != j && f(i, j) != 0 || i == j && f(i, j) != 1 {} } + +fn check_expect() { + let a: bool = unimplemented!(); + #[expect(clippy::nonminimal_bool)] + let _ = !!a; +} diff --git a/src/tools/clippy/tests/ui/nonminimal_bool.stderr b/src/tools/clippy/tests/ui/nonminimal_bool.stderr index bb93cbbd5e190..fc6a5ce1dc2ee 100644 --- a/src/tools/clippy/tests/ui/nonminimal_bool.stderr +++ b/src/tools/clippy/tests/ui/nonminimal_bool.stderr @@ -1,5 +1,5 @@ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:10:13 + --> $DIR/nonminimal_bool.rs:11:13 | LL | let _ = !true; | ^^^^^ help: try: `false` @@ -7,43 +7,43 @@ LL | let _ = !true; = note: `-D clippy::nonminimal-bool` implied by `-D warnings` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:11:13 + --> $DIR/nonminimal_bool.rs:12:13 | LL | let _ = !false; | ^^^^^^ help: try: `true` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:12:13 + --> $DIR/nonminimal_bool.rs:13:13 | LL | let _ = !!a; | ^^^ help: try: `a` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:13:13 + --> $DIR/nonminimal_bool.rs:14:13 | LL | let _ = false || a; | ^^^^^^^^^^ help: try: `a` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:17:13 + --> $DIR/nonminimal_bool.rs:18:13 | LL | let _ = !(!a && b); | ^^^^^^^^^^ help: try: `a || !b` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:18:13 + --> $DIR/nonminimal_bool.rs:19:13 | LL | let _ = !(!a || b); | ^^^^^^^^^^ help: try: `a && !b` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:19:13 + --> $DIR/nonminimal_bool.rs:20:13 | LL | let _ = !a && !(b && c); | ^^^^^^^^^^^^^^^ help: try: `!(a || b && c)` error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:27:13 + --> $DIR/nonminimal_bool.rs:28:13 | LL | let _ = a == b && c == 5 && a == b; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -56,7 +56,7 @@ LL | let _ = a == b && c == 5; | ~~~~~~~~~~~~~~~~ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:28:13 + --> $DIR/nonminimal_bool.rs:29:13 | LL | let _ = a == b || c == 5 || a == b; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -69,7 +69,7 @@ LL | let _ = a == b || c == 5; | ~~~~~~~~~~~~~~~~ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:29:13 + --> $DIR/nonminimal_bool.rs:30:13 | LL | let _ = a == b && c == 5 && b == a; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -82,7 +82,7 @@ LL | let _ = a == b && c == 5; | ~~~~~~~~~~~~~~~~ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:30:13 + --> $DIR/nonminimal_bool.rs:31:13 | LL | let _ = a != b || !(a != b || c == d); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -95,7 +95,7 @@ LL | let _ = a != b || c != d; | ~~~~~~~~~~~~~~~~ error: this boolean expression can be simplified - --> $DIR/nonminimal_bool.rs:31:13 + --> $DIR/nonminimal_bool.rs:32:13 | LL | let _ = a != b && !(a != b && c == d); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/ptr_arg.rs b/src/tools/clippy/tests/ui/ptr_arg.rs index 814bbc7af713b..fd15001e540c6 100644 --- a/src/tools/clippy/tests/ui/ptr_arg.rs +++ b/src/tools/clippy/tests/ui/ptr_arg.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![allow(unused, clippy::many_single_char_names, clippy::redundant_clone)] #![warn(clippy::ptr_arg)] @@ -109,9 +110,12 @@ mod issue_5644 { #[allow(clippy::ptr_arg)] _s: &String, #[allow(clippy::ptr_arg)] _p: &PathBuf, #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, + #[expect(clippy::ptr_arg)] _expect: &Cow<[i32]>, ) { } + fn some_allowed(#[allow(clippy::ptr_arg)] _v: &Vec, _s: &String) {} + struct S; impl S { fn allowed( @@ -119,6 +123,7 @@ mod issue_5644 { #[allow(clippy::ptr_arg)] _s: &String, #[allow(clippy::ptr_arg)] _p: &PathBuf, #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, + #[expect(clippy::ptr_arg)] _expect: &Cow<[i32]>, ) { } } @@ -129,6 +134,7 @@ mod issue_5644 { #[allow(clippy::ptr_arg)] _s: &String, #[allow(clippy::ptr_arg)] _p: &PathBuf, #[allow(clippy::ptr_arg)] _c: &Cow<[i32]>, + #[expect(clippy::ptr_arg)] _expect: &Cow<[i32]>, ) { } } diff --git a/src/tools/clippy/tests/ui/ptr_arg.stderr b/src/tools/clippy/tests/ui/ptr_arg.stderr index 7ec4a566ff34b..d64b5f454a5aa 100644 --- a/src/tools/clippy/tests/ui/ptr_arg.stderr +++ b/src/tools/clippy/tests/ui/ptr_arg.stderr @@ -1,5 +1,5 @@ error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:7:14 + --> $DIR/ptr_arg.rs:8:14 | LL | fn do_vec(x: &Vec) { | ^^^^^^^^^ help: change this to: `&[i64]` @@ -7,43 +7,43 @@ LL | fn do_vec(x: &Vec) { = note: `-D clippy::ptr-arg` implied by `-D warnings` error: writing `&mut Vec` instead of `&mut [_]` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:11:18 + --> $DIR/ptr_arg.rs:12:18 | LL | fn do_vec_mut(x: &mut Vec) { | ^^^^^^^^^^^^^ help: change this to: `&mut [i64]` error: writing `&String` instead of `&str` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:15:14 + --> $DIR/ptr_arg.rs:16:14 | LL | fn do_str(x: &String) { | ^^^^^^^ help: change this to: `&str` error: writing `&mut String` instead of `&mut str` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:19:18 + --> $DIR/ptr_arg.rs:20:18 | LL | fn do_str_mut(x: &mut String) { | ^^^^^^^^^^^ help: change this to: `&mut str` error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:23:15 + --> $DIR/ptr_arg.rs:24:15 | LL | fn do_path(x: &PathBuf) { | ^^^^^^^^ help: change this to: `&Path` error: writing `&mut PathBuf` instead of `&mut Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:27:19 + --> $DIR/ptr_arg.rs:28:19 | LL | fn do_path_mut(x: &mut PathBuf) { | ^^^^^^^^^^^^ help: change this to: `&mut Path` error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:35:18 + --> $DIR/ptr_arg.rs:36:18 | LL | fn do_vec(x: &Vec); | ^^^^^^^^^ help: change this to: `&[i64]` error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:48:14 + --> $DIR/ptr_arg.rs:49:14 | LL | fn cloned(x: &Vec) -> Vec { | ^^^^^^^^ @@ -60,7 +60,7 @@ LL ~ x.to_owned() | error: writing `&String` instead of `&str` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:57:18 + --> $DIR/ptr_arg.rs:58:18 | LL | fn str_cloned(x: &String) -> String { | ^^^^^^^ @@ -76,7 +76,7 @@ LL ~ x.to_owned() | error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:65:19 + --> $DIR/ptr_arg.rs:66:19 | LL | fn path_cloned(x: &PathBuf) -> PathBuf { | ^^^^^^^^ @@ -92,7 +92,7 @@ LL ~ x.to_path_buf() | error: writing `&String` instead of `&str` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:73:44 + --> $DIR/ptr_arg.rs:74:44 | LL | fn false_positive_capacity(x: &Vec, y: &String) { | ^^^^^^^ @@ -106,13 +106,19 @@ LL ~ let c = y; | error: using a reference to `Cow` is not recommended - --> $DIR/ptr_arg.rs:87:25 + --> $DIR/ptr_arg.rs:88:25 | LL | fn test_cow_with_ref(c: &Cow<[i32]>) {} | ^^^^^^^^^^^ help: change this to: `&[i32]` +error: writing `&String` instead of `&str` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:117:66 + | +LL | fn some_allowed(#[allow(clippy::ptr_arg)] _v: &Vec, _s: &String) {} + | ^^^^^^^ help: change this to: `&str` + error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:140:21 + --> $DIR/ptr_arg.rs:146:21 | LL | fn foo_vec(vec: &Vec) { | ^^^^^^^^ @@ -125,7 +131,7 @@ LL ~ let _ = vec.to_owned().clone(); | error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:145:23 + --> $DIR/ptr_arg.rs:151:23 | LL | fn foo_path(path: &PathBuf) { | ^^^^^^^^ @@ -138,7 +144,7 @@ LL ~ let _ = path.to_path_buf().clone(); | error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:150:21 + --> $DIR/ptr_arg.rs:156:21 | LL | fn foo_str(str: &PathBuf) { | ^^^^^^^^ @@ -151,10 +157,10 @@ LL ~ let _ = str.to_path_buf().clone(); | error: writing `&mut Vec` instead of `&mut [_]` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:156:29 + --> $DIR/ptr_arg.rs:162:29 | LL | fn mut_vec_slice_methods(v: &mut Vec) { | ^^^^^^^^^^^^^ help: change this to: `&mut [u32]` -error: aborting due to 16 previous errors +error: aborting due to 17 previous errors diff --git a/src/tools/clippy/tests/ui/redundant_clone.fixed b/src/tools/clippy/tests/ui/redundant_clone.fixed index 1525f6a93dfdd..da52c0acf93b0 100644 --- a/src/tools/clippy/tests/ui/redundant_clone.fixed +++ b/src/tools/clippy/tests/ui/redundant_clone.fixed @@ -1,6 +1,7 @@ // run-rustfix // rustfix-only-machine-applicable +#![feature(lint_reasons)] #![allow(clippy::implicit_clone, clippy::drop_non_drop)] use std::ffi::OsString; use std::path::Path; @@ -29,6 +30,10 @@ fn main() { #[allow(clippy::redundant_clone)] let _s = String::new().to_string(); + // Check that lint level works + #[expect(clippy::redundant_clone)] + let _s = String::new().to_string(); + let tup = (String::from("foo"),); let _t = tup.0; diff --git a/src/tools/clippy/tests/ui/redundant_clone.rs b/src/tools/clippy/tests/ui/redundant_clone.rs index 2f82aefd92830..5867d019dbb7d 100644 --- a/src/tools/clippy/tests/ui/redundant_clone.rs +++ b/src/tools/clippy/tests/ui/redundant_clone.rs @@ -1,6 +1,7 @@ // run-rustfix // rustfix-only-machine-applicable +#![feature(lint_reasons)] #![allow(clippy::implicit_clone, clippy::drop_non_drop)] use std::ffi::OsString; use std::path::Path; @@ -29,6 +30,10 @@ fn main() { #[allow(clippy::redundant_clone)] let _s = String::new().to_string(); + // Check that lint level works + #[expect(clippy::redundant_clone)] + let _s = String::new().to_string(); + let tup = (String::from("foo"),); let _t = tup.0.clone(); diff --git a/src/tools/clippy/tests/ui/redundant_clone.stderr b/src/tools/clippy/tests/ui/redundant_clone.stderr index 9f59017b26199..aa1dd7cbb45cd 100644 --- a/src/tools/clippy/tests/ui/redundant_clone.stderr +++ b/src/tools/clippy/tests/ui/redundant_clone.stderr @@ -1,180 +1,180 @@ error: redundant clone - --> $DIR/redundant_clone.rs:9:42 + --> $DIR/redundant_clone.rs:10:42 | LL | let _s = ["lorem", "ipsum"].join(" ").to_string(); | ^^^^^^^^^^^^ help: remove this | = note: `-D clippy::redundant-clone` implied by `-D warnings` note: this value is dropped without further use - --> $DIR/redundant_clone.rs:9:14 + --> $DIR/redundant_clone.rs:10:14 | LL | let _s = ["lorem", "ipsum"].join(" ").to_string(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:12:15 + --> $DIR/redundant_clone.rs:13:15 | LL | let _s = s.clone(); | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:12:14 + --> $DIR/redundant_clone.rs:13:14 | LL | let _s = s.clone(); | ^ error: redundant clone - --> $DIR/redundant_clone.rs:15:15 + --> $DIR/redundant_clone.rs:16:15 | LL | let _s = s.to_string(); | ^^^^^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:15:14 + --> $DIR/redundant_clone.rs:16:14 | LL | let _s = s.to_string(); | ^ error: redundant clone - --> $DIR/redundant_clone.rs:18:15 + --> $DIR/redundant_clone.rs:19:15 | LL | let _s = s.to_owned(); | ^^^^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:18:14 + --> $DIR/redundant_clone.rs:19:14 | LL | let _s = s.to_owned(); | ^ error: redundant clone - --> $DIR/redundant_clone.rs:20:42 + --> $DIR/redundant_clone.rs:21:42 | LL | let _s = Path::new("/a/b/").join("c").to_owned(); | ^^^^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:20:14 + --> $DIR/redundant_clone.rs:21:14 | LL | let _s = Path::new("/a/b/").join("c").to_owned(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:22:42 + --> $DIR/redundant_clone.rs:23:42 | LL | let _s = Path::new("/a/b/").join("c").to_path_buf(); | ^^^^^^^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:22:14 + --> $DIR/redundant_clone.rs:23:14 | LL | let _s = Path::new("/a/b/").join("c").to_path_buf(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:24:29 + --> $DIR/redundant_clone.rs:25:29 | LL | let _s = OsString::new().to_owned(); | ^^^^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:24:14 + --> $DIR/redundant_clone.rs:25:14 | LL | let _s = OsString::new().to_owned(); | ^^^^^^^^^^^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:26:29 + --> $DIR/redundant_clone.rs:27:29 | LL | let _s = OsString::new().to_os_string(); | ^^^^^^^^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:26:14 + --> $DIR/redundant_clone.rs:27:14 | LL | let _s = OsString::new().to_os_string(); | ^^^^^^^^^^^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:33:19 + --> $DIR/redundant_clone.rs:38:19 | LL | let _t = tup.0.clone(); | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:33:14 + --> $DIR/redundant_clone.rs:38:14 | LL | let _t = tup.0.clone(); | ^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:65:25 + --> $DIR/redundant_clone.rs:70:25 | LL | if b { (a.clone(), a.clone()) } else { (Alpha, a) } | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:65:24 + --> $DIR/redundant_clone.rs:70:24 | LL | if b { (a.clone(), a.clone()) } else { (Alpha, a) } | ^ error: redundant clone - --> $DIR/redundant_clone.rs:122:15 + --> $DIR/redundant_clone.rs:127:15 | LL | let _s = s.clone(); | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:122:14 + --> $DIR/redundant_clone.rs:127:14 | LL | let _s = s.clone(); | ^ error: redundant clone - --> $DIR/redundant_clone.rs:123:15 + --> $DIR/redundant_clone.rs:128:15 | LL | let _t = t.clone(); | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:123:14 + --> $DIR/redundant_clone.rs:128:14 | LL | let _t = t.clone(); | ^ error: redundant clone - --> $DIR/redundant_clone.rs:133:19 + --> $DIR/redundant_clone.rs:138:19 | LL | let _f = f.clone(); | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:133:18 + --> $DIR/redundant_clone.rs:138:18 | LL | let _f = f.clone(); | ^ error: redundant clone - --> $DIR/redundant_clone.rs:145:14 + --> $DIR/redundant_clone.rs:150:14 | LL | let y = x.clone().join("matthias"); | ^^^^^^^^ help: remove this | note: cloned value is neither consumed nor mutated - --> $DIR/redundant_clone.rs:145:13 + --> $DIR/redundant_clone.rs:150:13 | LL | let y = x.clone().join("matthias"); | ^^^^^^^^^ error: redundant clone - --> $DIR/redundant_clone.rs:199:11 + --> $DIR/redundant_clone.rs:204:11 | LL | foo(&x.clone(), move || { | ^^^^^^^^ help: remove this | note: this value is dropped without further use - --> $DIR/redundant_clone.rs:199:10 + --> $DIR/redundant_clone.rs:204:10 | LL | foo(&x.clone(), move || { | ^ diff --git a/src/tools/clippy/tests/ui/ref_binding_to_reference.rs b/src/tools/clippy/tests/ui/ref_binding_to_reference.rs index 570ef406e4a99..c8d0e56b197f4 100644 --- a/src/tools/clippy/tests/ui/ref_binding_to_reference.rs +++ b/src/tools/clippy/tests/ui/ref_binding_to_reference.rs @@ -2,7 +2,7 @@ #![feature(lint_reasons)] #![warn(clippy::ref_binding_to_reference)] -#![allow(clippy::needless_borrowed_reference)] +#![allow(clippy::needless_borrowed_reference, clippy::explicit_auto_deref)] fn f1(_: &str) {} macro_rules! m2 { diff --git a/src/tools/clippy/tests/ui/search_is_some_fixable_none.fixed b/src/tools/clippy/tests/ui/search_is_some_fixable_none.fixed index 6831fb2cf59e0..5190c5304c75d 100644 --- a/src/tools/clippy/tests/ui/search_is_some_fixable_none.fixed +++ b/src/tools/clippy/tests/ui/search_is_some_fixable_none.fixed @@ -1,5 +1,5 @@ // run-rustfix -#![allow(dead_code)] +#![allow(dead_code, clippy::explicit_auto_deref)] #![warn(clippy::search_is_some)] fn main() { diff --git a/src/tools/clippy/tests/ui/search_is_some_fixable_none.rs b/src/tools/clippy/tests/ui/search_is_some_fixable_none.rs index 767518ab0c0f5..310d87333a93c 100644 --- a/src/tools/clippy/tests/ui/search_is_some_fixable_none.rs +++ b/src/tools/clippy/tests/ui/search_is_some_fixable_none.rs @@ -1,5 +1,5 @@ // run-rustfix -#![allow(dead_code)] +#![allow(dead_code, clippy::explicit_auto_deref)] #![warn(clippy::search_is_some)] fn main() { diff --git a/src/tools/clippy/tests/ui/search_is_some_fixable_some.fixed b/src/tools/clippy/tests/ui/search_is_some_fixable_some.fixed index 7c940a2b069e4..5a2aee465d1bd 100644 --- a/src/tools/clippy/tests/ui/search_is_some_fixable_some.fixed +++ b/src/tools/clippy/tests/ui/search_is_some_fixable_some.fixed @@ -1,5 +1,5 @@ // run-rustfix -#![allow(dead_code)] +#![allow(dead_code, clippy::explicit_auto_deref)] #![warn(clippy::search_is_some)] fn main() { diff --git a/src/tools/clippy/tests/ui/search_is_some_fixable_some.rs b/src/tools/clippy/tests/ui/search_is_some_fixable_some.rs index 77fd52e4ce769..0e98ae18a217c 100644 --- a/src/tools/clippy/tests/ui/search_is_some_fixable_some.rs +++ b/src/tools/clippy/tests/ui/search_is_some_fixable_some.rs @@ -1,5 +1,5 @@ // run-rustfix -#![allow(dead_code)] +#![allow(dead_code, clippy::explicit_auto_deref)] #![warn(clippy::search_is_some)] fn main() { diff --git a/src/tools/clippy/tests/ui/should_impl_trait/corner_cases.rs b/src/tools/clippy/tests/ui/should_impl_trait/corner_cases.rs index 1ccb0a1d167d2..50999c6f2198b 100644 --- a/src/tools/clippy/tests/ui/should_impl_trait/corner_cases.rs +++ b/src/tools/clippy/tests/ui/should_impl_trait/corner_cases.rs @@ -8,7 +8,8 @@ clippy::missing_safety_doc, clippy::wrong_self_convention, clippy::missing_panics_doc, - clippy::return_self_not_must_use + clippy::return_self_not_must_use, + clippy::unused_async )] use std::ops::Mul; diff --git a/src/tools/clippy/tests/ui/significant_drop_in_scrutinee.rs b/src/tools/clippy/tests/ui/significant_drop_in_scrutinee.rs index 4347610f393f3..185e5009b60f1 100644 --- a/src/tools/clippy/tests/ui/significant_drop_in_scrutinee.rs +++ b/src/tools/clippy/tests/ui/significant_drop_in_scrutinee.rs @@ -64,6 +64,19 @@ fn should_trigger_lint_with_mutex_guard_in_match_scrutinee() { }; } +fn should_not_trigger_lint_with_mutex_guard_in_match_scrutinee_when_lint_allowed() { + let mutex = Mutex::new(State {}); + + // Lint should not be triggered because it is "allowed" below. + #[allow(clippy::significant_drop_in_scrutinee)] + match mutex.lock().unwrap().foo() { + true => { + mutex.lock().unwrap().bar(); + }, + false => {}, + }; +} + fn should_not_trigger_lint_for_insignificant_drop() { // Should not trigger lint because there are no temporaries whose drops have a significant // side effect. @@ -591,4 +604,20 @@ fn should_trigger_lint_for_read_write_lock_for_loop() { } } +fn do_bar(mutex: &Mutex) { + mutex.lock().unwrap().bar(); +} + +fn should_trigger_lint_without_significant_drop_in_arm() { + let mutex = Mutex::new(State {}); + + // Should trigger lint because the lifetime of the temporary MutexGuard is surprising because it + // is preserved until the end of the match, but there is no clear indication that this is the + // case. + match mutex.lock().unwrap().foo() { + true => do_bar(&mutex), + false => {}, + }; +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/significant_drop_in_scrutinee.stderr b/src/tools/clippy/tests/ui/significant_drop_in_scrutinee.stderr index 5ce9520441637..88ea6bce25b6b 100644 --- a/src/tools/clippy/tests/ui/significant_drop_in_scrutinee.stderr +++ b/src/tools/clippy/tests/ui/significant_drop_in_scrutinee.stderr @@ -1,174 +1,290 @@ -error: temporary with significant drop in match scrutinee +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression --> $DIR/significant_drop_in_scrutinee.rs:59:11 | LL | match mutex.lock().unwrap().foo() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | true => { +LL | mutex.lock().unwrap().bar(); + | --------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here | = note: `-D clippy::significant-drop-in-scrutinee` implied by `-D warnings` + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex.lock().unwrap().foo(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:132:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:145:11 | LL | match s.lock_m().get_the_value() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +... +LL | println!("{}", s.lock_m().get_the_value()); + | ---------- another value with significant `Drop` created here +... +LL | } + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = s.lock_m().get_the_value(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:153:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:166:11 | LL | match s.lock_m_m().get_the_value() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +... +LL | println!("{}", s.lock_m().get_the_value()); + | ---------- another value with significant `Drop` created here +... +LL | } + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = s.lock_m_m().get_the_value(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:201:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:214:11 | LL | match counter.temp_increment().len() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = counter.temp_increment().len(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:224:16 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:237:16 | LL | match (mutex1.lock().unwrap().s.len(), true) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex1.lock().unwrap().s.len(); LL ~ match (value, true) { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:233:22 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:246:22 | LL | match (true, mutex1.lock().unwrap().s.len(), true) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex1.lock().unwrap().s.len(); LL ~ match (true, value, true) { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:243:16 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:256:16 | LL | match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex1.lock().unwrap().s.len(); LL ~ match (value, true, mutex2.lock().unwrap().s.len()) { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:243:54 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:256:54 | LL | match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex2.lock().unwrap().s.len(); LL ~ match (mutex1.lock().unwrap().s.len(), true, value) { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:254:15 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:267:15 | LL | match mutex3.lock().unwrap().s.as_str() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:264:22 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:277:22 | LL | match (true, mutex3.lock().unwrap().s.as_str()) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:283:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:296:11 | LL | match mutex.lock().unwrap().s.len() > 1 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +LL | true => { +LL | mutex.lock().unwrap().s.len(); + | --------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex.lock().unwrap().s.len() > 1; LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:290:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:303:11 | LL | match 1 < mutex.lock().unwrap().s.len() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +LL | true => { +LL | mutex.lock().unwrap().s.len(); + | --------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = 1 < mutex.lock().unwrap().s.len(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:308:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:321:11 | LL | match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +... +LL | mutex1.lock().unwrap().s.len(), + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len() + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:319:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:332:11 | LL | match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +... +LL | mutex1.lock().unwrap().s.len(), + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len() + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:354:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:367:11 | LL | match get_mutex_guard().s.len() > 1 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +LL | true => { +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = get_mutex_guard().s.len() > 1; LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:371:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:384:11 | LL | match match i { | ___________^ @@ -179,7 +295,14 @@ LL | | .s LL | | .len() LL | | > 1 | |___________^ - | +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = match i { @@ -192,8 +315,8 @@ LL + > 1; LL ~ match value | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:397:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:410:11 | LL | match if i > 1 { | ___________^ @@ -204,7 +327,14 @@ LL | | mutex2.lock().unwrap() LL | | .len() LL | | > 1 | |___________^ - | +... +LL | mutex1.lock().unwrap().s.len(); + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ let value = if i > 1 { @@ -218,83 +348,150 @@ LL + > 1; LL ~ match value | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:451:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:464:11 | LL | match s.lock().deref().deref() { | ^^^^^^^^^^^^^^^^^^^^^^^^ +LL | 0 | 1 => println!("Value was less than 2"), +LL | _ => println!("Value is {}", s.lock().deref()), + | ---------------- another value with significant `Drop` created here +LL | }; + | - temporary lives until here | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match and create a copy | LL ~ let value = *s.lock().deref().deref(); LL ~ match value { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:479:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:492:11 | LL | match s.lock().deref().deref() { | ^^^^^^^^^^^^^^^^^^^^^^^^ +LL | matcher => println!("Value is {}", s.lock().deref()), + | ---------------- another value with significant `Drop` created here +LL | _ => println!("Value was not a match"), +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:498:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:511:11 | LL | match mutex.lock().unwrap().i = i { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +LL | _ => { +LL | println!("{}", mutex.lock().unwrap().i); + | --------------------- another value with significant `Drop` created here +LL | }, +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ mutex.lock().unwrap().i = i; LL ~ match () { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:504:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:517:11 | LL | match i = mutex.lock().unwrap().i { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +LL | _ => { +LL | println!("{}", mutex.lock().unwrap().i); + | --------------------- another value with significant `Drop` created here +LL | }, +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ i = mutex.lock().unwrap().i; LL ~ match () { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:510:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:523:11 | LL | match mutex.lock().unwrap().i += 1 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +LL | _ => { +LL | println!("{}", mutex.lock().unwrap().i); + | --------------------- another value with significant `Drop` created here +LL | }, +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ mutex.lock().unwrap().i += 1; LL ~ match () { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:516:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:529:11 | LL | match i += mutex.lock().unwrap().i { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +LL | _ => { +LL | println!("{}", mutex.lock().unwrap().i); + | --------------------- another value with significant `Drop` created here +LL | }, +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | LL ~ i += mutex.lock().unwrap().i; LL ~ match () { | -error: temporary with significant drop in match scrutinee - --> $DIR/significant_drop_in_scrutinee.rs:579:11 +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:592:11 | LL | match rwlock.read().unwrap().to_number() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior -error: temporary with significant drop in for loop - --> $DIR/significant_drop_in_scrutinee.rs:589:14 +error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression + --> $DIR/significant_drop_in_scrutinee.rs:602:14 | LL | for s in rwlock.read().unwrap().iter() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | println!("{}", s); +LL | } + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior + +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> $DIR/significant_drop_in_scrutinee.rs:617:11 + | +LL | match mutex.lock().unwrap().foo() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap().foo(); +LL ~ match value { + | -error: aborting due to 25 previous errors +error: aborting due to 26 previous errors diff --git a/src/tools/clippy/tests/ui/single_match.stderr b/src/tools/clippy/tests/ui/single_match.stderr index 318faf2571758..4d2b9ec5f903a 100644 --- a/src/tools/clippy/tests/ui/single_match.stderr +++ b/src/tools/clippy/tests/ui/single_match.stderr @@ -38,6 +38,15 @@ LL | | _ => {}, LL | | }; | |_____^ help: try this: `if let (2..=3, 7..=9) = z { dummy() }` +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:54:5 + | +LL | / match x { +LL | | Some(y) => dummy(), +LL | | None => (), +LL | | }; + | |_____^ help: try this: `if let Some(y) = x { dummy() }` + error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` --> $DIR/single_match.rs:59:5 | @@ -146,5 +155,5 @@ LL | | (..) => {}, LL | | } | |_____^ help: try this: `if let (.., Some(E::V), _) = (Some(42), Some(E::V), Some(42)) {}` -error: aborting due to 15 previous errors +error: aborting due to 16 previous errors diff --git a/src/tools/clippy/tests/ui/single_match_else.rs b/src/tools/clippy/tests/ui/single_match_else.rs index 82387f3d80b77..70d6febb71f9d 100644 --- a/src/tools/clippy/tests/ui/single_match_else.rs +++ b/src/tools/clippy/tests/ui/single_match_else.rs @@ -97,4 +97,23 @@ fn main() { return; }, } + + // lint here + use std::convert::Infallible; + match Result::::Ok(1) { + Ok(a) => println!("${:?}", a), + Err(_) => { + println!("else block"); + return; + } + } + + use std::borrow::Cow; + match Cow::from("moo") { + Cow::Owned(a) => println!("${:?}", a), + Cow::Borrowed(_) => { + println!("else block"); + return; + } + } } diff --git a/src/tools/clippy/tests/ui/single_match_else.stderr b/src/tools/clippy/tests/ui/single_match_else.stderr index 7756c6f204e67..38fd9c6a6782a 100644 --- a/src/tools/clippy/tests/ui/single_match_else.stderr +++ b/src/tools/clippy/tests/ui/single_match_else.stderr @@ -20,5 +20,85 @@ LL + None LL ~ }; | -error: aborting due to previous error +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:84:5 + | +LL | / match Some(1) { +LL | | Some(a) => println!("${:?}", a), +LL | | None => { +LL | | println!("else block"); +LL | | return +LL | | }, +LL | | } + | |_____^ + | +help: try this + | +LL ~ if let Some(a) = Some(1) { println!("${:?}", a) } else { +LL + println!("else block"); +LL + return +LL + } + | + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:93:5 + | +LL | / match Some(1) { +LL | | Some(a) => println!("${:?}", a), +LL | | None => { +LL | | println!("else block"); +LL | | return; +LL | | }, +LL | | } + | |_____^ + | +help: try this + | +LL ~ if let Some(a) = Some(1) { println!("${:?}", a) } else { +LL + println!("else block"); +LL + return; +LL + } + | + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:103:5 + | +LL | / match Result::::Ok(1) { +LL | | Ok(a) => println!("${:?}", a), +LL | | Err(_) => { +LL | | println!("else block"); +LL | | return; +LL | | } +LL | | } + | |_____^ + | +help: try this + | +LL ~ if let Ok(a) = Result::::Ok(1) { println!("${:?}", a) } else { +LL + println!("else block"); +LL + return; +LL + } + | + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:112:5 + | +LL | / match Cow::from("moo") { +LL | | Cow::Owned(a) => println!("${:?}", a), +LL | | Cow::Borrowed(_) => { +LL | | println!("else block"); +LL | | return; +LL | | } +LL | | } + | |_____^ + | +help: try this + | +LL ~ if let Cow::Owned(a) = Cow::from("moo") { println!("${:?}", a) } else { +LL + println!("else block"); +LL + return; +LL + } + | + +error: aborting due to 5 previous errors diff --git a/src/tools/clippy/tests/ui/slow_vector_initialization.rs b/src/tools/clippy/tests/ui/slow_vector_initialization.rs index 7e3d357ae50dc..16be9f6d203aa 100644 --- a/src/tools/clippy/tests/ui/slow_vector_initialization.rs +++ b/src/tools/clippy/tests/ui/slow_vector_initialization.rs @@ -19,6 +19,9 @@ fn extend_vector() { // Extend with mismatching expression should not be warned let mut vec3 = Vec::with_capacity(24322); vec3.extend(repeat(0).take(2)); + + let mut vec4 = Vec::with_capacity(len); + vec4.extend(repeat(0).take(vec4.capacity())); } fn mixed_extend_resize_vector() { @@ -48,6 +51,9 @@ fn resize_vector() { let mut vec3 = Vec::with_capacity(len - 10); vec3.resize(len - 10, 0); + let mut vec4 = Vec::with_capacity(len); + vec4.resize(vec4.capacity(), 0); + // Reinitialization should be warned vec1 = Vec::with_capacity(10); vec1.resize(10, 0); diff --git a/src/tools/clippy/tests/ui/slow_vector_initialization.stderr b/src/tools/clippy/tests/ui/slow_vector_initialization.stderr index 5d2788ec26086..cb3ce3e95a7af 100644 --- a/src/tools/clippy/tests/ui/slow_vector_initialization.stderr +++ b/src/tools/clippy/tests/ui/slow_vector_initialization.stderr @@ -17,7 +17,15 @@ LL | vec2.extend(repeat(0).take(len - 10)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:31:5 + --> $DIR/slow_vector_initialization.rs:24:5 + | +LL | let mut vec4 = Vec::with_capacity(len); + | ----------------------- help: consider replace allocation with: `vec![0; len]` +LL | vec4.extend(repeat(0).take(vec4.capacity())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:34:5 | LL | let mut resized_vec = Vec::with_capacity(30); | ---------------------- help: consider replace allocation with: `vec![0; 30]` @@ -25,7 +33,7 @@ LL | resized_vec.resize(30, 0); | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:34:5 + --> $DIR/slow_vector_initialization.rs:37:5 | LL | let mut extend_vec = Vec::with_capacity(30); | ---------------------- help: consider replace allocation with: `vec![0; 30]` @@ -33,7 +41,7 @@ LL | extend_vec.extend(repeat(0).take(30)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:41:5 + --> $DIR/slow_vector_initialization.rs:44:5 | LL | let mut vec1 = Vec::with_capacity(len); | ----------------------- help: consider replace allocation with: `vec![0; len]` @@ -41,7 +49,7 @@ LL | vec1.resize(len, 0); | ^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:49:5 + --> $DIR/slow_vector_initialization.rs:52:5 | LL | let mut vec3 = Vec::with_capacity(len - 10); | ---------------------------- help: consider replace allocation with: `vec![0; len - 10]` @@ -49,12 +57,20 @@ LL | vec3.resize(len - 10, 0); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:53:5 + --> $DIR/slow_vector_initialization.rs:55:5 + | +LL | let mut vec4 = Vec::with_capacity(len); + | ----------------------- help: consider replace allocation with: `vec![0; len]` +LL | vec4.resize(vec4.capacity(), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:59:5 | LL | vec1 = Vec::with_capacity(10); | ---------------------- help: consider replace allocation with: `vec![0; 10]` LL | vec1.resize(10, 0); | ^^^^^^^^^^^^^^^^^^ -error: aborting due to 7 previous errors +error: aborting due to 9 previous errors diff --git a/src/tools/clippy/tests/ui/transmute_ptr_to_ref.fixed b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.fixed new file mode 100644 index 0000000000000..e5fe9133f975e --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.fixed @@ -0,0 +1,78 @@ +// run-rustfix + +#![feature(custom_inner_attributes)] +#![warn(clippy::transmute_ptr_to_ref)] +#![allow(clippy::match_single_binding)] + +unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { + let _: &T = &*p; + let _: &T = &*p; + + let _: &mut T = &mut *m; + let _: &mut T = &mut *m; + + let _: &T = &*m; + let _: &T = &*m; + + let _: &mut T = &mut *(p as *mut T); + let _ = &mut *(p as *mut T); + + let _: &T = &*(o as *const T); + let _: &T = &*(o as *const T); + + let _: &mut T = &mut *(om as *mut T); + let _: &mut T = &mut *(om as *mut T); + + let _: &T = &*(om as *const T); + let _: &T = &*(om as *const T); +} + +fn _issue1231() { + struct Foo<'a, T> { + bar: &'a T, + } + + let raw = 42 as *const i32; + let _: &Foo = unsafe { &*raw.cast::>() }; + + let _: &Foo<&u8> = unsafe { &*raw.cast::>() }; + + type Bar<'a> = &'a u8; + let raw = 42 as *const i32; + unsafe { &*(raw as *const u8) }; +} + +unsafe fn _issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 { + match 0 { + 0 => &*x.cast::<&u32>(), + 1 => &*y.cast::<&u32>(), + 2 => &*x.cast::<&'b u32>(), + _ => &*y.cast::<&'b u32>(), + } +} + +unsafe fn _meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { + #![clippy::msrv = "1.38"] + let a = 0u32; + let a = &a as *const u32; + let _: &u32 = &*a; + let _: &u32 = &*a.cast::(); + match 0 { + 0 => &*x.cast::<&u32>(), + _ => &*x.cast::<&'b u32>(), + } +} + +unsafe fn _under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { + #![clippy::msrv = "1.37"] + let a = 0u32; + let a = &a as *const u32; + let _: &u32 = &*a; + let _: &u32 = &*(a as *const u32); + match 0 { + 0 => &*(x as *const () as *const &u32), + _ => &*(x as *const () as *const &'b u32), + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/transmute_ptr_to_ref.rs b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.rs index ba35c6adc4dee..fe49cdc324fdd 100644 --- a/src/tools/clippy/tests/ui/transmute_ptr_to_ref.rs +++ b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.rs @@ -1,4 +1,8 @@ +// run-rustfix + +#![feature(custom_inner_attributes)] #![warn(clippy::transmute_ptr_to_ref)] +#![allow(clippy::match_single_binding)] unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { let _: &T = std::mem::transmute(p); @@ -23,7 +27,7 @@ unsafe fn _ptr_to_ref(p: *const T, m: *mut T, o: *const U, om: *mut U) { let _: &T = &*(om as *const T); } -fn issue1231() { +fn _issue1231() { struct Foo<'a, T> { bar: &'a T, } @@ -38,4 +42,37 @@ fn issue1231() { unsafe { std::mem::transmute::<_, Bar>(raw) }; } +unsafe fn _issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 { + match 0 { + 0 => std::mem::transmute(x), + 1 => std::mem::transmute(y), + 2 => std::mem::transmute::<_, &&'b u32>(x), + _ => std::mem::transmute::<_, &&'b u32>(y), + } +} + +unsafe fn _meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { + #![clippy::msrv = "1.38"] + let a = 0u32; + let a = &a as *const u32; + let _: &u32 = std::mem::transmute(a); + let _: &u32 = std::mem::transmute::<_, &u32>(a); + match 0 { + 0 => std::mem::transmute(x), + _ => std::mem::transmute::<_, &&'b u32>(x), + } +} + +unsafe fn _under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 { + #![clippy::msrv = "1.37"] + let a = 0u32; + let a = &a as *const u32; + let _: &u32 = std::mem::transmute(a); + let _: &u32 = std::mem::transmute::<_, &u32>(a); + match 0 { + 0 => std::mem::transmute(x), + _ => std::mem::transmute::<_, &&'b u32>(x), + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/transmute_ptr_to_ref.stderr b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.stderr index df0598a58cd36..2993e5e7b0c91 100644 --- a/src/tools/clippy/tests/ui/transmute_ptr_to_ref.stderr +++ b/src/tools/clippy/tests/ui/transmute_ptr_to_ref.stderr @@ -1,5 +1,5 @@ error: transmute from a pointer type (`*const T`) to a reference type (`&T`) - --> $DIR/transmute_ptr_to_ref.rs:4:17 + --> $DIR/transmute_ptr_to_ref.rs:8:17 | LL | let _: &T = std::mem::transmute(p); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*p` @@ -7,58 +7,130 @@ LL | let _: &T = std::mem::transmute(p); = note: `-D clippy::transmute-ptr-to-ref` implied by `-D warnings` error: transmute from a pointer type (`*mut T`) to a reference type (`&mut T`) - --> $DIR/transmute_ptr_to_ref.rs:7:21 + --> $DIR/transmute_ptr_to_ref.rs:11:21 | LL | let _: &mut T = std::mem::transmute(m); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *m` error: transmute from a pointer type (`*mut T`) to a reference type (`&T`) - --> $DIR/transmute_ptr_to_ref.rs:10:17 + --> $DIR/transmute_ptr_to_ref.rs:14:17 | LL | let _: &T = std::mem::transmute(m); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*m` error: transmute from a pointer type (`*mut T`) to a reference type (`&mut T`) - --> $DIR/transmute_ptr_to_ref.rs:13:21 + --> $DIR/transmute_ptr_to_ref.rs:17:21 | LL | let _: &mut T = std::mem::transmute(p as *mut T); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *(p as *mut T)` error: transmute from a pointer type (`*const U`) to a reference type (`&T`) - --> $DIR/transmute_ptr_to_ref.rs:16:17 + --> $DIR/transmute_ptr_to_ref.rs:20:17 | LL | let _: &T = std::mem::transmute(o); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(o as *const T)` error: transmute from a pointer type (`*mut U`) to a reference type (`&mut T`) - --> $DIR/transmute_ptr_to_ref.rs:19:21 + --> $DIR/transmute_ptr_to_ref.rs:23:21 | LL | let _: &mut T = std::mem::transmute(om); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *(om as *mut T)` error: transmute from a pointer type (`*mut U`) to a reference type (`&T`) - --> $DIR/transmute_ptr_to_ref.rs:22:17 + --> $DIR/transmute_ptr_to_ref.rs:26:17 | LL | let _: &T = std::mem::transmute(om); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(om as *const T)` -error: transmute from a pointer type (`*const i32`) to a reference type (`&issue1231::Foo`) - --> $DIR/transmute_ptr_to_ref.rs:32:32 +error: transmute from a pointer type (`*const i32`) to a reference type (`&_issue1231::Foo`) + --> $DIR/transmute_ptr_to_ref.rs:36:32 | LL | let _: &Foo = unsafe { std::mem::transmute::<_, &Foo<_>>(raw) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(raw as *const Foo<_>)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*raw.cast::>()` -error: transmute from a pointer type (`*const i32`) to a reference type (`&issue1231::Foo<&u8>`) - --> $DIR/transmute_ptr_to_ref.rs:34:33 +error: transmute from a pointer type (`*const i32`) to a reference type (`&_issue1231::Foo<&u8>`) + --> $DIR/transmute_ptr_to_ref.rs:38:33 | LL | let _: &Foo<&u8> = unsafe { std::mem::transmute::<_, &Foo<&_>>(raw) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(raw as *const Foo<&_>)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*raw.cast::>()` error: transmute from a pointer type (`*const i32`) to a reference type (`&u8`) - --> $DIR/transmute_ptr_to_ref.rs:38:14 + --> $DIR/transmute_ptr_to_ref.rs:42:14 | LL | unsafe { std::mem::transmute::<_, Bar>(raw) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(raw as *const u8)` -error: aborting due to 10 previous errors +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:47:14 + | +LL | 0 => std::mem::transmute(x), + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*x.cast::<&u32>()` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:48:14 + | +LL | 1 => std::mem::transmute(y), + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*y.cast::<&u32>()` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:49:14 + | +LL | 2 => std::mem::transmute::<_, &&'b u32>(x), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*x.cast::<&'b u32>()` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:50:14 + | +LL | _ => std::mem::transmute::<_, &&'b u32>(y), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*y.cast::<&'b u32>()` + +error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`) + --> $DIR/transmute_ptr_to_ref.rs:58:19 + | +LL | let _: &u32 = std::mem::transmute(a); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*a` + +error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`) + --> $DIR/transmute_ptr_to_ref.rs:59:19 + | +LL | let _: &u32 = std::mem::transmute::<_, &u32>(a); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*a.cast::()` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:61:14 + | +LL | 0 => std::mem::transmute(x), + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*x.cast::<&u32>()` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:62:14 + | +LL | _ => std::mem::transmute::<_, &&'b u32>(x), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*x.cast::<&'b u32>()` + +error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`) + --> $DIR/transmute_ptr_to_ref.rs:70:19 + | +LL | let _: &u32 = std::mem::transmute(a); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*a` + +error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`) + --> $DIR/transmute_ptr_to_ref.rs:71:19 + | +LL | let _: &u32 = std::mem::transmute::<_, &u32>(a); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a as *const u32)` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:73:14 + | +LL | 0 => std::mem::transmute(x), + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(x as *const () as *const &u32)` + +error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`) + --> $DIR/transmute_ptr_to_ref.rs:74:14 + | +LL | _ => std::mem::transmute::<_, &&'b u32>(x), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(x as *const () as *const &'b u32)` + +error: aborting due to 22 previous errors diff --git a/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.rs b/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.rs index ea3dce17081b1..8f78f16a0a1a6 100644 --- a/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.rs +++ b/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.rs @@ -113,6 +113,44 @@ mod issue5876 { } } +fn _ref_to_opt_ref_implicit(x: &u32) -> Option<&u32> { + Some(x) +} + +#[allow(clippy::needless_lifetimes)] +fn _ref_to_opt_ref_explicit<'a>(x: &'a u32) -> Option<&'a u32> { + Some(x) +} + +fn _with_constraint<'a, 'b: 'a>(x: &'b u32, y: &'a u32) -> &'a u32 { + if true { x } else { y } +} + +async fn _async_implicit(x: &u32) -> &u32 { + x +} + +#[allow(clippy::needless_lifetimes)] +async fn _async_explicit<'a>(x: &'a u32) -> &'a u32 { + x +} + +fn _unrelated_lifetimes<'a, 'b>(_x: &'a u32, y: &'b u32) -> &'b u32 { + y +} + +fn _return_ptr(x: &u32) -> *const u32 { + x +} + +fn _return_field_ptr(x: &(u32, u32)) -> *const u32 { + &x.0 +} + +fn _return_field_ptr_addr_of(x: &(u32, u32)) -> *const u32 { + core::ptr::addr_of!(x.0) +} + fn main() { let (mut foo, bar) = (Foo(0), Bar([0; 24])); let (mut a, b, c, x, y, z) = (0, 0, Bar([0; 24]), 0, Foo(0), 0); diff --git a/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.stderr b/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.stderr index a88d35f3ea5a9..66ecb3d8e77ac 100644 --- a/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.stderr +++ b/src/tools/clippy/tests/ui/trivially_copy_pass_by_ref.stderr @@ -106,5 +106,11 @@ error: this argument (N byte) is passed by reference, but would be more efficien LL | fn foo(x: &i32) { | ^^^^ help: consider passing by value instead: `i32` -error: aborting due to 17 previous errors +error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte) + --> $DIR/trivially_copy_pass_by_ref.rs:138:37 + | +LL | fn _unrelated_lifetimes<'a, 'b>(_x: &'a u32, y: &'b u32) -> &'b u32 { + | ^^^^^^^ help: consider passing by value instead: `u32` + +error: aborting due to 18 previous errors diff --git a/src/tools/clippy/tests/ui/unused_async.rs b/src/tools/clippy/tests/ui/unused_async.rs index 2a3a506a57b14..4ca7f29b34cd0 100644 --- a/src/tools/clippy/tests/ui/unused_async.rs +++ b/src/tools/clippy/tests/ui/unused_async.rs @@ -1,5 +1,8 @@ #![warn(clippy::unused_async)] +use std::future::Future; +use std::pin::Pin; + async fn foo() -> i32 { 4 } @@ -8,6 +11,37 @@ async fn bar() -> i32 { foo().await } +struct S; + +impl S { + async fn unused(&self) -> i32 { + 1 + } + + async fn used(&self) -> i32 { + self.unused().await + } +} + +trait AsyncTrait { + fn trait_method() -> Pin>>; +} + +macro_rules! async_trait_impl { + () => { + impl AsyncTrait for S { + fn trait_method() -> Pin>> { + async fn unused() -> i32 { + 5 + } + + Box::pin(unused()) + } + } + }; +} +async_trait_impl!(); + fn main() { foo(); bar(); diff --git a/src/tools/clippy/tests/ui/unused_async.stderr b/src/tools/clippy/tests/ui/unused_async.stderr index cc6096d65d9f3..8b8ad065a4caa 100644 --- a/src/tools/clippy/tests/ui/unused_async.stderr +++ b/src/tools/clippy/tests/ui/unused_async.stderr @@ -1,5 +1,5 @@ error: unused `async` for function with no await statements - --> $DIR/unused_async.rs:3:1 + --> $DIR/unused_async.rs:6:1 | LL | / async fn foo() -> i32 { LL | | 4 @@ -9,5 +9,15 @@ LL | | } = note: `-D clippy::unused-async` implied by `-D warnings` = help: consider removing the `async` from this function -error: aborting due to previous error +error: unused `async` for function with no await statements + --> $DIR/unused_async.rs:17:5 + | +LL | / async fn unused(&self) -> i32 { +LL | | 1 +LL | | } + | |_____^ + | + = help: consider removing the `async` from this function + +error: aborting due to 2 previous errors diff --git a/src/tools/clippy/tests/ui/useless_asref.fixed b/src/tools/clippy/tests/ui/useless_asref.fixed index e431661d180de..90cb8945e77ff 100644 --- a/src/tools/clippy/tests/ui/useless_asref.fixed +++ b/src/tools/clippy/tests/ui/useless_asref.fixed @@ -1,6 +1,7 @@ // run-rustfix #![deny(clippy::useless_asref)] +#![allow(clippy::explicit_auto_deref)] use std::fmt::Debug; diff --git a/src/tools/clippy/tests/ui/useless_asref.rs b/src/tools/clippy/tests/ui/useless_asref.rs index 6ae931d7aa481..cb9f8ae5909a5 100644 --- a/src/tools/clippy/tests/ui/useless_asref.rs +++ b/src/tools/clippy/tests/ui/useless_asref.rs @@ -1,6 +1,7 @@ // run-rustfix #![deny(clippy::useless_asref)] +#![allow(clippy::explicit_auto_deref)] use std::fmt::Debug; diff --git a/src/tools/clippy/tests/ui/useless_asref.stderr b/src/tools/clippy/tests/ui/useless_asref.stderr index 5876b54aca8f0..b21c67bb3645f 100644 --- a/src/tools/clippy/tests/ui/useless_asref.stderr +++ b/src/tools/clippy/tests/ui/useless_asref.stderr @@ -1,5 +1,5 @@ error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:43:18 + --> $DIR/useless_asref.rs:44:18 | LL | foo_rstr(rstr.as_ref()); | ^^^^^^^^^^^^^ help: try this: `rstr` @@ -11,61 +11,61 @@ LL | #![deny(clippy::useless_asref)] | ^^^^^^^^^^^^^^^^^^^^^ error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:45:20 + --> $DIR/useless_asref.rs:46:20 | LL | foo_rslice(rslice.as_ref()); | ^^^^^^^^^^^^^^^ help: try this: `rslice` error: this call to `as_mut` does nothing - --> $DIR/useless_asref.rs:49:21 + --> $DIR/useless_asref.rs:50:21 | LL | foo_mrslice(mrslice.as_mut()); | ^^^^^^^^^^^^^^^^ help: try this: `mrslice` error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:51:20 + --> $DIR/useless_asref.rs:52:20 | LL | foo_rslice(mrslice.as_ref()); | ^^^^^^^^^^^^^^^^ help: try this: `mrslice` error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:58:20 + --> $DIR/useless_asref.rs:59:20 | LL | foo_rslice(rrrrrslice.as_ref()); | ^^^^^^^^^^^^^^^^^^^ help: try this: `rrrrrslice` error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:60:18 + --> $DIR/useless_asref.rs:61:18 | LL | foo_rstr(rrrrrstr.as_ref()); | ^^^^^^^^^^^^^^^^^ help: try this: `rrrrrstr` error: this call to `as_mut` does nothing - --> $DIR/useless_asref.rs:65:21 + --> $DIR/useless_asref.rs:66:21 | LL | foo_mrslice(mrrrrrslice.as_mut()); | ^^^^^^^^^^^^^^^^^^^^ help: try this: `mrrrrrslice` error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:67:20 + --> $DIR/useless_asref.rs:68:20 | LL | foo_rslice(mrrrrrslice.as_ref()); | ^^^^^^^^^^^^^^^^^^^^ help: try this: `mrrrrrslice` error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:71:16 + --> $DIR/useless_asref.rs:72:16 | LL | foo_rrrrmr((&&&&MoreRef).as_ref()); | ^^^^^^^^^^^^^^^^^^^^^^ help: try this: `(&&&&MoreRef)` error: this call to `as_mut` does nothing - --> $DIR/useless_asref.rs:121:13 + --> $DIR/useless_asref.rs:122:13 | LL | foo_mrt(mrt.as_mut()); | ^^^^^^^^^^^^ help: try this: `mrt` error: this call to `as_ref` does nothing - --> $DIR/useless_asref.rs:123:12 + --> $DIR/useless_asref.rs:124:12 | LL | foo_rt(mrt.as_ref()); | ^^^^^^^^^^^^ help: try this: `mrt` diff --git a/src/tools/clippy/tests/ui/while_let_loop.rs b/src/tools/clippy/tests/ui/while_let_loop.rs index 3ce699f551b20..c42e2a79a9bf9 100644 --- a/src/tools/clippy/tests/ui/while_let_loop.rs +++ b/src/tools/clippy/tests/ui/while_let_loop.rs @@ -117,3 +117,29 @@ fn issue1948() { } }; } + +fn issue_7913(m: &std::sync::Mutex>) { + // Don't lint. The lock shouldn't be held while printing. + loop { + let x = if let Some(x) = m.lock().unwrap().pop() { + x + } else { + break; + }; + + println!("{}", x); + } +} + +fn issue_5715(mut m: core::cell::RefCell>) { + // Don't lint. The temporary from `borrow_mut` must be dropped before overwriting the `RefCell`. + loop { + let x = if let &mut Some(x) = &mut *m.borrow_mut() { + x + } else { + break; + }; + + m = core::cell::RefCell::new(Some(x + 1)); + } +} diff --git a/src/tools/clippy/tests/ui/while_let_on_iterator.fixed b/src/tools/clippy/tests/ui/while_let_on_iterator.fixed index cb8892a3f009b..e9ff64431e19d 100644 --- a/src/tools/clippy/tests/ui/while_let_on_iterator.fixed +++ b/src/tools/clippy/tests/ui/while_let_on_iterator.fixed @@ -6,7 +6,8 @@ unreachable_code, unused_mut, dead_code, - clippy::equatable_if_let + clippy::equatable_if_let, + clippy::manual_find )] fn base() { diff --git a/src/tools/clippy/tests/ui/while_let_on_iterator.rs b/src/tools/clippy/tests/ui/while_let_on_iterator.rs index d91571844959e..03da39526b2fd 100644 --- a/src/tools/clippy/tests/ui/while_let_on_iterator.rs +++ b/src/tools/clippy/tests/ui/while_let_on_iterator.rs @@ -6,7 +6,8 @@ unreachable_code, unused_mut, dead_code, - clippy::equatable_if_let + clippy::equatable_if_let, + clippy::manual_find )] fn base() { diff --git a/src/tools/clippy/tests/ui/while_let_on_iterator.stderr b/src/tools/clippy/tests/ui/while_let_on_iterator.stderr index fb2b0f2467c0d..42859243855a6 100644 --- a/src/tools/clippy/tests/ui/while_let_on_iterator.stderr +++ b/src/tools/clippy/tests/ui/while_let_on_iterator.stderr @@ -1,5 +1,5 @@ error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:14:5 + --> $DIR/while_let_on_iterator.rs:15:5 | LL | while let Option::Some(x) = iter.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in iter` @@ -7,85 +7,85 @@ LL | while let Option::Some(x) = iter.next() { = note: `-D clippy::while-let-on-iterator` implied by `-D warnings` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:19:5 + --> $DIR/while_let_on_iterator.rs:20:5 | LL | while let Some(x) = iter.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in iter` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:24:5 + --> $DIR/while_let_on_iterator.rs:25:5 | LL | while let Some(_) = iter.next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in iter` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:100:9 + --> $DIR/while_let_on_iterator.rs:101:9 | LL | while let Some([..]) = it.next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for [..] in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:107:9 + --> $DIR/while_let_on_iterator.rs:108:9 | LL | while let Some([_x]) = it.next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for [_x] in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:120:9 + --> $DIR/while_let_on_iterator.rs:121:9 | LL | while let Some(x @ [_]) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x @ [_] in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:140:9 + --> $DIR/while_let_on_iterator.rs:141:9 | LL | while let Some(_) = y.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in y` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:197:9 + --> $DIR/while_let_on_iterator.rs:198:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:208:5 + --> $DIR/while_let_on_iterator.rs:209:5 | LL | while let Some(n) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for n in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:210:9 + --> $DIR/while_let_on_iterator.rs:211:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:219:9 + --> $DIR/while_let_on_iterator.rs:220:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:228:9 + --> $DIR/while_let_on_iterator.rs:229:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:245:9 + --> $DIR/while_let_on_iterator.rs:246:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:260:13 + --> $DIR/while_let_on_iterator.rs:261:13 | LL | while let Some(i) = self.0.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for i in self.0.by_ref()` error: manual `!RangeInclusive::contains` implementation - --> $DIR/while_let_on_iterator.rs:261:20 + --> $DIR/while_let_on_iterator.rs:262:20 | LL | if i < 3 || i > 7 { | ^^^^^^^^^^^^^^ help: use: `!(3..=7).contains(&i)` @@ -93,49 +93,49 @@ LL | if i < 3 || i > 7 { = note: `-D clippy::manual-range-contains` implied by `-D warnings` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:292:13 + --> $DIR/while_let_on_iterator.rs:293:13 | LL | while let Some(i) = self.0.0.0.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for i in self.0.0.0.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:321:5 + --> $DIR/while_let_on_iterator.rs:322:5 | LL | while let Some(n) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for n in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:333:9 + --> $DIR/while_let_on_iterator.rs:334:9 | LL | while let Some(x) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:347:5 + --> $DIR/while_let_on_iterator.rs:348:5 | LL | while let Some(x) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:358:5 + --> $DIR/while_let_on_iterator.rs:359:5 | LL | while let Some(x) = it.0.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.0.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:393:5 + --> $DIR/while_let_on_iterator.rs:394:5 | LL | while let Some(x) = s.x.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in s.x.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:400:5 + --> $DIR/while_let_on_iterator.rs:401:5 | LL | while let Some(x) = x[0].next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in x[0].by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:407:5 + --> $DIR/while_let_on_iterator.rs:408:5 | LL | while let Some(..) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in it` diff --git a/src/tools/clippy/tests/ui/zero_div_zero.rs b/src/tools/clippy/tests/ui/zero_div_zero.rs index ed3a9208fc3f0..968c58f40aefa 100644 --- a/src/tools/clippy/tests/ui/zero_div_zero.rs +++ b/src/tools/clippy/tests/ui/zero_div_zero.rs @@ -1,4 +1,4 @@ -#[allow(unused_variables)] +#[allow(unused_variables, clippy::eq_op)] #[warn(clippy::zero_divided_by_zero)] fn main() { let nan = 0.0 / 0.0; diff --git a/src/tools/clippy/tests/ui/zero_div_zero.stderr b/src/tools/clippy/tests/ui/zero_div_zero.stderr index 0931dd32e7af2..86563542e0602 100644 --- a/src/tools/clippy/tests/ui/zero_div_zero.stderr +++ b/src/tools/clippy/tests/ui/zero_div_zero.stderr @@ -1,11 +1,3 @@ -error: equal expressions as operands to `/` - --> $DIR/zero_div_zero.rs:4:15 - | -LL | let nan = 0.0 / 0.0; - | ^^^^^^^^^ - | - = note: `#[deny(clippy::eq_op)]` on by default - error: constant division of `0.0` with `0.0` will always result in NaN --> $DIR/zero_div_zero.rs:4:15 | @@ -15,12 +7,6 @@ LL | let nan = 0.0 / 0.0; = note: `-D clippy::zero-divided-by-zero` implied by `-D warnings` = help: consider using `f64::NAN` if you would like a constant representing NaN -error: equal expressions as operands to `/` - --> $DIR/zero_div_zero.rs:5:19 - | -LL | let f64_nan = 0.0 / 0.0f64; - | ^^^^^^^^^^^^ - error: constant division of `0.0` with `0.0` will always result in NaN --> $DIR/zero_div_zero.rs:5:19 | @@ -29,12 +15,6 @@ LL | let f64_nan = 0.0 / 0.0f64; | = help: consider using `f64::NAN` if you would like a constant representing NaN -error: equal expressions as operands to `/` - --> $DIR/zero_div_zero.rs:6:25 - | -LL | let other_f64_nan = 0.0f64 / 0.0; - | ^^^^^^^^^^^^ - error: constant division of `0.0` with `0.0` will always result in NaN --> $DIR/zero_div_zero.rs:6:25 | @@ -43,12 +23,6 @@ LL | let other_f64_nan = 0.0f64 / 0.0; | = help: consider using `f64::NAN` if you would like a constant representing NaN -error: equal expressions as operands to `/` - --> $DIR/zero_div_zero.rs:7:28 - | -LL | let one_more_f64_nan = 0.0f64 / 0.0f64; - | ^^^^^^^^^^^^^^^ - error: constant division of `0.0` with `0.0` will always result in NaN --> $DIR/zero_div_zero.rs:7:28 | @@ -57,5 +31,5 @@ LL | let one_more_f64_nan = 0.0f64 / 0.0f64; | = help: consider using `f64::NAN` if you would like a constant representing NaN -error: aborting due to 8 previous errors +error: aborting due to 4 previous errors diff --git a/src/tools/clippy/util/gh-pages/index.html b/src/tools/clippy/util/gh-pages/index.html index 4999cce75114b..6183089911bdc 100644 --- a/src/tools/clippy/util/gh-pages/index.html +++ b/src/tools/clippy/util/gh-pages/index.html @@ -10,6 +10,7 @@ + Clippy Lints