diff --git a/.github/workflows/clippy_dev.yml b/.github/workflows/clippy_dev.yml index 9a5416153ab..fe8bce00fa8 100644 --- a/.github/workflows/clippy_dev.yml +++ b/.github/workflows/clippy_dev.yml @@ -25,18 +25,6 @@ jobs: - name: Checkout uses: actions/checkout@v2.3.3 - - name: remove toolchain file - run: rm rust-toolchain - - - name: rust-toolchain - uses: actions-rs/toolchain@v1.0.6 - with: - toolchain: nightly - target: x86_64-unknown-linux-gnu - profile: minimal - components: rustfmt - default: true - # Run - name: Build run: cargo build --features deny-warnings diff --git a/CHANGELOG.md b/CHANGELOG.md index 85a6a6be8b7..f6bbcbdad89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2904,6 +2904,7 @@ Released 2018-09-13 [`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops [`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping [`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor +[`index_refutable_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice [`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing [`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask [`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string @@ -3038,6 +3039,7 @@ Released 2018-09-13 [`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark [`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop [`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return +[`needless_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_splitn [`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update [`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord [`neg_multiply`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_multiply diff --git a/Cargo.toml b/Cargo.toml index 602877bb9d6..fdafdffca64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ itertools = "0.10" quote = "1.0" serde = { version = "1.0", features = ["derive"] } syn = { version = "1.0", features = ["full"] } +parking_lot = "0.11.2" [build-dependencies] rustc_tools_util = { version = "0.2", path = "rustc_tools_util" } diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index affb283017c..d350d9a0018 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -12,6 +12,7 @@ opener = "0.5" regex = "1.5" shell-escape = "0.1" walkdir = "2.3" +cargo_metadata = "0.14" [features] deny-warnings = [] diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index c81eb40d52f..9ceadee58ea 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -1,6 +1,7 @@ use crate::clippy_project_root; +use itertools::Itertools; use shell_escape::escape; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::path::Path; use std::process::{self, Command}; use std::{fs, io}; @@ -56,15 +57,22 @@ pub fn run(check: bool, verbose: bool) { success &= cargo_fmt(context, &project_root.join("rustc_tools_util"))?; success &= cargo_fmt(context, &project_root.join("lintcheck"))?; - for entry in WalkDir::new(project_root.join("tests")) { - let entry = entry?; - let path = entry.path(); + let chunks = WalkDir::new(project_root.join("tests")) + .into_iter() + .filter_map(|entry| { + let entry = entry.expect("failed to find tests"); + let path = entry.path(); - if path.extension() != Some("rs".as_ref()) || entry.file_name() == "ice-3891.rs" { - continue; - } + if path.extension() != Some("rs".as_ref()) || entry.file_name() == "ice-3891.rs" { + None + } else { + Some(entry.into_path().into_os_string()) + } + }) + .chunks(250); - success &= rustfmt(context, path)?; + for chunk in &chunks { + success &= rustfmt(context, chunk)?; } Ok(success) @@ -149,7 +157,7 @@ fn exec( } fn cargo_fmt(context: &FmtContext, path: &Path) -> Result { - let mut args = vec!["+nightly", "fmt", "--all"]; + let mut args = vec!["fmt", "--all"]; if context.check { args.push("--"); args.push("--check"); @@ -162,7 +170,7 @@ fn cargo_fmt(context: &FmtContext, path: &Path) -> Result { fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> { let program = "rustfmt"; let dir = std::env::current_dir()?; - let args = &["+nightly", "--version"]; + let args = &["--version"]; if context.verbose { println!("{}", format_command(&program, &dir, args)); @@ -185,14 +193,14 @@ fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> { } } -fn rustfmt(context: &FmtContext, path: &Path) -> Result { - let mut args = vec!["+nightly".as_ref(), path.as_os_str()]; +fn rustfmt(context: &FmtContext, paths: impl Iterator) -> Result { + let mut args = Vec::new(); if context.check { - args.push("--check".as_ref()); + args.push(OsString::from("--check")); } + args.extend(paths); + let success = exec(context, "rustfmt", std::env::current_dir()?, &args)?; - if !success { - eprintln!("rustfmt failed on {}", path.display()); - } + Ok(success) } diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 5538f62c8e7..59fde447547 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -7,6 +7,7 @@ use std::path::PathBuf; pub mod bless; pub mod fmt; +pub mod lint; pub mod new_lint; pub mod serve; pub mod setup; diff --git a/clippy_dev/src/lint.rs b/clippy_dev/src/lint.rs new file mode 100644 index 00000000000..dfd16f71054 --- /dev/null +++ b/clippy_dev/src/lint.rs @@ -0,0 +1,20 @@ +use std::process::{self, Command}; + +pub fn run(filename: &str) { + let code = Command::new("cargo") + .args(["run", "--bin", "clippy-driver", "--"]) + .args(["-L", "./target/debug"]) + .args(["-Z", "no-codegen"]) + .args(["--edition", "2021"]) + .arg(filename) + .env("__CLIPPY_INTERNAL_TESTS", "true") + .status() + .expect("failed to run cargo") + .code(); + + if code.is_none() { + eprintln!("Killed by signal"); + } + + process::exit(code.unwrap_or(1)); +} diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index b5c04efce3b..30a241c8ba1 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -3,7 +3,7 @@ #![warn(rust_2018_idioms, unused_lifetimes)] use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; -use clippy_dev::{bless, fmt, new_lint, serve, setup, update_lints}; +use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints}; fn main() { let matches = get_clap_config(); @@ -55,6 +55,10 @@ fn main() { let lint = matches.value_of("lint"); serve::run(port, lint); }, + ("lint", Some(matches)) => { + let filename = matches.value_of("filename").unwrap(); + lint::run(filename); + }, _ => {}, } } @@ -219,5 +223,14 @@ fn get_clap_config<'a>() -> ArgMatches<'a> { ) .arg(Arg::with_name("lint").help("Which lint's page to load initially (optional)")), ) + .subcommand( + SubCommand::with_name("lint") + .about("Manually run clippy on a file") + .arg( + Arg::with_name("filename") + .required(true) + .help("The path to a file to lint"), + ), + ) .get_matches() } diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 43a478ee77d..59658b42c79 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -132,6 +132,18 @@ fn to_camel_case(name: &str) -> String { .collect() } +fn get_stabilisation_version() -> String { + let mut command = cargo_metadata::MetadataCommand::new(); + command.no_deps(); + if let Ok(metadata) = command.exec() { + if let Some(pkg) = metadata.packages.iter().find(|pkg| pkg.name == "clippy") { + return format!("{}.{}.0", pkg.version.minor, pkg.version.patch); + } + } + + String::from("") +} + fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String { let mut contents = format!( indoc! {" @@ -178,6 +190,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { }, }; + let version = get_stabilisation_version(); let lint_name = lint.name; let category = lint.category; let name_camel = to_camel_case(lint.name); @@ -212,7 +225,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { }); result.push_str(&format!( - indoc! {" + indoc! {r#" declare_clippy_lint! {{ /// ### What it does /// @@ -226,11 +239,13 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { /// ```rust /// // example code which does not raise clippy warning /// ``` + #[clippy::version = "{version}"] pub {name_upper}, {category}, - \"default lint description\" + "default lint description" }} - "}, + "#}, + version = version, name_upper = name_upper, category = category, )); diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 23f58bc4915..8dd073ef405 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -18,6 +18,7 @@ static DEC_CLIPPY_LINT_RE: SyncLazy = SyncLazy::new(|| { r#"(?x) declare_clippy_lint!\s*[\{(] (?:\s+///.*)* + (?:\s*\#\[clippy::version\s*=\s*"[^"]*"\])? \s+pub\s+(?P[A-Z_][A-Z_0-9]*)\s*,\s* (?P[a-z_]+)\s*,\s* "(?P(?:[^"\\]+|\\(?s).(?-s))*)"\s*[})] @@ -31,6 +32,7 @@ static DEC_DEPRECATED_LINT_RE: SyncLazy = SyncLazy::new(|| { r#"(?x) declare_deprecated_lint!\s*[{(]\s* (?:\s+///.*)* + (?:\s*\#\[clippy::version\s*=\s*"[^"]*"\])? \s+pub\s+(?P[A-Z_][A-Z_0-9]*)\s*,\s* "(?P(?:[^"\\]+|\\(?s).(?-s))*)"\s*[})] "#, @@ -495,6 +497,7 @@ fn test_parse_contents() { let result: Vec = parse_contents( r#" declare_clippy_lint! { + #[clippy::version = "Hello Clippy!"] pub PTR_ARG, style, "really long \ @@ -502,6 +505,7 @@ declare_clippy_lint! { } declare_clippy_lint!{ + #[clippy::version = "Test version"] pub DOC_MARKDOWN, pedantic, "single line" @@ -509,6 +513,7 @@ declare_clippy_lint!{ /// some doc comment declare_deprecated_lint! { + #[clippy::version = "I'm a version"] pub SHOULD_ASSERT_EQ, "`assert!()` will be more flexible with RFC 2011" } diff --git a/clippy_lints/src/absurd_extreme_comparisons.rs b/clippy_lints/src/absurd_extreme_comparisons.rs index 1483f3f9185..7665aa8380b 100644 --- a/clippy_lints/src/absurd_extreme_comparisons.rs +++ b/clippy_lints/src/absurd_extreme_comparisons.rs @@ -36,6 +36,7 @@ declare_clippy_lint! { /// 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" diff --git a/clippy_lints/src/approx_const.rs b/clippy_lints/src/approx_const.rs index fb54ac1ec51..12435eefbc4 100644 --- a/clippy_lints/src/approx_const.rs +++ b/clippy_lints/src/approx_const.rs @@ -33,6 +33,7 @@ declare_clippy_lint! { /// let x = std::f32::consts::PI; /// let y = std::f64::consts::FRAC_1_PI; /// ``` + #[clippy::version = "pre 1.29.0"] pub APPROX_CONSTANT, correctness, "the approximate of a known float constant (in `std::fXX::consts`)" diff --git a/clippy_lints/src/arithmetic.rs b/clippy_lints/src/arithmetic.rs index 36fe7b7a867..e0c1d6ab6e1 100644 --- a/clippy_lints/src/arithmetic.rs +++ b/clippy_lints/src/arithmetic.rs @@ -25,6 +25,7 @@ declare_clippy_lint! { /// # let a = 0; /// a + 1; /// ``` + #[clippy::version = "pre 1.29.0"] pub INTEGER_ARITHMETIC, restriction, "any integer arithmetic expression which could overflow or panic" @@ -43,6 +44,7 @@ declare_clippy_lint! { /// # let a = 0.0; /// a + 1.0; /// ``` + #[clippy::version = "pre 1.29.0"] pub FLOAT_ARITHMETIC, restriction, "any floating-point arithmetic statement" diff --git a/clippy_lints/src/as_conversions.rs b/clippy_lints/src/as_conversions.rs index 0be460d67a7..53704da1046 100644 --- a/clippy_lints/src/as_conversions.rs +++ b/clippy_lints/src/as_conversions.rs @@ -38,6 +38,7 @@ declare_clippy_lint! { /// f(a.try_into().expect("Unexpected u16 overflow in f")); /// ``` /// + #[clippy::version = "1.41.0"] pub AS_CONVERSIONS, restriction, "using a potentially dangerous silent `as` conversion" diff --git a/clippy_lints/src/asm_syntax.rs b/clippy_lints/src/asm_syntax.rs index 825832eb79d..0322698f029 100644 --- a/clippy_lints/src/asm_syntax.rs +++ b/clippy_lints/src/asm_syntax.rs @@ -75,6 +75,7 @@ declare_clippy_lint! { /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax)); /// # } /// ``` + #[clippy::version = "1.49.0"] pub INLINE_ASM_X86_INTEL_SYNTAX, restriction, "prefer AT&T x86 assembly syntax" @@ -111,6 +112,7 @@ declare_clippy_lint! { /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr); /// # } /// ``` + #[clippy::version = "1.49.0"] pub INLINE_ASM_X86_ATT_SYNTAX, restriction, "prefer Intel x86 assembly syntax" diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index d834a1d317a..521fc84ee9c 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -26,6 +26,7 @@ declare_clippy_lint! { /// const B: bool = false; /// assert!(B) /// ``` + #[clippy::version = "1.34.0"] pub ASSERTIONS_ON_CONSTANTS, style, "`assert!(true)` / `assert!(false)` will be optimized out by the compiler, and should probably be replaced by a `panic!()` or `unreachable!()`" diff --git a/clippy_lints/src/assign_ops.rs b/clippy_lints/src/assign_ops.rs index 2097a1feff9..e16f4369da9 100644 --- a/clippy_lints/src/assign_ops.rs +++ b/clippy_lints/src/assign_ops.rs @@ -34,6 +34,7 @@ declare_clippy_lint! { /// // Good /// 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" @@ -60,6 +61,7 @@ declare_clippy_lint! { /// // ... /// a += a + b; /// ``` + #[clippy::version = "pre 1.29.0"] pub MISREFACTORED_ASSIGN_OP, suspicious, "having a variable on both sides of an assign op" diff --git a/clippy_lints/src/async_yields_async.rs b/clippy_lints/src/async_yields_async.rs index 182736a5a20..0619490e73c 100644 --- a/clippy_lints/src/async_yields_async.rs +++ b/clippy_lints/src/async_yields_async.rs @@ -34,6 +34,7 @@ declare_clippy_lint! { /// }; /// } /// ``` + #[clippy::version = "1.48.0"] pub ASYNC_YIELDS_ASYNC, correctness, "async blocks that return a type that can be awaited" diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index 6f8b645dd70..1edb7c950e7 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -1,8 +1,9 @@ //! checks for attributes use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::match_panic_def_id; +use clippy_utils::msrvs; use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments}; +use clippy_utils::{extract_msrv_attr, match_panic_def_id, meets_msrv}; use if_chain::if_chain; use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem}; use rustc_errors::Applicability; @@ -12,7 +13,8 @@ use rustc_hir::{ use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::Span; use rustc_span::sym; use rustc_span::symbol::{Symbol, SymbolStr}; @@ -64,6 +66,7 @@ declare_clippy_lint! { /// #[inline(always)] /// fn not_quite_hot_code(..) { ... } /// ``` + #[clippy::version = "pre 1.29.0"] pub INLINE_ALWAYS, pedantic, "use of `#[inline(always)]`" @@ -98,6 +101,7 @@ declare_clippy_lint! { /// #[macro_use] /// extern crate baz; /// ``` + #[clippy::version = "pre 1.29.0"] pub USELESS_ATTRIBUTE, correctness, "use of lint attributes on `extern crate` items" @@ -117,6 +121,7 @@ declare_clippy_lint! { /// #[deprecated(since = "forever")] /// fn something_else() { /* ... */ } /// ``` + #[clippy::version = "pre 1.29.0"] pub DEPRECATED_SEMVER, correctness, "use of `#[deprecated(since = \"x\")]` where x is not semver" @@ -154,6 +159,7 @@ declare_clippy_lint! { /// #[allow(dead_code)] /// fn this_is_fine_too() { } /// ``` + #[clippy::version = "pre 1.29.0"] pub EMPTY_LINE_AFTER_OUTER_ATTR, nursery, "empty line after outer attribute" @@ -177,6 +183,7 @@ declare_clippy_lint! { /// ```rust /// #![deny(clippy::as_conversions)] /// ``` + #[clippy::version = "1.47.0"] pub BLANKET_CLIPPY_RESTRICTION_LINTS, suspicious, "enabling the complete restriction group" @@ -208,6 +215,7 @@ declare_clippy_lint! { /// #[rustfmt::skip] /// fn main() { } /// ``` + #[clippy::version = "1.32.0"] pub DEPRECATED_CFG_ATTR, complexity, "usage of `cfg_attr(rustfmt)` instead of tool attributes" @@ -240,6 +248,7 @@ declare_clippy_lint! { /// fn conditional() { } /// ``` /// Check the [Rust Reference](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os) for more details. + #[clippy::version = "1.45.0"] pub MISMATCHED_TARGET_OS, correctness, "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`" @@ -497,7 +506,11 @@ fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool { } } -declare_lint_pass!(EarlyAttributes => [ +pub struct EarlyAttributes { + pub msrv: Option, +} + +impl_lint_pass!(EarlyAttributes => [ DEPRECATED_CFG_ATTR, MISMATCHED_TARGET_OS, EMPTY_LINE_AFTER_OUTER_ATTR, @@ -509,9 +522,11 @@ impl EarlyLintPass for EarlyAttributes { } fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { - check_deprecated_cfg_attr(cx, attr); + check_deprecated_cfg_attr(cx, attr, self.msrv); check_mismatched_target_os(cx, attr); } + + extract_msrv_attr!(EarlyContext); } fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) { @@ -548,8 +563,9 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It } } -fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute) { +fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: Option) { if_chain! { + if meets_msrv(msrv.as_ref(), &msrvs::TOOL_ATTRIBUTES); // check cfg_attr if attr.has_name(sym::cfg_attr); if let Some(items) = attr.meta_item_list(); diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 28615b9217c..1cc3418d474 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -47,6 +47,7 @@ declare_clippy_lint! { /// bar.await; /// } /// ``` + #[clippy::version = "1.45.0"] pub AWAIT_HOLDING_LOCK, pedantic, "Inside an async function, holding a MutexGuard while calling await" @@ -88,6 +89,7 @@ declare_clippy_lint! { /// bar.await; /// } /// ``` + #[clippy::version = "1.49.0"] pub AWAIT_HOLDING_REFCELL_REF, pedantic, "Inside an async function, holding a RefCell ref while calling await" diff --git a/clippy_lints/src/bit_mask.rs b/clippy_lints/src/bit_mask.rs index 11346e7c96a..0977cf22b2c 100644 --- a/clippy_lints/src/bit_mask.rs +++ b/clippy_lints/src/bit_mask.rs @@ -41,6 +41,7 @@ declare_clippy_lint! { /// # 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`" @@ -73,6 +74,7 @@ declare_clippy_lint! { /// # 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`" @@ -95,6 +97,7 @@ declare_clippy_lint! { /// # 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" diff --git a/clippy_lints/src/blacklisted_name.rs b/clippy_lints/src/blacklisted_name.rs index 916c78c982a..1600fb25d89 100644 --- a/clippy_lints/src/blacklisted_name.rs +++ b/clippy_lints/src/blacklisted_name.rs @@ -17,6 +17,7 @@ declare_clippy_lint! { /// ```rust /// let foo = 3.14; /// ``` + #[clippy::version = "pre 1.29.0"] pub BLACKLISTED_NAME, style, "usage of a blacklisted/placeholder name" diff --git a/clippy_lints/src/blocks_in_if_conditions.rs b/clippy_lints/src/blocks_in_if_conditions.rs index 47e5b0d583d..b59f49357df 100644 --- a/clippy_lints/src/blocks_in_if_conditions.rs +++ b/clippy_lints/src/blocks_in_if_conditions.rs @@ -41,6 +41,7 @@ declare_clippy_lint! { /// let res = { let x = somefunc(); x }; /// if res { /* ... */ } /// ``` + #[clippy::version = "1.45.0"] pub BLOCKS_IN_IF_CONDITIONS, style, "useless or complex blocks that can be eliminated in conditions" diff --git a/clippy_lints/src/bool_assert_comparison.rs b/clippy_lints/src/bool_assert_comparison.rs index cdc192a47e4..d0b8c52a36a 100644 --- a/clippy_lints/src/bool_assert_comparison.rs +++ b/clippy_lints/src/bool_assert_comparison.rs @@ -23,6 +23,7 @@ declare_clippy_lint! { /// // Good /// assert!(!"a".is_empty()); /// ``` + #[clippy::version = "1.53.0"] pub BOOL_ASSERT_COMPARISON, style, "Using a boolean as comparison value in an assert_* macro when there is no need" @@ -72,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison { if let Some(span) = is_direct_expn_of(expr.span, mac) { if let Some(args) = higher::extract_assert_macro_args(expr) { if let [a, b, ..] = args[..] { - let nb_bool_args = is_bool_lit(a) as usize + is_bool_lit(b) as usize; + let nb_bool_args = usize::from(is_bool_lit(a)) + usize::from(is_bool_lit(b)); if nb_bool_args != 1 { // If there are two boolean arguments, we definitely don't understand diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 8282800c819..51835ee7488 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_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, in_macro, paths}; +use clippy_utils::{eq_expr_value, get_trait_def_id, paths}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -31,6 +31,7 @@ declare_clippy_lint! { /// if a && true // should be: if a /// if !(a == b) // should be: if a != b /// ``` + #[clippy::version = "pre 1.29.0"] pub NONMINIMAL_BOOL, complexity, "boolean expressions that can be written more concisely" @@ -52,6 +53,7 @@ declare_clippy_lint! { /// if a && b || a { ... } /// ``` /// The `b` is unnecessary, the expression is equivalent to `if a`. + #[clippy::version = "pre 1.29.0"] pub LOGIC_BUG, correctness, "boolean expressions that contain terminals which can be eliminated" @@ -453,22 +455,20 @@ impl<'a, 'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'a, 'tcx> { type Map = Map<'tcx>; fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if in_macro(e.span) { - return; - } - match &e.kind { - ExprKind::Binary(binop, _, _) if binop.node == BinOpKind::Or || binop.node == BinOpKind::And => { - self.bool_expr(e); - }, - ExprKind::Unary(UnOp::Not, inner) => { - if self.cx.typeck_results().node_types()[inner.hir_id].is_bool() { + if !e.span.from_expansion() { + match &e.kind { + ExprKind::Binary(binop, _, _) if binop.node == BinOpKind::Or || binop.node == BinOpKind::And => { self.bool_expr(e); - } else { - walk_expr(self, e); - } - }, - _ => walk_expr(self, e), + }, + ExprKind::Unary(UnOp::Not, inner) => { + if self.cx.typeck_results().node_types()[inner.hir_id].is_bool() { + self.bool_expr(e); + } + }, + _ => {}, + } } + walk_expr(self, e); } fn nested_visit_map(&mut self) -> NestedVisitorMap { NestedVisitorMap::None diff --git a/clippy_lints/src/bytecount.rs b/clippy_lints/src/bytecount.rs index a07cd5e5f4e..a938ada9d2a 100644 --- a/clippy_lints/src/bytecount.rs +++ b/clippy_lints/src/bytecount.rs @@ -30,6 +30,7 @@ declare_clippy_lint! { /// # let vec = vec![1_u8]; /// &vec.iter().filter(|x| **x == 0u8).count(); // use bytecount::count instead /// ``` + #[clippy::version = "pre 1.29.0"] pub NAIVE_BYTECOUNT, pedantic, "use of naive `.filter(|&x| x == y).count()` to count byte values" diff --git a/clippy_lints/src/cargo_common_metadata.rs b/clippy_lints/src/cargo_common_metadata.rs index ff619c59b6e..23f79fdc682 100644 --- a/clippy_lints/src/cargo_common_metadata.rs +++ b/clippy_lints/src/cargo_common_metadata.rs @@ -42,6 +42,7 @@ declare_clippy_lint! { /// keywords = ["clippy", "lint", "plugin"] /// categories = ["development-tools", "development-tools::cargo-plugins"] /// ``` + #[clippy::version = "1.32.0"] pub CARGO_COMMON_METADATA, cargo, "common metadata is defined in `Cargo.toml`" diff --git a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/case_sensitive_file_extension_comparisons.rs index c876553c165..3f286dd9e2f 100644 --- a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs +++ b/clippy_lints/src/case_sensitive_file_extension_comparisons.rs @@ -27,6 +27,7 @@ declare_clippy_lint! { /// filename.rsplit('.').next().map(|ext| ext.eq_ignore_ascii_case("rs")) == Some(true) /// } /// ``` + #[clippy::version = "1.51.0"] pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, pedantic, "Checks for calls to ends_with with case-sensitive file extensions" diff --git a/clippy_lints/src/casts/cast_lossless.rs b/clippy_lints/src/casts/cast_lossless.rs index 869deecfbd5..4a95bed1148 100644 --- a/clippy_lints/src/casts/cast_lossless.rs +++ b/clippy_lints/src/casts/cast_lossless.rs @@ -1,16 +1,24 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::in_constant; use clippy_utils::source::snippet_opt; use clippy_utils::ty::is_isize_or_usize; +use clippy_utils::{in_constant, meets_msrv, msrvs}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::{self, FloatTy, Ty}; +use rustc_semver::RustcVersion; use super::{utils, CAST_LOSSLESS}; -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { - if !should_lint(cx, expr, cast_from, cast_to) { +pub(super) fn check( + cx: &LateContext<'_>, + expr: &Expr<'_>, + cast_op: &Expr<'_>, + cast_from: Ty<'_>, + cast_to: Ty<'_>, + msrv: &Option, +) { + if !should_lint(cx, expr, cast_from, cast_to, msrv) { return; } @@ -32,21 +40,36 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, c }, ); + let message = if cast_from.is_bool() { + format!( + "casting `{0:}` to `{1:}` is more cleanly stated with `{1:}::from(_)`", + cast_from, cast_to + ) + } else { + format!( + "casting `{}` to `{}` may become silently lossy if you later change the type", + cast_from, cast_to + ) + }; + span_lint_and_sugg( cx, CAST_LOSSLESS, expr.span, - &format!( - "casting `{}` to `{}` may become silently lossy if you later change the type", - cast_from, cast_to - ), + &message, "try", format!("{}::from({})", cast_to, sugg), applicability, ); } -fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) -> bool { +fn should_lint( + cx: &LateContext<'_>, + expr: &Expr<'_>, + cast_from: Ty<'_>, + cast_to: Ty<'_>, + msrv: &Option, +) -> bool { // Do not suggest using From in consts/statics until it is valid to do so (see #2267). if in_constant(cx, expr.hir_id) { return false; @@ -72,7 +95,7 @@ fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to }; from_nbits < to_nbits }, - + (false, true) if matches!(cast_from.kind(), ty::Bool) && meets_msrv(msrv.as_ref(), &msrvs::FROM_BOOL) => true, (_, _) => { matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64)) }, diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 233abd17894..aee1e50b94a 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -40,6 +40,7 @@ declare_clippy_lint! { /// let x = u64::MAX; /// x as f64; /// ``` + #[clippy::version = "pre 1.29.0"] pub CAST_PRECISION_LOSS, pedantic, "casts that cause loss of precision, e.g., `x as f32` where `x: u64`" @@ -61,6 +62,7 @@ declare_clippy_lint! { /// let y: i8 = -1; /// y as u128; // will return 18446744073709551615 /// ``` + #[clippy::version = "pre 1.29.0"] pub CAST_SIGN_LOSS, pedantic, "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`" @@ -83,6 +85,7 @@ declare_clippy_lint! { /// x as u8 /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub CAST_POSSIBLE_TRUNCATION, pedantic, "casts that may cause truncation of the value, e.g., `x as u8` where `x: u32`, or `x as i32` where `x: f32`" @@ -106,6 +109,7 @@ declare_clippy_lint! { /// ```rust /// u32::MAX as i32; // will yield a value of `-1` /// ``` + #[clippy::version = "pre 1.29.0"] pub CAST_POSSIBLE_WRAP, pedantic, "casts that may cause wrapping around the value, e.g., `x as i32` where `x: u32` and `x > i32::MAX`" @@ -138,6 +142,7 @@ declare_clippy_lint! { /// u64::from(x) /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub CAST_LOSSLESS, pedantic, "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`" @@ -163,6 +168,7 @@ declare_clippy_lint! { /// let _ = 2_i32; /// let _ = 0.5_f32; /// ``` + #[clippy::version = "pre 1.29.0"] pub UNNECESSARY_CAST, complexity, "cast to the same type, e.g., `x as i32` where `x: i32`" @@ -190,6 +196,7 @@ declare_clippy_lint! { /// (&1u8 as *const u8).cast::(); /// (&mut 1u8 as *mut u8).cast::(); /// ``` + #[clippy::version = "pre 1.29.0"] pub CAST_PTR_ALIGNMENT, pedantic, "cast from a pointer to a more-strictly-aligned pointer" @@ -217,6 +224,7 @@ declare_clippy_lint! { /// fn fun2() -> i32 { 1 } /// let a = fun2 as usize; /// ``` + #[clippy::version = "pre 1.29.0"] pub FN_TO_NUMERIC_CAST, style, "casting a function pointer to a numeric type other than usize" @@ -247,6 +255,7 @@ declare_clippy_lint! { /// let fn_ptr = fn2 as usize; /// let fn_ptr_truncated = fn_ptr as i32; /// ``` + #[clippy::version = "pre 1.29.0"] pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION, style, "casting a function pointer to a numeric type not wide enough to store the address" @@ -283,6 +292,7 @@ declare_clippy_lint! { /// } /// let _ = fn3 as fn() -> u16; /// ``` + #[clippy::version = "1.58.0"] pub FN_TO_NUMERIC_CAST_ANY, restriction, "casting a function pointer to any integer type" @@ -317,6 +327,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.33.0"] pub CAST_REF_TO_MUT, correctness, "a cast of reference to a mutable pointer" @@ -344,6 +355,7 @@ declare_clippy_lint! { /// ```rust,ignore /// b'x' /// ``` + #[clippy::version = "pre 1.29.0"] pub CHAR_LIT_AS_U8, complexity, "casting a character literal to `u8` truncates" @@ -372,6 +384,7 @@ declare_clippy_lint! { /// let _ = ptr.cast::(); /// let _ = mut_ptr.cast::(); /// ``` + #[clippy::version = "1.51.0"] pub PTR_AS_PTR, pedantic, "casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`" @@ -426,12 +439,16 @@ impl<'tcx> LateLintPass<'tcx> for Casts { fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to); fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to); fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to); - if cast_from.is_numeric() && cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) { - cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to); - cast_possible_wrap::check(cx, expr, cast_from, cast_to); - cast_precision_loss::check(cx, expr, cast_from, cast_to); - cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to); - cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to); + + if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) { + if cast_from.is_numeric() { + cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to); + cast_possible_wrap::check(cx, expr, cast_from, cast_to); + cast_precision_loss::check(cx, expr, cast_from, cast_to); + cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to); + } + + cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv); } } diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 842bbf006cc..ffe6340bd77 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -36,6 +36,7 @@ declare_clippy_lint! { /// i32::try_from(foo).is_ok() /// # ; /// ``` + #[clippy::version = "1.37.0"] pub CHECKED_CONVERSIONS, pedantic, "`try_from` could replace manual bounds checking when casting" diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index 1ccb8c5d880..84a2373efe1 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -27,6 +27,7 @@ declare_clippy_lint! { /// /// ### Example /// No. You'll see it when you get the warning. + #[clippy::version = "1.35.0"] pub COGNITIVE_COMPLEXITY, nursery, "functions that should be split up into multiple functions" diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index 4aa87980715..f03f34e5a4b 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -47,6 +47,7 @@ declare_clippy_lint! { /// … /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub COLLAPSIBLE_IF, style, "nested `if`s that can be collapsed (e.g., `if x { if y { ... } }`" @@ -82,6 +83,7 @@ declare_clippy_lint! { /// … /// } /// ``` + #[clippy::version = "1.51.0"] pub COLLAPSIBLE_ELSE_IF, style, "nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)" diff --git a/clippy_lints/src/collapsible_match.rs b/clippy_lints/src/collapsible_match.rs index a4693fa213b..626f9971f01 100644 --- a/clippy_lints/src/collapsible_match.rs +++ b/clippy_lints/src/collapsible_match.rs @@ -41,6 +41,7 @@ declare_clippy_lint! { /// }; /// } /// ``` + #[clippy::version = "1.50.0"] pub COLLAPSIBLE_MATCH, style, "Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together." diff --git a/clippy_lints/src/comparison_chain.rs b/clippy_lints/src/comparison_chain.rs index 597a3c67024..399d11472b0 100644 --- a/clippy_lints/src/comparison_chain.rs +++ b/clippy_lints/src/comparison_chain.rs @@ -49,6 +49,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.40.0"] pub COMPARISON_CHAIN, style, "`if`s that can be rewritten with `match` and `cmp`" diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 8abf10c0d1c..d07bc23235b 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then}; use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt}; use clippy_utils::{ - both, count_eq, eq_expr_value, get_enclosing_block, get_parent_expr, if_sequence, in_macro, is_else_clause, - is_lint_allowed, search_same, ContainsName, SpanlessEq, SpanlessHash, + both, count_eq, eq_expr_value, get_enclosing_block, get_parent_expr, if_sequence, is_else_clause, is_lint_allowed, + search_same, ContainsName, SpanlessEq, SpanlessHash, }; use if_chain::if_chain; use rustc_data_structures::fx::FxHashSet; @@ -41,6 +41,7 @@ declare_clippy_lint! { /// … /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub IFS_SAME_COND, correctness, "consecutive `if`s with the same condition" @@ -88,6 +89,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.41.0"] pub SAME_FUNCTIONS_IN_IF_CONDITION, pedantic, "consecutive `if`s with the same function call" @@ -109,6 +111,7 @@ declare_clippy_lint! { /// 42 /// }; /// ``` + #[clippy::version = "pre 1.29.0"] pub IF_SAME_THEN_ELSE, correctness, "`if` with the same `then` and `else` blocks" @@ -147,6 +150,7 @@ declare_clippy_lint! { /// 42 /// }; /// ``` + #[clippy::version = "1.53.0"] pub BRANCHES_SHARING_CODE, nursery, "`if` statement with shared code in all blocks" @@ -623,7 +627,7 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { // Do not lint if any expr originates from a macro - if in_macro(lhs.span) || in_macro(rhs.span) { + if lhs.span.from_expansion() || rhs.span.from_expansion() { return false; } // Do not spawn warning if `IFS_SAME_COND` already produced it. diff --git a/clippy_lints/src/copy_iterator.rs b/clippy_lints/src/copy_iterator.rs index c2e9e8b3ab7..026683f6006 100644 --- a/clippy_lints/src/copy_iterator.rs +++ b/clippy_lints/src/copy_iterator.rs @@ -28,6 +28,7 @@ declare_clippy_lint! { /// let a: Vec<_> = my_iterator.take(1).collect(); /// let b: Vec<_> = my_iterator.collect(); /// ``` + #[clippy::version = "1.30.0"] pub COPY_ITERATOR, pedantic, "implementing `Iterator` on a `Copy` type" diff --git a/clippy_lints/src/create_dir.rs b/clippy_lints/src/create_dir.rs index e4ee2772483..6bc4054a5ab 100644 --- a/clippy_lints/src/create_dir.rs +++ b/clippy_lints/src/create_dir.rs @@ -23,6 +23,7 @@ declare_clippy_lint! { /// ```rust /// std::fs::create_dir_all("foo"); /// ``` + #[clippy::version = "1.48.0"] pub CREATE_DIR, restriction, "calling `std::fs::create_dir` instead of `std::fs::create_dir_all`" diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index bab4a696f83..1aae4c81c73 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -23,6 +23,7 @@ declare_clippy_lint! { /// // Good /// true /// ``` + #[clippy::version = "1.34.0"] pub DBG_MACRO, restriction, "`dbg!` macro is intended as a debugging tool" diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index cde27d3ad2a..a0b137efe22 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg}; use clippy_utils::source::snippet_with_macro_callsite; use clippy_utils::ty::{has_drop, is_copy}; -use clippy_utils::{any_parent_is_automatically_derived, contains_name, in_macro, match_def_path, paths}; +use clippy_utils::{any_parent_is_automatically_derived, contains_name, match_def_path, paths}; use if_chain::if_chain; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; @@ -29,6 +29,7 @@ declare_clippy_lint! { /// // Good /// let s = String::default(); /// ``` + #[clippy::version = "pre 1.29.0"] pub DEFAULT_TRAIT_ACCESS, pedantic, "checks for literal calls to `Default::default()`" @@ -62,6 +63,7 @@ declare_clippy_lint! { /// .. Default::default() /// }; /// ``` + #[clippy::version = "1.49.0"] pub FIELD_REASSIGN_WITH_DEFAULT, style, "binding initialized with Default should have its fields set in the initializer" @@ -78,7 +80,7 @@ impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]); impl LateLintPass<'_> for Default { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if_chain! { - if !in_macro(expr.span); + if !expr.span.from_expansion(); // Avoid cases already linted by `field_reassign_with_default` if !self.reassigned_linted.contains(&expr.span); if let ExprKind::Call(path, ..) = expr.kind; @@ -125,7 +127,7 @@ impl LateLintPass<'_> for Default { if let StmtKind::Local(local) = stmt.kind; if let Some(expr) = local.init; if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id); - if !in_macro(expr.span); + if !expr.span.from_expansion(); // only take bindings to identifiers if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind; // only when assigning `... = Default::default()` diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 3f1b7ea6214..3573ea5f026 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -46,6 +46,7 @@ declare_clippy_lint! { /// let i = 10i32; /// let f = 1.23f64; /// ``` + #[clippy::version = "1.52.0"] pub DEFAULT_NUMERIC_FALLBACK, restriction, "usage of unconstrained numeric literals which may cause default numeric fallback." diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index 9d8524ec91c..bba27576c89 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -21,6 +21,7 @@ declare_deprecated_lint! { /// ### Deprecation reason /// This used to check for `assert!(a == b)` and recommend /// replacement with `assert_eq!(a, b)`, but this is no longer needed after RFC 2011. + #[clippy::version = "pre 1.29.0"] pub SHOULD_ASSERT_EQ, "`assert!()` will be more flexible with RFC 2011" } @@ -32,6 +33,7 @@ declare_deprecated_lint! { /// ### Deprecation reason /// This used to check for `Vec::extend`, which was slower than /// `Vec::extend_from_slice`. Thanks to specialization, this is no longer true. + #[clippy::version = "pre 1.29.0"] pub EXTEND_FROM_SLICE, "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice" } @@ -45,6 +47,7 @@ declare_deprecated_lint! { /// an infinite iterator, which is better expressed by `iter::repeat`, /// but the method has been removed for `Iterator::step_by` which panics /// if given a zero + #[clippy::version = "pre 1.29.0"] pub RANGE_STEP_BY_ZERO, "`iterator.step_by(0)` panics nowadays" } @@ -56,6 +59,7 @@ declare_deprecated_lint! { /// ### Deprecation reason /// This used to check for `Vec::as_slice`, which was unstable with good /// stable alternatives. `Vec::as_slice` has now been stabilized. + #[clippy::version = "pre 1.29.0"] pub UNSTABLE_AS_SLICE, "`Vec::as_slice` has been stabilized in 1.7" } @@ -67,6 +71,7 @@ declare_deprecated_lint! { /// ### Deprecation reason /// This used to check for `Vec::as_mut_slice`, which was unstable with good /// stable alternatives. `Vec::as_mut_slice` has now been stabilized. + #[clippy::version = "pre 1.29.0"] pub UNSTABLE_AS_MUT_SLICE, "`Vec::as_mut_slice` has been stabilized in 1.7" } @@ -80,6 +85,7 @@ declare_deprecated_lint! { /// between non-pointer types of differing alignment is well-defined behavior (it's semantically /// equivalent to a memcpy). This lint has thus been refactored into two separate lints: /// cast_ptr_alignment and transmute_ptr_to_ptr. + #[clippy::version = "pre 1.29.0"] pub MISALIGNED_TRANSMUTE, "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr" } @@ -92,6 +98,7 @@ declare_deprecated_lint! { /// This lint is too subjective, not having a good reason for being in clippy. /// Additionally, compound assignment operators may be overloaded separately from their non-assigning /// counterparts, so this lint may suggest a change in behavior or the code may not compile. + #[clippy::version = "1.30.0"] pub ASSIGN_OPS, "using compound assignment operators (e.g., `+=`) is harmless" } @@ -104,6 +111,7 @@ declare_deprecated_lint! { /// The original rule will only lint for `if let`. After /// making it support to lint `match`, naming as `if let` is not suitable for it. /// So, this lint is deprecated. + #[clippy::version = "pre 1.29.0"] pub IF_LET_REDUNDANT_PATTERN_MATCHING, "this lint has been changed to redundant_pattern_matching" } @@ -117,6 +125,7 @@ declare_deprecated_lint! { /// Vec::with_capacity(n); vec.set_len(n);` with `let vec = vec![0; n];`. The /// replacement has very different performance characteristics so the lint is /// deprecated. + #[clippy::version = "pre 1.29.0"] pub UNSAFE_VECTOR_INITIALIZATION, "the replacement suggested by this lint had substantially different behavior" } @@ -127,6 +136,7 @@ declare_deprecated_lint! { /// /// ### Deprecation reason /// This lint has been superseded by #[must_use] in rustc. + #[clippy::version = "1.39.0"] pub UNUSED_COLLECT, "`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint" } @@ -137,6 +147,7 @@ declare_deprecated_lint! { /// /// ### Deprecation reason /// Associated-constants are now preferred. + #[clippy::version = "1.44.0"] pub REPLACE_CONSTS, "associated-constants `MIN`/`MAX` of integers are preferred to `{min,max}_value()` and module constants" } @@ -147,6 +158,7 @@ declare_deprecated_lint! { /// /// ### Deprecation reason /// The regex! macro does not exist anymore. + #[clippy::version = "1.47.0"] pub REGEX_MACRO, "the regex! macro has been removed from the regex crate in 2018" } @@ -158,6 +170,7 @@ declare_deprecated_lint! { /// ### Deprecation reason /// This lint has been replaced by `manual_find_map`, a /// more specific lint. + #[clippy::version = "1.51.0"] pub FIND_MAP, "this lint has been replaced by `manual_find_map`, a more specific lint" } @@ -169,6 +182,7 @@ declare_deprecated_lint! { /// ### Deprecation reason /// This lint has been replaced by `manual_filter_map`, a /// more specific lint. + #[clippy::version = "1.53.0"] pub FILTER_MAP, "this lint has been replaced by `manual_filter_map`, a more specific lint" } @@ -181,6 +195,7 @@ declare_deprecated_lint! { /// The `avoid_breaking_exported_api` config option was added, which /// enables the `enum_variant_names` lint for public items. /// ``` + #[clippy::version = "1.54.0"] pub PUB_ENUM_VARIANT_NAMES, "set the `avoid-breaking-exported-api` config option to `false` to enable the `enum_variant_names` lint for public items" } @@ -192,6 +207,7 @@ declare_deprecated_lint! { /// ### Deprecation reason /// The `avoid_breaking_exported_api` config option was added, which /// enables the `wrong_self_conversion` lint for public items. + #[clippy::version = "1.54.0"] pub WRONG_PUB_SELF_CONVENTION, "set the `avoid-breaking-exported-api` config option to `false` to enable the `wrong_self_convention` lint for public items" } diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index ce59311c4aa..bbaae94d3dd 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::peel_mid_ty_refs; -use clippy_utils::{get_parent_node, in_macro, is_lint_allowed}; +use clippy_utils::{get_parent_node, is_lint_allowed}; use rustc_ast::util::parser::PREC_PREFIX; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, MatchSource, Mutability, Node, UnOp}; @@ -34,6 +34,7 @@ declare_clippy_lint! { /// ```rust,ignore /// let _ = d.unwrap().deref(); /// ``` + #[clippy::version = "1.44.0"] pub EXPLICIT_DEREF_METHODS, pedantic, "Explicit use of deref or deref_mut method while not in a method chain." @@ -84,7 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { } // Stop processing sub expressions when a macro call is seen - if in_macro(expr.span) { + if expr.span.from_expansion() { if let Some((state, data)) = self.state.take() { report(cx, expr, state, data); } diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index fdef0abe970..d0fab2b48fb 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::{in_macro, is_automatically_derived, is_default_equivalent, remove_blocks}; +use clippy_utils::{is_automatically_derived, is_default_equivalent, remove_blocks}; use rustc_hir::{ def::{DefKind, Res}, Body, Expr, ExprKind, GenericArg, Impl, ImplItemKind, Item, ItemKind, Node, PathSegment, QPath, TyKind, @@ -46,6 +46,7 @@ declare_clippy_lint! { /// has exactly equal bounds, and therefore this lint is disabled for types with /// generic parameters. /// + #[clippy::version = "1.57.0"] pub DERIVABLE_IMPLS, complexity, "manual implementation of the `Default` trait which is equal to a derive" @@ -72,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls { }) = item.kind; if let attrs = cx.tcx.hir().attrs(item.hir_id()); if !is_automatically_derived(attrs); - if !in_macro(item.span); + if !item.span.from_expansion(); if let Some(def_id) = trait_ref.trait_def_id(); if cx.tcx.is_diagnostic_item(sym::Default, def_id); if let impl_item_hir = child.id.hir_id(); diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 24ac5917dcb..4b232a26e5d 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -38,6 +38,7 @@ declare_clippy_lint! { /// ... /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub DERIVE_HASH_XOR_EQ, correctness, "deriving `Hash` but implementing `PartialEq` explicitly" @@ -88,6 +89,7 @@ declare_clippy_lint! { /// #[derive(Ord, PartialOrd, PartialEq, Eq)] /// struct Foo; /// ``` + #[clippy::version = "1.47.0"] pub DERIVE_ORD_XOR_PARTIAL_ORD, correctness, "deriving `Ord` but implementing `PartialOrd` explicitly" @@ -114,6 +116,7 @@ declare_clippy_lint! { /// // .. /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub EXPL_IMPL_CLONE_ON_COPY, pedantic, "implementing `Clone` explicitly on `Copy` types" @@ -147,6 +150,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.45.0"] pub UNSAFE_DERIVE_DESERIALIZE, pedantic, "deriving `serde::Deserialize` on a type that has methods using `unsafe`" diff --git a/clippy_lints/src/disallowed_method.rs b/clippy_lints/src/disallowed_method.rs index 22d726cdcb7..c2217353b32 100644 --- a/clippy_lints/src/disallowed_method.rs +++ b/clippy_lints/src/disallowed_method.rs @@ -47,6 +47,7 @@ declare_clippy_lint! { /// let mut xs = Vec::new(); // Vec::new is _not_ disallowed in the config. /// xs.push(123); // Vec::push is _not_ disallowed in the config. /// ``` + #[clippy::version = "1.49.0"] pub DISALLOWED_METHOD, nursery, "use of a disallowed method call" diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs index 6d38d30cd0b..3c3f3631849 100644 --- a/clippy_lints/src/disallowed_script_idents.rs +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -38,6 +38,7 @@ declare_clippy_lint! { /// let zähler = 10; // OK, it's still latin. /// let カウンタ = 10; // Will spawn the lint. /// ``` + #[clippy::version = "1.55.0"] pub DISALLOWED_SCRIPT_IDENTS, restriction, "usage of non-allowed Unicode scripts" diff --git a/clippy_lints/src/disallowed_type.rs b/clippy_lints/src/disallowed_type.rs index 48f781516f4..7e784ca2244 100644 --- a/clippy_lints/src/disallowed_type.rs +++ b/clippy_lints/src/disallowed_type.rs @@ -42,6 +42,7 @@ declare_clippy_lint! { /// // A similar type that is allowed by the config /// use std::collections::HashMap; /// ``` + #[clippy::version = "1.55.0"] pub DISALLOWED_TYPE, nursery, "use of a disallowed type" diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs index d4ba072807f..c1ce851a67d 100644 --- a/clippy_lints/src/doc.rs +++ b/clippy_lints/src/doc.rs @@ -67,6 +67,7 @@ declare_clippy_lint! { /// /// [SmallVec]: SmallVec /// fn main() {} /// ``` + #[clippy::version = "pre 1.29.0"] pub DOC_MARKDOWN, pedantic, "presence of `_`, `::` or camel-case outside backticks in documentation" @@ -101,6 +102,7 @@ declare_clippy_lint! { /// unimplemented!(); /// } /// ``` + #[clippy::version = "1.39.0"] pub MISSING_SAFETY_DOC, style, "`pub unsafe fn` without `# Safety` docs" @@ -129,6 +131,7 @@ declare_clippy_lint! { /// unimplemented!(); /// } /// ``` + #[clippy::version = "1.41.0"] pub MISSING_ERRORS_DOC, pedantic, "`pub fn` returns `Result` without `# Errors` in doc comment" @@ -159,6 +162,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.52.0"] pub MISSING_PANICS_DOC, pedantic, "`pub fn` may panic without `# Panics` in doc comment" @@ -187,6 +191,7 @@ declare_clippy_lint! { /// unimplemented!(); /// } /// `````` + #[clippy::version = "1.40.0"] pub NEEDLESS_DOCTEST_MAIN, style, "presence of `fn main() {` in code examples" @@ -639,7 +644,9 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) { | ItemKind::ExternCrate(..) | ItemKind::ForeignMod(..) => return false, // We found a main function ... - ItemKind::Fn(box Fn { sig, body: Some(block), .. }) if item.ident.name == sym::main => { + ItemKind::Fn(box Fn { + sig, body: Some(block), .. + }) if item.ident.name == sym::main => { let is_async = matches!(sig.header.asyncness, Async::Yes { .. }); let returns_nothing = match &sig.decl.output { FnRetTy::Default(..) => true, diff --git a/clippy_lints/src/double_comparison.rs b/clippy_lints/src/double_comparison.rs index 6520bb91faf..176092e5b28 100644 --- a/clippy_lints/src/double_comparison.rs +++ b/clippy_lints/src/double_comparison.rs @@ -31,6 +31,7 @@ declare_clippy_lint! { /// # let y = 2; /// if x <= y {} /// ``` + #[clippy::version = "pre 1.29.0"] pub DOUBLE_COMPARISONS, complexity, "unnecessary double comparisons that can be simplified" diff --git a/clippy_lints/src/double_parens.rs b/clippy_lints/src/double_parens.rs index d0d87b6df9a..e10f740d24a 100644 --- a/clippy_lints/src/double_parens.rs +++ b/clippy_lints/src/double_parens.rs @@ -32,6 +32,7 @@ declare_clippy_lint! { /// // Good /// foo(0); /// ``` + #[clippy::version = "pre 1.29.0"] pub DOUBLE_PARENS, complexity, "Warn on unnecessary double parentheses" diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index 0f3dc866afb..a8f8e3d8a42 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -25,6 +25,7 @@ declare_clippy_lint! { /// // still locked /// operation_that_requires_mutex_to_be_unlocked(); /// ``` + #[clippy::version = "pre 1.29.0"] pub DROP_REF, correctness, "calls to `std::mem::drop` with a reference instead of an owned value" @@ -46,6 +47,7 @@ declare_clippy_lint! { /// let x = Box::new(1); /// std::mem::forget(&x) // Should have been forget(x), x will still be dropped /// ``` + #[clippy::version = "pre 1.29.0"] pub FORGET_REF, correctness, "calls to `std::mem::forget` with a reference instead of an owned value" @@ -67,6 +69,7 @@ declare_clippy_lint! { /// std::mem::drop(x) // A copy of x is passed to the function, leaving the /// // original unaffected /// ``` + #[clippy::version = "pre 1.29.0"] pub DROP_COPY, correctness, "calls to `std::mem::drop` with a value that implements Copy" @@ -94,6 +97,7 @@ declare_clippy_lint! { /// std::mem::forget(x) // A copy of x is passed to the function, leaving the /// // original unaffected /// ``` + #[clippy::version = "pre 1.29.0"] pub FORGET_COPY, correctness, "calls to `std::mem::forget` with a value that implements Copy" diff --git a/clippy_lints/src/duration_subsec.rs b/clippy_lints/src/duration_subsec.rs index 3774de62521..3070d105014 100644 --- a/clippy_lints/src/duration_subsec.rs +++ b/clippy_lints/src/duration_subsec.rs @@ -33,6 +33,7 @@ declare_clippy_lint! { /// let _micros = dur.subsec_micros(); /// let _millis = dur.subsec_millis(); /// ``` + #[clippy::version = "pre 1.29.0"] pub DURATION_SUBSEC, complexity, "checks for calculation of subsecond microseconds or milliseconds" diff --git a/clippy_lints/src/else_if_without_else.rs b/clippy_lints/src/else_if_without_else.rs index b64246515f3..92c56c762aa 100644 --- a/clippy_lints/src/else_if_without_else.rs +++ b/clippy_lints/src/else_if_without_else.rs @@ -40,6 +40,7 @@ declare_clippy_lint! { /// // We don't care about zero. /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub ELSE_IF_WITHOUT_ELSE, restriction, "`if` expression with an `else if`, but without a final `else` branch" diff --git a/clippy_lints/src/empty_enum.rs b/clippy_lints/src/empty_enum.rs index 3453c2da278..af9e65e6361 100644 --- a/clippy_lints/src/empty_enum.rs +++ b/clippy_lints/src/empty_enum.rs @@ -34,6 +34,7 @@ declare_clippy_lint! { /// /// struct Test(!); /// ``` + #[clippy::version = "pre 1.29.0"] pub EMPTY_ENUM, pedantic, "enum with no variants" diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index 57fd24bd4f0..3d92eb16870 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -54,6 +54,7 @@ declare_clippy_lint! { /// # let v = 1; /// map.entry(k).or_insert(v); /// ``` + #[clippy::version = "pre 1.29.0"] pub MAP_ENTRY, perf, "use of `contains_key` followed by `insert` on a `HashMap` or `BTreeMap`" diff --git a/clippy_lints/src/enum_clike.rs b/clippy_lints/src/enum_clike.rs index a2c3c7a7b49..3b6661c817b 100644 --- a/clippy_lints/src/enum_clike.rs +++ b/clippy_lints/src/enum_clike.rs @@ -28,6 +28,7 @@ declare_clippy_lint! { /// Y = 0, /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub ENUM_CLIKE_UNPORTABLE_VARIANT, correctness, "C-like enums that are `repr(isize/usize)` and have values that don't fit into an `i32`" diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs index 404b67c8f29..fc3a35efaf8 100644 --- a/clippy_lints/src/enum_variants.rs +++ b/clippy_lints/src/enum_variants.rs @@ -34,6 +34,7 @@ declare_clippy_lint! { /// Battenberg, /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub ENUM_VARIANT_NAMES, style, "enums where all variants share a prefix/postfix" @@ -59,6 +60,7 @@ declare_clippy_lint! { /// struct BlackForest; /// } /// ``` + #[clippy::version = "1.33.0"] pub MODULE_NAME_REPETITIONS, pedantic, "type names prefixed/postfixed with their containing module's name" @@ -89,6 +91,7 @@ declare_clippy_lint! { /// ... /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub MODULE_INCEPTION, style, "modules that have the same name as their parent module" diff --git a/clippy_lints/src/eq_op.rs b/clippy_lints/src/eq_op.rs index 655560afd42..10123460527 100644 --- a/clippy_lints/src/eq_op.rs +++ b/clippy_lints/src/eq_op.rs @@ -1,9 +1,7 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint, span_lint_and_then}; 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, higher, in_macro, is_expn_of, is_in_test_function, -}; +use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, higher, is_expn_of, is_in_test_function}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, StmtKind}; @@ -36,6 +34,7 @@ declare_clippy_lint! { /// # 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`)" @@ -61,6 +60,7 @@ declare_clippy_lint! { /// // Good /// x == *y /// ``` + #[clippy::version = "pre 1.29.0"] pub OP_REF, style, "taking a reference to satisfy the type constraints on `==`" @@ -102,7 +102,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp { } let macro_with_not_op = |expr_kind: &ExprKind<'_>| { if let ExprKind::Unary(_, expr) = *expr_kind { - in_macro(expr.span) + expr.span.from_expansion() } else { false } diff --git a/clippy_lints/src/equatable_if_let.rs b/clippy_lints/src/equatable_if_let.rs index e8b1d6f6eda..8905cc0de45 100644 --- a/clippy_lints/src/equatable_if_let.rs +++ b/clippy_lints/src/equatable_if_let.rs @@ -32,6 +32,7 @@ declare_clippy_lint! { /// do_thing(); /// } /// ``` + #[clippy::version = "1.57.0"] pub EQUATABLE_IF_LET, nursery, "using pattern matching instead of equality" diff --git a/clippy_lints/src/erasing_op.rs b/clippy_lints/src/erasing_op.rs index d0944c37cf5..d49cec26be5 100644 --- a/clippy_lints/src/erasing_op.rs +++ b/clippy_lints/src/erasing_op.rs @@ -21,6 +21,7 @@ declare_clippy_lint! { /// 0 * x; /// x & 0; /// ``` + #[clippy::version = "pre 1.29.0"] pub ERASING_OP, correctness, "using erasing operations, e.g., `x * 0` or `y & 0`" diff --git a/clippy_lints/src/escape.rs b/clippy_lints/src/escape.rs index 75b1c882c23..bc5d2f6278d 100644 --- a/clippy_lints/src/escape.rs +++ b/clippy_lints/src/escape.rs @@ -41,6 +41,7 @@ declare_clippy_lint! { /// foo(x); /// println!("{}", x); /// ``` + #[clippy::version = "pre 1.29.0"] pub BOXED_LOCAL, perf, "using `Box` where unnecessary" diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 9247343b52a..5a4b4247104 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::higher::VecArgs; use clippy_utils::source::snippet_opt; -use clippy_utils::usage::UsedAfterExprVisitor; -use clippy_utils::{get_enclosing_loop_or_closure, higher, path_to_local_id}; +use clippy_utils::usage::local_used_after_expr; +use clippy_utils::{get_enclosing_loop_or_closure, higher, path_to_local, path_to_local_id}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; @@ -39,6 +39,7 @@ declare_clippy_lint! { /// ``` /// where `foo(_)` is a plain function that takes the exact argument type of /// `x`. + #[clippy::version = "pre 1.29.0"] pub REDUNDANT_CLOSURE, style, "redundant closures, i.e., `|a| foo(a)` (which can be written as just `foo`)" @@ -60,6 +61,7 @@ declare_clippy_lint! { /// ```rust,ignore /// Some('a').map(char::to_uppercase); /// ``` + #[clippy::version = "1.35.0"] pub REDUNDANT_CLOSURE_FOR_METHOD_CALLS, pedantic, "redundant closures for method calls" @@ -118,7 +120,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction { if let ty::Closure(_, substs) = callee_ty.peel_refs().kind(); if substs.as_closure().kind() == ClosureKind::FnMut; if get_enclosing_loop_or_closure(cx.tcx, expr).is_some() - || UsedAfterExprVisitor::is_found(cx, callee); + || path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, callee)); then { // Mutable closure is used after current expr; we cannot consume it. diff --git a/clippy_lints/src/eval_order_dependence.rs b/clippy_lints/src/eval_order_dependence.rs index 1b56dd4b081..cdac9f3e6e1 100644 --- a/clippy_lints/src/eval_order_dependence.rs +++ b/clippy_lints/src/eval_order_dependence.rs @@ -40,6 +40,7 @@ declare_clippy_lint! { /// }; /// let a = tmp + x; /// ``` + #[clippy::version = "pre 1.29.0"] pub EVAL_ORDER_DEPENDENCE, suspicious, "whether a variable read occurs before a write depends on sub-expression evaluation order" @@ -67,6 +68,7 @@ declare_clippy_lint! { /// let x = (a, b, c, panic!()); /// // can simply be replaced by `panic!()` /// ``` + #[clippy::version = "pre 1.29.0"] pub DIVERGING_SUB_EXPRESSION, complexity, "whether an expression contains a diverging sub expression" diff --git a/clippy_lints/src/excessive_bools.rs b/clippy_lints/src/excessive_bools.rs index 09b6e200838..245a4fc12fd 100644 --- a/clippy_lints/src/excessive_bools.rs +++ b/clippy_lints/src/excessive_bools.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::in_macro; use rustc_ast::ast::{AssocItemKind, Extern, Fn, FnSig, Impl, Item, ItemKind, Trait, Ty, TyKind}; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -38,6 +37,7 @@ declare_clippy_lint! { /// Finished, /// } /// ``` + #[clippy::version = "1.43.0"] pub STRUCT_EXCESSIVE_BOOLS, pedantic, "using too many bools in a struct" @@ -76,6 +76,7 @@ declare_clippy_lint! { /// /// fn f(shape: Shape, temperature: Temperature) { ... } /// ``` + #[clippy::version = "1.43.0"] pub FN_PARAMS_EXCESSIVE_BOOLS, pedantic, "using too many bools in function parameters" @@ -135,7 +136,7 @@ fn is_bool_ty(ty: &Ty) -> bool { impl EarlyLintPass for ExcessiveBools { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { - if in_macro(item.span) { + if item.span.from_expansion() { return; } match &item.kind { diff --git a/clippy_lints/src/exhaustive_items.rs b/clippy_lints/src/exhaustive_items.rs index bb4684ce38b..b0f50b5c144 100644 --- a/clippy_lints/src/exhaustive_items.rs +++ b/clippy_lints/src/exhaustive_items.rs @@ -31,6 +31,7 @@ declare_clippy_lint! { /// Baz /// } /// ``` + #[clippy::version = "1.51.0"] pub EXHAUSTIVE_ENUMS, restriction, "detects exported enums that have not been marked #[non_exhaustive]" @@ -60,6 +61,7 @@ declare_clippy_lint! { /// baz: String, /// } /// ``` + #[clippy::version = "1.51.0"] pub EXHAUSTIVE_STRUCTS, restriction, "detects exported structs that have not been marked #[non_exhaustive]" diff --git a/clippy_lints/src/exit.rs b/clippy_lints/src/exit.rs index 9cd5b2d9f44..d64cc61916c 100644 --- a/clippy_lints/src/exit.rs +++ b/clippy_lints/src/exit.rs @@ -18,6 +18,7 @@ declare_clippy_lint! { /// ```ignore /// std::process::exit(0) /// ``` + #[clippy::version = "1.41.0"] pub EXIT, restriction, "`std::process::exit` is called, terminating the program" diff --git a/clippy_lints/src/explicit_write.rs b/clippy_lints/src/explicit_write.rs index 4f46ef906f4..6b327b9ce17 100644 --- a/clippy_lints/src/explicit_write.rs +++ b/clippy_lints/src/explicit_write.rs @@ -23,6 +23,7 @@ declare_clippy_lint! { /// // this would be clearer as `eprintln!("foo: {:?}", bar);` /// writeln!(&mut std::io::stderr(), "foo: {:?}", bar).unwrap(); /// ``` + #[clippy::version = "pre 1.29.0"] pub EXPLICIT_WRITE, complexity, "using the `write!()` family of functions instead of the `print!()` family of functions, when using the latter would work" diff --git a/clippy_lints/src/fallible_impl_from.rs b/clippy_lints/src/fallible_impl_from.rs index 70337f5bbeb..05d300058cf 100644 --- a/clippy_lints/src/fallible_impl_from.rs +++ b/clippy_lints/src/fallible_impl_from.rs @@ -44,6 +44,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub FALLIBLE_IMPL_FROM, nursery, "Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`" diff --git a/clippy_lints/src/feature_name.rs b/clippy_lints/src/feature_name.rs index f534327f7a0..dc6bef52ddd 100644 --- a/clippy_lints/src/feature_name.rs +++ b/clippy_lints/src/feature_name.rs @@ -31,6 +31,7 @@ declare_clippy_lint! { /// ghi = [] /// ``` /// + #[clippy::version = "1.57.0"] pub REDUNDANT_FEATURE_NAMES, cargo, "usage of a redundant feature name" @@ -60,6 +61,7 @@ declare_clippy_lint! { /// def = [] /// /// ``` + #[clippy::version = "1.57.0"] pub NEGATIVE_FEATURE_NAMES, cargo, "usage of a negative feature name" diff --git a/clippy_lints/src/float_equality_without_abs.rs b/clippy_lints/src/float_equality_without_abs.rs index c33d80b8e8e..ca8886228de 100644 --- a/clippy_lints/src/float_equality_without_abs.rs +++ b/clippy_lints/src/float_equality_without_abs.rs @@ -37,6 +37,7 @@ declare_clippy_lint! { /// (a - b).abs() < f32::EPSILON /// } /// ``` + #[clippy::version = "1.48.0"] pub FLOAT_EQUALITY_WITHOUT_ABS, suspicious, "float equality check without `.abs()`" diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index 1e8a5bd7d34..d30dede833c 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -27,6 +27,7 @@ declare_clippy_lint! { /// let v: f64 = 0.123_456_789_9; /// println!("{}", v); // 0.123_456_789_9 /// ``` + #[clippy::version = "pre 1.29.0"] pub EXCESSIVE_PRECISION, style, "excessive precision for float literal" @@ -50,6 +51,7 @@ declare_clippy_lint! { /// let _: f32 = 16_777_216.0; /// let _: f64 = 16_777_217.0; /// ``` + #[clippy::version = "1.43.0"] pub LOSSY_FLOAT_LITERAL, restriction, "lossy whole number float literals" diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index eda611117ba..914723a4802 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -43,6 +43,7 @@ declare_clippy_lint! { /// let _ = a.ln_1p(); /// let _ = a.exp_m1(); /// ``` + #[clippy::version = "1.43.0"] pub IMPRECISE_FLOPS, nursery, "usage of imprecise floating point operations" @@ -99,6 +100,7 @@ declare_clippy_lint! { /// let _ = a.abs(); /// let _ = -a.abs(); /// ``` + #[clippy::version = "1.43.0"] pub SUBOPTIMAL_FLOPS, nursery, "usage of sub-optimal floating point operations" diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index 7169ac9ad6c..3f043e5f2f1 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -33,6 +33,7 @@ declare_clippy_lint! { /// // Good /// foo.to_owned(); /// ``` + #[clippy::version = "pre 1.29.0"] pub USELESS_FORMAT, complexity, "useless use of `format!`" diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 8b27442aa94..f0e1a67dcdd 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -10,7 +10,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment}; use rustc_middle::ty::Ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{sym, BytePos, ExpnData, ExpnKind, Span, Symbol}; +use rustc_span::{sym, ExpnData, ExpnKind, Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -31,6 +31,7 @@ declare_clippy_lint! { /// # use std::panic::Location; /// println!("error: something failed at {}", Location::caller()); /// ``` + #[clippy::version = "1.58.0"] pub FORMAT_IN_FORMAT_ARGS, perf, "`format!` used in a macro that does formatting" @@ -56,6 +57,7 @@ declare_clippy_lint! { /// # use std::panic::Location; /// println!("error: something failed at {}", Location::caller()); /// ``` + #[clippy::version = "1.58.0"] pub TO_STRING_IN_FORMAT_ARGS, perf, "`to_string` applied to a type that implements `Display` in format args" @@ -128,7 +130,7 @@ fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symb span_lint_and_then( cx, FORMAT_IN_FORMAT_ARGS, - trim_semicolon(cx, call_site), + call_site, &format!("`format!` in `{}!` args", name), |diag| { diag.help(&format!( @@ -192,13 +194,6 @@ fn is_aliased(args: &[FormatArgsArg<'_>], i: usize) -> bool { .any(|(j, arg)| i != j && std::ptr::eq(value, arg.value)) } -fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span { - snippet_opt(cx, span).map_or(span, |snippet| { - let snippet = snippet.trim_end_matches(';'); - span.with_hi(span.lo() + BytePos(u32::try_from(snippet.len()).unwrap())) - }) -} - fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>) where I: Iterator>, diff --git a/clippy_lints/src/formatting.rs b/clippy_lints/src/formatting.rs index b4f186525c5..3e85c8a9c80 100644 --- a/clippy_lints/src/formatting.rs +++ b/clippy_lints/src/formatting.rs @@ -21,6 +21,7 @@ declare_clippy_lint! { /// ```rust,ignore /// a =- 42; // confusing, should it be `a -= 42` or `a = -42`? /// ``` + #[clippy::version = "pre 1.29.0"] pub SUSPICIOUS_ASSIGNMENT_FORMATTING, suspicious, "suspicious formatting of `*=`, `-=` or `!=`" @@ -43,6 +44,7 @@ declare_clippy_lint! { /// if foo &&! bar { // this should be `foo && !bar` but looks like a different operator /// } /// ``` + #[clippy::version = "1.40.0"] pub SUSPICIOUS_UNARY_OP_FORMATTING, suspicious, "suspicious formatting of unary `-` or `!` on the RHS of a BinOp" @@ -79,6 +81,7 @@ declare_clippy_lint! { /// if bar { // this is the `else` block of the previous `if`, but should it be? /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub SUSPICIOUS_ELSE_FORMATTING, suspicious, "suspicious formatting of `else`" @@ -99,6 +102,7 @@ declare_clippy_lint! { /// -4, -5, -6 /// ]; /// ``` + #[clippy::version = "pre 1.29.0"] pub POSSIBLE_MISSING_COMMA, correctness, "possible missing comma in array" diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs index 347c6eb12cb..866ff216f84 100644 --- a/clippy_lints/src/from_over_into.rs +++ b/clippy_lints/src/from_over_into.rs @@ -34,6 +34,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.51.0"] pub FROM_OVER_INTO, style, "Warns on implementations of `Into<..>` to use `From<..>`" diff --git a/clippy_lints/src/from_str_radix_10.rs b/clippy_lints/src/from_str_radix_10.rs index 98ce3db025c..73e800073b0 100644 --- a/clippy_lints/src/from_str_radix_10.rs +++ b/clippy_lints/src/from_str_radix_10.rs @@ -35,6 +35,7 @@ declare_clippy_lint! { /// let input: &str = get_input(); /// let num: u16 = input.parse()?; /// ``` + #[clippy::version = "1.52.0"] pub FROM_STR_RADIX_10, style, "from_str_radix with radix 10" diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index d7c5ec9ba35..ad031cbc09d 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -26,6 +26,7 @@ declare_clippy_lint! { /// // .. /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub TOO_MANY_ARGUMENTS, complexity, "functions with too many arguments" @@ -49,6 +50,7 @@ declare_clippy_lint! { /// println!(""); /// } /// ``` + #[clippy::version = "1.34.0"] pub TOO_MANY_LINES, pedantic, "functions with too many lines" @@ -84,6 +86,7 @@ declare_clippy_lint! { /// println!("{}", unsafe { *x }); /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub NOT_UNSAFE_PTR_ARG_DEREF, correctness, "public functions dereferencing raw pointer arguments but not marked `unsafe`" @@ -103,6 +106,7 @@ declare_clippy_lint! { /// #[must_use] /// fn useless() { } /// ``` + #[clippy::version = "1.40.0"] pub MUST_USE_UNIT, style, "`#[must_use]` attribute on a unit-returning function / method" @@ -126,6 +130,7 @@ declare_clippy_lint! { /// unimplemented!(); /// } /// ``` + #[clippy::version = "1.40.0"] pub DOUBLE_MUST_USE, style, "`#[must_use]` attribute on a `#[must_use]`-returning function / method" @@ -155,6 +160,7 @@ declare_clippy_lint! { /// // this could be annotated with `#[must_use]`. /// fn id(t: T) -> T { t } /// ``` + #[clippy::version = "1.40.0"] pub MUST_USE_CANDIDATE, pedantic, "function or method that could take a `#[must_use]` attribute" @@ -204,6 +210,7 @@ declare_clippy_lint! { /// /// Note that there are crates that simplify creating the error type, e.g. /// [`thiserror`](https://docs.rs/thiserror). + #[clippy::version = "1.49.0"] pub RESULT_UNIT_ERR, style, "public function returning `Result` with an `Err` type of `()`" diff --git a/clippy_lints/src/functions/too_many_lines.rs b/clippy_lints/src/functions/too_many_lines.rs index 008ef661b55..65efbbab41a 100644 --- a/clippy_lints/src/functions/too_many_lines.rs +++ b/clippy_lints/src/functions/too_many_lines.rs @@ -56,8 +56,8 @@ pub(super) fn check_fn( continue; } } else { - let multi_idx = line.find("/*").unwrap_or_else(|| line.len()); - let single_idx = line.find("//").unwrap_or_else(|| line.len()); + let multi_idx = line.find("/*").unwrap_or(line.len()); + let single_idx = line.find("//").unwrap_or(line.len()); code_in_line |= multi_idx > 0 && single_idx > 0; // Implies multi_idx is below line.len() if multi_idx < single_idx { diff --git a/clippy_lints/src/future_not_send.rs b/clippy_lints/src/future_not_send.rs index 6b2ac985555..fefdcfed42f 100644 --- a/clippy_lints/src/future_not_send.rs +++ b/clippy_lints/src/future_not_send.rs @@ -41,6 +41,7 @@ declare_clippy_lint! { /// ```rust /// async fn is_send(bytes: std::sync::Arc<[u8]>) {} /// ``` + #[clippy::version = "1.44.0"] pub FUTURE_NOT_SEND, nursery, "public Futures must be Send" diff --git a/clippy_lints/src/get_last_with_len.rs b/clippy_lints/src/get_last_with_len.rs index f3929b0f1e6..edca701869e 100644 --- a/clippy_lints/src/get_last_with_len.rs +++ b/clippy_lints/src/get_last_with_len.rs @@ -39,6 +39,7 @@ declare_clippy_lint! { /// let x = vec![2, 3, 5]; /// let last_element = x.last(); /// ``` + #[clippy::version = "1.37.0"] pub GET_LAST_WITH_LEN, complexity, "Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler" diff --git a/clippy_lints/src/identity_op.rs b/clippy_lints/src/identity_op.rs index 414f465c494..b4e7bbc7671 100644 --- a/clippy_lints/src/identity_op.rs +++ b/clippy_lints/src/identity_op.rs @@ -22,6 +22,7 @@ declare_clippy_lint! { /// # 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`" diff --git a/clippy_lints/src/if_let_mutex.rs b/clippy_lints/src/if_let_mutex.rs index a4118bf54b6..e20741d2407 100644 --- a/clippy_lints/src/if_let_mutex.rs +++ b/clippy_lints/src/if_let_mutex.rs @@ -36,6 +36,7 @@ declare_clippy_lint! { /// use_locked(locked); /// } /// ``` + #[clippy::version = "1.45.0"] pub IF_LET_MUTEX, correctness, "locking a `Mutex` in an `if let` block can cause deadlocks" diff --git a/clippy_lints/src/if_not_else.rs b/clippy_lints/src/if_not_else.rs index ac938156237..3d59b783337 100644 --- a/clippy_lints/src/if_not_else.rs +++ b/clippy_lints/src/if_not_else.rs @@ -39,6 +39,7 @@ declare_clippy_lint! { /// a() /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub IF_NOT_ELSE, pedantic, "`if` branches that could be swapped so no negation operation is necessary on the condition" diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index a2dac57454f..d95f5d41071 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -36,6 +36,7 @@ declare_clippy_lint! { /// 42 /// }); /// ``` + #[clippy::version = "1.53.0"] pub IF_THEN_SOME_ELSE_NONE, restriction, "Finds if-else that could be written using `bool::then`" diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index 81eb51e6f7c..6358228dd47 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -54,6 +54,7 @@ declare_clippy_lint! { /// /// pub fn foo(map: &mut HashMap) { } /// ``` + #[clippy::version = "pre 1.29.0"] pub IMPLICIT_HASHER, pedantic, "missing generalization over different hashers" diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index fa7b5302cb1..07caeada80d 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -2,10 +2,10 @@ use clippy_utils::{ diagnostics::span_lint_and_sugg, get_async_fn_body, is_async_fn, source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}, - visitors::visit_break_exprs, + visitors::expr_visitor_no_bodies, }; use rustc_errors::Applicability; -use rustc_hir::intravisit::FnKind; +use rustc_hir::intravisit::{FnKind, Visitor}; use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; @@ -35,6 +35,7 @@ declare_clippy_lint! { /// return x; /// } /// ``` + #[clippy::version = "1.33.0"] pub IMPLICIT_RETURN, restriction, "use a return statement like `return expr` instead of an expression" @@ -144,20 +145,24 @@ fn lint_implicit_returns( ExprKind::Loop(block, ..) => { let mut add_return = false; - visit_break_exprs(block, |break_expr, dest, sub_expr| { - if dest.target_id.ok() == Some(expr.hir_id) { - if call_site_span.is_none() && break_expr.span.ctxt() == ctxt { - // 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, break_expr.span, sub_expr.span); + expr_visitor_no_bodies(|e| { + if let ExprKind::Break(dest, sub_expr) = e.kind { + if dest.target_id.ok() == Some(expr.hir_id) { + if call_site_span.is_none() && e.span.ctxt() == ctxt { + // 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); + } + } else { + // the break expression is from a macro call, add a return to the loop + add_return = true; } - } else { - // the break expression is from a macro call, add a return to the loop - add_return = true; } } - }); + true + }) + .visit_block(block); if add_return { #[allow(clippy::option_if_let_else)] if let Some(span) = call_site_span { diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index a4f60ded3a6..4088c54623d 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher; -use clippy_utils::{in_macro, SpanlessEq}; +use clippy_utils::SpanlessEq; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -30,6 +30,7 @@ declare_clippy_lint! { /// // Good /// i = i.saturating_sub(1); /// ``` + #[clippy::version = "1.44.0"] pub IMPLICIT_SATURATING_SUB, pedantic, "Perform saturating subtraction instead of implicitly checking lower bound of data type" @@ -39,7 +40,7 @@ declare_lint_pass!(ImplicitSaturatingSub => [IMPLICIT_SATURATING_SUB]); impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if in_macro(expr.span) { + if expr.span.from_expansion() { return; } if_chain! { diff --git a/clippy_lints/src/inconsistent_struct_constructor.rs b/clippy_lints/src/inconsistent_struct_constructor.rs index 1f8240a1f63..1debdef9d86 100644 --- a/clippy_lints/src/inconsistent_struct_constructor.rs +++ b/clippy_lints/src/inconsistent_struct_constructor.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::in_macro; use clippy_utils::source::snippet; use if_chain::if_chain; use rustc_data_structures::fx::FxHashMap; @@ -56,6 +55,7 @@ declare_clippy_lint! { /// # let y = 2; /// Foo { x, y }; /// ``` + #[clippy::version = "1.52.0"] pub INCONSISTENT_STRUCT_CONSTRUCTOR, pedantic, "the order of the field init shorthand is inconsistent with the order in the struct definition" @@ -66,7 +66,7 @@ declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRU impl LateLintPass<'_> for InconsistentStructConstructor { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { if_chain! { - if !in_macro(expr.span); + if !expr.span.from_expansion(); if let ExprKind::Struct(qpath, fields, base) = expr.kind; let ty = cx.typeck_results().expr_ty(expr); if let Some(adt_def) = ty.ty_adt_def(); diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs new file mode 100644 index 00000000000..69f1c90beec --- /dev/null +++ b/clippy_lints/src/index_refutable_slice.rs @@ -0,0 +1,276 @@ +use clippy_utils::consts::{constant, Constant}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::higher::IfLet; +use clippy_utils::ty::is_copy; +use clippy_utils::{is_expn_of, is_lint_allowed, meets_msrv, msrvs, path_to_local}; +use if_chain::if_chain; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::ty; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{symbol::Ident, Span}; +use std::convert::TryInto; + +declare_clippy_lint! { + /// ### What it does + /// The lint checks for slice bindings in patterns that are only used to + /// access individual slice values. + /// + /// ### Why is this bad? + /// Accessing slice values using indices can lead to panics. Using refutable + /// patterns can avoid these. Binding to individual values also improves the + /// readability as they can be named. + /// + /// ### Limitations + /// This lint currently only checks for immutable access inside `if let` + /// patterns. + /// + /// ### Example + /// ```rust + /// let slice: Option<&[u32]> = Some(&[1, 2, 3]); + /// + /// if let Some(slice) = slice { + /// println!("{}", slice[0]); + /// } + /// ``` + /// Use instead: + /// ```rust + /// let slice: Option<&[u32]> = Some(&[1, 2, 3]); + /// + /// if let Some(&[first, ..]) = slice { + /// println!("{}", first); + /// } + /// ``` + #[clippy::version = "1.58.0"] + pub INDEX_REFUTABLE_SLICE, + nursery, + "avoid indexing on slices which could be destructed" +} + +#[derive(Copy, Clone)] +pub struct IndexRefutableSlice { + max_suggested_slice: u64, + msrv: Option, +} + +impl IndexRefutableSlice { + pub fn new(max_suggested_slice_pattern_length: u64, msrv: Option) -> Self { + Self { + max_suggested_slice: max_suggested_slice_pattern_length, + msrv, + } + } +} + +impl_lint_pass!(IndexRefutableSlice => [INDEX_REFUTABLE_SLICE]); + +impl LateLintPass<'_> for IndexRefutableSlice { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if_chain! { + if !expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some(); + if let Some(IfLet {let_pat, if_then, ..}) = IfLet::hir(cx, expr); + if !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id); + if meets_msrv(self.msrv.as_ref(), &msrvs::SLICE_PATTERNS); + + let found_slices = find_slice_values(cx, let_pat); + if !found_slices.is_empty(); + let filtered_slices = filter_lintable_slices(cx, found_slices, self.max_suggested_slice, if_then); + if !filtered_slices.is_empty(); + then { + for slice in filtered_slices.values() { + lint_slice(cx, slice); + } + } + } + } + + extract_msrv_attr!(LateContext); +} + +fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxHashMap { + let mut removed_pat: FxHashSet = FxHashSet::default(); + let mut slices: FxHashMap = FxHashMap::default(); + pat.walk_always(|pat| { + if let hir::PatKind::Binding(binding, value_hir_id, ident, sub_pat) = pat.kind { + // We'll just ignore mut and ref mut for simplicity sake right now + if let hir::BindingAnnotation::Mutable | hir::BindingAnnotation::RefMut = binding { + return; + } + + // This block catches bindings with sub patterns. It would be hard to build a correct suggestion + // for them and it's likely that the user knows what they are doing in such a case. + if removed_pat.contains(&value_hir_id) { + return; + } + if sub_pat.is_some() { + removed_pat.insert(value_hir_id); + slices.remove(&value_hir_id); + return; + } + + let bound_ty = cx.typeck_results().node_type(pat.hir_id); + if let ty::Slice(inner_ty) | ty::Array(inner_ty, _) = bound_ty.peel_refs().kind() { + // The values need to use the `ref` keyword if they can't be copied. + // This will need to be adjusted if the lint want to support multable access in the future + let src_is_ref = bound_ty.is_ref() && binding != hir::BindingAnnotation::Ref; + let needs_ref = !(src_is_ref || is_copy(cx, inner_ty)); + + let slice_info = slices + .entry(value_hir_id) + .or_insert_with(|| SliceLintInformation::new(ident, needs_ref)); + slice_info.pattern_spans.push(pat.span); + } + } + }); + + slices +} + +fn lint_slice(cx: &LateContext<'_>, slice: &SliceLintInformation) { + let used_indices = slice + .index_use + .iter() + .map(|(index, _)| *index) + .collect::>(); + + let value_name = |index| format!("{}_{}", slice.ident.name, index); + + if let Some(max_index) = used_indices.iter().max() { + let opt_ref = if slice.needs_ref { "ref " } else { "" }; + let pat_sugg_idents = (0..=*max_index) + .map(|index| { + if used_indices.contains(&index) { + format!("{}{}", opt_ref, value_name(index)) + } else { + "_".to_string() + } + }) + .collect::>(); + let pat_sugg = format!("[{}, ..]", pat_sugg_idents.join(", ")); + + span_lint_and_then( + cx, + INDEX_REFUTABLE_SLICE, + slice.ident.span, + "this binding can be a slice pattern to avoid indexing", + |diag| { + diag.multipart_suggestion( + "try using a slice pattern here", + slice + .pattern_spans + .iter() + .map(|span| (*span, pat_sugg.clone())) + .collect(), + Applicability::MaybeIncorrect, + ); + + diag.multipart_suggestion( + "and replace the index expressions here", + slice + .index_use + .iter() + .map(|(index, span)| (*span, value_name(*index))) + .collect(), + Applicability::MaybeIncorrect, + ); + + // The lint message doesn't contain a warning about the removed index expression, + // since `filter_lintable_slices` will only return slices where all access indices + // are known at compile time. Therefore, they can be removed without side effects. + }, + ); + } +} + +#[derive(Debug)] +struct SliceLintInformation { + ident: Ident, + needs_ref: bool, + pattern_spans: Vec, + index_use: Vec<(u64, Span)>, +} + +impl SliceLintInformation { + fn new(ident: Ident, needs_ref: bool) -> Self { + Self { + ident, + needs_ref, + pattern_spans: Vec::new(), + index_use: Vec::new(), + } + } +} + +fn filter_lintable_slices<'a, 'tcx>( + cx: &'a LateContext<'tcx>, + slice_lint_info: FxHashMap, + max_suggested_slice: u64, + scope: &'tcx hir::Expr<'tcx>, +) -> FxHashMap { + let mut visitor = SliceIndexLintingVisitor { + cx, + slice_lint_info, + max_suggested_slice, + }; + + intravisit::walk_expr(&mut visitor, scope); + + visitor.slice_lint_info +} + +struct SliceIndexLintingVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + slice_lint_info: FxHashMap, + max_suggested_slice: u64, +} + +impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + if let Some(local_id) = path_to_local(expr) { + let Self { + cx, + ref mut slice_lint_info, + max_suggested_slice, + } = *self; + + if_chain! { + // Check if this is even a local we're interested in + if let Some(use_info) = slice_lint_info.get_mut(&local_id); + + let map = cx.tcx.hir(); + + // Checking for slice indexing + let parent_id = map.get_parent_node(expr.hir_id); + if let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id); + if let hir::ExprKind::Index(_, index_expr) = parent_expr.kind; + if let Some((Constant::Int(index_value), _)) = constant(cx, cx.typeck_results(), index_expr); + if let Ok(index_value) = index_value.try_into(); + if index_value < max_suggested_slice; + + // Make sure that this slice index is read only + let maybe_addrof_id = map.get_parent_node(parent_id); + if let Some(hir::Node::Expr(maybe_addrof_expr)) = map.find(maybe_addrof_id); + if let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind; + then { + use_info.index_use.push((index_value, map.span(parent_expr.hir_id))); + return; + } + } + + // The slice was used for something other than indexing + self.slice_lint_info.remove(&local_id); + } + intravisit::walk_expr(self, expr); + } +} diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index f52f090d387..9ead4bb27a5 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -33,6 +33,7 @@ declare_clippy_lint! { /// x[0]; /// x[3]; /// ``` + #[clippy::version = "pre 1.29.0"] pub OUT_OF_BOUNDS_INDEXING, correctness, "out of bounds constant indexing" @@ -85,6 +86,7 @@ declare_clippy_lint! { /// y.get(10..); /// y.get(..100); /// ``` + #[clippy::version = "pre 1.29.0"] pub INDEXING_SLICING, restriction, "indexing/slicing usage" diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index 68c1fa35fcc..c7db47a552b 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -20,6 +20,7 @@ declare_clippy_lint! { /// /// iter::repeat(1_u8).collect::>(); /// ``` + #[clippy::version = "pre 1.29.0"] pub INFINITE_ITER, correctness, "infinite iteration" @@ -42,6 +43,7 @@ declare_clippy_lint! { /// let infinite_iter = 0..; /// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5)); /// ``` + #[clippy::version = "pre 1.29.0"] pub MAYBE_INFINITE_ITER, pedantic, "possible infinite iteration" diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index 0d23bec27a3..254d3f8a4d0 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -1,7 +1,7 @@ //! lint on inherent implementations use clippy_utils::diagnostics::span_lint_and_note; -use clippy_utils::{in_macro, is_lint_allowed}; +use clippy_utils::is_lint_allowed; use rustc_data_structures::fx::FxHashMap; use rustc_hir::{def_id::LocalDefId, Item, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; @@ -36,6 +36,7 @@ declare_clippy_lint! { /// fn other() {} /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub MULTIPLE_INHERENT_IMPL, restriction, "Multiple inherent impl that could be grouped" @@ -123,8 +124,10 @@ fn get_impl_span(cx: &LateContext<'_>, id: LocalDefId) -> Option { .. }) = cx.tcx.hir().get(id) { - (!in_macro(span) && impl_item.generics.params.is_empty() && !is_lint_allowed(cx, MULTIPLE_INHERENT_IMPL, id)) - .then(|| span) + (!span.from_expansion() + && impl_item.generics.params.is_empty() + && !is_lint_allowed(cx, MULTIPLE_INHERENT_IMPL, id)) + .then(|| span) } else { None } diff --git a/clippy_lints/src/inherent_to_string.rs b/clippy_lints/src/inherent_to_string.rs index 61dd0eb4af7..60d234cd6f0 100644 --- a/clippy_lints/src/inherent_to_string.rs +++ b/clippy_lints/src/inherent_to_string.rs @@ -41,6 +41,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.38.0"] pub INHERENT_TO_STRING, style, "type implements inherent method `to_string()`, but should instead implement the `Display` trait" @@ -88,6 +89,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.38.0"] pub INHERENT_TO_STRING_SHADOW_DISPLAY, correctness, "type implements inherent method `to_string()`, which gets shadowed by the implementation of the `Display` trait" diff --git a/clippy_lints/src/inline_fn_without_body.rs b/clippy_lints/src/inline_fn_without_body.rs index 3e3df903f17..df69d3dcc51 100644 --- a/clippy_lints/src/inline_fn_without_body.rs +++ b/clippy_lints/src/inline_fn_without_body.rs @@ -24,6 +24,7 @@ declare_clippy_lint! { /// fn name(&self) -> &'static str; /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub INLINE_FN_WITHOUT_BODY, correctness, "use of `#[inline]` on trait methods without bodies" diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs index 6850e0c3476..3716d36ad88 100644 --- a/clippy_lints/src/int_plus_one.rs +++ b/clippy_lints/src/int_plus_one.rs @@ -28,6 +28,7 @@ declare_clippy_lint! { /// # let y = 1; /// if x > y {} /// ``` + #[clippy::version = "pre 1.29.0"] pub INT_PLUS_ONE, complexity, "instead of using `x >= y + 1`, use `x > y`" diff --git a/clippy_lints/src/integer_division.rs b/clippy_lints/src/integer_division.rs index c962e814fa5..fa786205678 100644 --- a/clippy_lints/src/integer_division.rs +++ b/clippy_lints/src/integer_division.rs @@ -23,6 +23,7 @@ declare_clippy_lint! { /// let x = 3f32 / 2f32; /// println!("{}", x); /// ``` + #[clippy::version = "1.37.0"] pub INTEGER_DIVISION, restriction, "integer division may cause loss of precision" diff --git a/clippy_lints/src/invalid_upcast_comparisons.rs b/clippy_lints/src/invalid_upcast_comparisons.rs index 82438d85c7a..36e03e50a8e 100644 --- a/clippy_lints/src/invalid_upcast_comparisons.rs +++ b/clippy_lints/src/invalid_upcast_comparisons.rs @@ -30,6 +30,7 @@ declare_clippy_lint! { /// let x: u8 = 1; /// (x as u32) > 300; /// ``` + #[clippy::version = "pre 1.29.0"] pub INVALID_UPCAST_COMPARISONS, pedantic, "a comparison involving an upcast which is always true or false" diff --git a/clippy_lints/src/items_after_statements.rs b/clippy_lints/src/items_after_statements.rs index 3736d237642..b118d3c8b87 100644 --- a/clippy_lints/src/items_after_statements.rs +++ b/clippy_lints/src/items_after_statements.rs @@ -45,6 +45,7 @@ declare_clippy_lint! { /// foo(); // prints "foo" /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub ITEMS_AFTER_STATEMENTS, pedantic, "blocks where an item comes after a statement" diff --git a/clippy_lints/src/iter_not_returning_iterator.rs b/clippy_lints/src/iter_not_returning_iterator.rs index 6c779348ca2..968bbc524b2 100644 --- a/clippy_lints/src/iter_not_returning_iterator.rs +++ b/clippy_lints/src/iter_not_returning_iterator.rs @@ -32,6 +32,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.57.0"] pub ITER_NOT_RETURNING_ITERATOR, pedantic, "methods named `iter` or `iter_mut` that do not return an `Iterator`" diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index fe6814e35d0..80260e4cd83 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -27,6 +27,7 @@ declare_clippy_lint! { /// // Good /// pub static a = [0u32; 1_000_000]; /// ``` + #[clippy::version = "1.44.0"] pub LARGE_CONST_ARRAYS, perf, "large non-scalar const array may cause performance overhead" diff --git a/clippy_lints/src/large_enum_variant.rs b/clippy_lints/src/large_enum_variant.rs index 392166237be..0191713f60d 100644 --- a/clippy_lints/src/large_enum_variant.rs +++ b/clippy_lints/src/large_enum_variant.rs @@ -40,6 +40,7 @@ declare_clippy_lint! { /// B(Box<[i32; 8000]>), /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub LARGE_ENUM_VARIANT, perf, "large size difference between variants on an enum" diff --git a/clippy_lints/src/large_stack_arrays.rs b/clippy_lints/src/large_stack_arrays.rs index bbb6c1f902c..1cc2c28c04a 100644 --- a/clippy_lints/src/large_stack_arrays.rs +++ b/clippy_lints/src/large_stack_arrays.rs @@ -19,6 +19,7 @@ declare_clippy_lint! { /// ```rust,ignore /// let a = [0u32; 1_000_000]; /// ``` + #[clippy::version = "1.41.0"] pub LARGE_STACK_ARRAYS, pedantic, "allocating large arrays on stack may cause stack overflow" diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index f336fb9d42f..09cd0d22d8b 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -46,6 +46,7 @@ declare_clippy_lint! { /// .. /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub LEN_ZERO, style, "checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` could be used instead" @@ -71,6 +72,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub LEN_WITHOUT_IS_EMPTY, style, "traits or impls with a public `len` method but no corresponding `is_empty` method" @@ -108,6 +110,7 @@ declare_clippy_lint! { /// .. /// } /// ``` + #[clippy::version = "1.49.0"] pub COMPARISON_TO_EMPTY, style, "checking `x == \"\"` or `x == []` (or similar) when `.is_empty()` could be used instead" diff --git a/clippy_lints/src/let_if_seq.rs b/clippy_lints/src/let_if_seq.rs index 7f2c7b707f0..db09d00d730 100644 --- a/clippy_lints/src/let_if_seq.rs +++ b/clippy_lints/src/let_if_seq.rs @@ -48,6 +48,7 @@ declare_clippy_lint! { /// None /// }; /// ``` + #[clippy::version = "pre 1.29.0"] pub USELESS_LET_IF_SEQ, nursery, "unidiomatic `let mut` declaration followed by initialization in `if`" diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index 9efd7aba7e8..d03276f7f98 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -26,6 +26,7 @@ declare_clippy_lint! { /// // is_ok() is marked #[must_use] /// let _ = f().is_ok(); /// ``` + #[clippy::version = "1.42.0"] pub LET_UNDERSCORE_MUST_USE, restriction, "non-binding let on a `#[must_use]` expression" @@ -33,7 +34,8 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for `let _ = sync_lock` + /// Checks for `let _ = sync_lock`. + /// This supports `mutex` and `rwlock` in `std::sync` and `parking_lot`. /// /// ### Why is this bad? /// This statement immediately drops the lock instead of @@ -53,6 +55,7 @@ declare_clippy_lint! { /// ```rust,ignore /// let _lock = mutex.lock(); /// ``` + #[clippy::version = "1.43.0"] pub LET_UNDERSCORE_LOCK, correctness, "non-binding let on a synchronization lock" @@ -94,6 +97,7 @@ declare_clippy_lint! { /// // dropped at end of scope /// } /// ``` + #[clippy::version = "1.50.0"] pub LET_UNDERSCORE_DROP, pedantic, "non-binding let on a type that implements `Drop`" @@ -101,10 +105,12 @@ declare_clippy_lint! { declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_DROP]); -const SYNC_GUARD_PATHS: [&[&str]; 3] = [ +const SYNC_GUARD_PATHS: [&[&str]; 5] = [ &paths::MUTEX_GUARD, &paths::RWLOCK_READ_GUARD, &paths::RWLOCK_WRITE_GUARD, + &paths::PARKING_LOT_RAWMUTEX, + &paths::PARKING_LOT_RAWRWLOCK, ]; impl<'tcx> LateLintPass<'tcx> for LetUnderscore { diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 15edb79d36c..225ee3c4cc2 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -159,6 +159,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::MANUAL_STR_REPEAT), LintId::of(methods::MAP_COLLECT_RESULT_UNIT), LintId::of(methods::MAP_IDENTITY), + LintId::of(methods::NEEDLESS_SPLITN), LintId::of(methods::NEW_RET_NO_SELF), LintId::of(methods::OK_EXPECT), LintId::of(methods::OPTION_AS_REF_DEREF), diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index c51341bdf0c..dcc95cf6c07 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -42,6 +42,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(methods::MANUAL_FIND_MAP), LintId::of(methods::MANUAL_SPLIT_ONCE), LintId::of(methods::MAP_IDENTITY), + LintId::of(methods::NEEDLESS_SPLITN), LintId::of(methods::OPTION_AS_REF_DEREF), LintId::of(methods::OPTION_FILTER_MAP), LintId::of(methods::SEARCH_IS_SOME), diff --git a/clippy_lints/src/lib.register_internal.rs b/clippy_lints/src/lib.register_internal.rs index c8c1e0262ab..7d4c7d2adb5 100644 --- a/clippy_lints/src/lib.register_internal.rs +++ b/clippy_lints/src/lib.register_internal.rs @@ -9,9 +9,11 @@ store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![ LintId::of(utils::internal_lints::DEFAULT_LINT), LintId::of(utils::internal_lints::IF_CHAIN_STYLE), LintId::of(utils::internal_lints::INTERNING_DEFINED_SYMBOL), + LintId::of(utils::internal_lints::INVALID_CLIPPY_VERSION_ATTRIBUTE), LintId::of(utils::internal_lints::INVALID_PATHS), LintId::of(utils::internal_lints::LINT_WITHOUT_LINT_PASS), LintId::of(utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM), + LintId::of(utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE), LintId::of(utils::internal_lints::OUTER_EXPN_EXPN_DATA), LintId::of(utils::internal_lints::PRODUCE_ICE), LintId::of(utils::internal_lints::UNNECESSARY_SYMBOL_STR), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 2cb86418e3c..d931227765f 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -16,12 +16,16 @@ store.register_lints(&[ #[cfg(feature = "internal-lints")] utils::internal_lints::INTERNING_DEFINED_SYMBOL, #[cfg(feature = "internal-lints")] + utils::internal_lints::INVALID_CLIPPY_VERSION_ATTRIBUTE, + #[cfg(feature = "internal-lints")] utils::internal_lints::INVALID_PATHS, #[cfg(feature = "internal-lints")] utils::internal_lints::LINT_WITHOUT_LINT_PASS, #[cfg(feature = "internal-lints")] utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM, #[cfg(feature = "internal-lints")] + utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE, + #[cfg(feature = "internal-lints")] utils::internal_lints::OUTER_EXPN_EXPN_DATA, #[cfg(feature = "internal-lints")] utils::internal_lints::PRODUCE_ICE, @@ -164,6 +168,7 @@ store.register_lints(&[ implicit_return::IMPLICIT_RETURN, implicit_saturating_sub::IMPLICIT_SATURATING_SUB, inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR, + index_refutable_slice::INDEX_REFUTABLE_SLICE, indexing_slicing::INDEXING_SLICING, indexing_slicing::OUT_OF_BOUNDS_INDEXING, infinite_iter::INFINITE_ITER, @@ -288,6 +293,7 @@ store.register_lints(&[ methods::MAP_FLATTEN, methods::MAP_IDENTITY, methods::MAP_UNWRAP_OR, + methods::NEEDLESS_SPLITN, methods::NEW_RET_NO_SELF, methods::OK_EXPECT, methods::OPTION_AS_REF_DEREF, diff --git a/clippy_lints/src/lib.register_nursery.rs b/clippy_lints/src/lib.register_nursery.rs index 44c75a11eec..cc0eb71be69 100644 --- a/clippy_lints/src/lib.register_nursery.rs +++ b/clippy_lints/src/lib.register_nursery.rs @@ -13,6 +13,7 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ LintId::of(floating_point_arithmetic::IMPRECISE_FLOPS), LintId::of(floating_point_arithmetic::SUBOPTIMAL_FLOPS), LintId::of(future_not_send::FUTURE_NOT_SEND), + LintId::of(index_refutable_slice::INDEX_REFUTABLE_SLICE), LintId::of(let_if_seq::USELESS_LET_IF_SEQ), LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN), LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 7174d0a082e..2445a0aeed0 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -8,6 +8,7 @@ #![feature(rustc_private)] #![feature(stmt_expr_attributes)] #![feature(control_flow_enum)] +#![feature(let_else)] #![recursion_limit = "512"] #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![allow(clippy::missing_docs_in_private_items, clippy::must_use_candidate)] @@ -153,6 +154,10 @@ macro_rules! declare_clippy_lint { #[cfg(feature = "metadata-collector-lint")] mod deprecated_lints; +#[cfg_attr( + any(feature = "internal-lints", feature = "metadata-collector-lint"), + allow(clippy::missing_clippy_version_attribute) +)] mod utils; // begin lints modules, do not remove this comment, it’s used in `update_lints` @@ -233,6 +238,7 @@ mod implicit_hasher; mod implicit_return; mod implicit_saturating_sub; mod inconsistent_struct_constructor; +mod index_refutable_slice; mod indexing_slicing; mod infinite_iter; mod inherent_impl; @@ -404,10 +410,21 @@ use crate::utils::conf::TryConf; /// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass. /// /// Used in `./src/driver.rs`. -pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) { +pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) { // NOTE: Do not add any more pre-expansion passes. These should be removed eventually. + + 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 + }) + }); + store.register_pre_expansion_pass(|| Box::new(write::Write::default())); - store.register_pre_expansion_pass(|| Box::new(attrs::EarlyAttributes)); + store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv })); store.register_pre_expansion_pass(|| Box::new(dbg_macro::DbgMacro)); } @@ -441,7 +458,6 @@ pub fn read_conf(sess: &Session) -> Conf { /// /// Used in `./src/driver.rs`. #[allow(clippy::too_many_lines)] -#[rustfmt::skip] pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) { register_removed_non_tool_lints(store); @@ -493,11 +509,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: let vec_box_size_threshold = conf.vec_box_size_threshold; let type_complexity_threshold = conf.type_complexity_threshold; let avoid_breaking_exported_api = conf.avoid_breaking_exported_api; - store.register_late_pass(move || Box::new(types::Types::new( - vec_box_size_threshold, - type_complexity_threshold, - avoid_breaking_exported_api, - ))); + store.register_late_pass(move || { + Box::new(types::Types::new( + vec_box_size_threshold, + type_complexity_threshold, + avoid_breaking_exported_api, + )) + }); 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)); @@ -535,7 +553,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: 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)); + sess.err(&format!( + "error reading Clippy's configuration file. `{}` is not a valid Rust version", + s + )); None }) }); @@ -560,6 +581,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(size_of_in_element_count::SizeOfInElementCount)); store.register_late_pass(|| Box::new(same_name_method::SameNameMethod)); + let max_suggested_slice_pattern_length = conf.max_suggested_slice_pattern_length; + store.register_late_pass(move || { + Box::new(index_refutable_slice::IndexRefutableSlice::new( + max_suggested_slice_pattern_length, + msrv, + )) + }); store.register_late_pass(|| Box::new(map_clone::MapClone)); store.register_late_pass(|| Box::new(map_err_ignore::MapErrIgnore)); store.register_late_pass(|| Box::new(shadow::Shadow::default())); @@ -579,10 +607,14 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(temporary_assignment::TemporaryAssignment)); store.register_late_pass(|| Box::new(transmute::Transmute)); let cognitive_complexity_threshold = conf.cognitive_complexity_threshold; - store.register_late_pass(move || Box::new(cognitive_complexity::CognitiveComplexity::new(cognitive_complexity_threshold))); + store.register_late_pass(move || { + Box::new(cognitive_complexity::CognitiveComplexity::new( + cognitive_complexity_threshold, + )) + }); let too_large_for_stack = conf.too_large_for_stack; - store.register_late_pass(move || Box::new(escape::BoxedLocal{too_large_for_stack})); - store.register_late_pass(move || Box::new(vec::UselessVec{too_large_for_stack})); + store.register_late_pass(move || Box::new(escape::BoxedLocal { too_large_for_stack })); + store.register_late_pass(move || Box::new(vec::UselessVec { too_large_for_stack })); store.register_late_pass(|| Box::new(panic_unimplemented::PanicUnimplemented)); store.register_late_pass(|| Box::new(strings::StringLitAsBytes)); store.register_late_pass(|| Box::new(derive::Derive)); @@ -603,7 +635,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(blacklisted_name::BlacklistedName::new(blacklisted_names.clone()))); let too_many_arguments_threshold = conf.too_many_arguments_threshold; let too_many_lines_threshold = conf.too_many_lines_threshold; - store.register_late_pass(move || Box::new(functions::Functions::new(too_many_arguments_threshold, too_many_lines_threshold))); + store.register_late_pass(move || { + Box::new(functions::Functions::new( + too_many_arguments_threshold, + too_many_lines_threshold, + )) + }); let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::>(); store.register_late_pass(move || Box::new(doc::DocMarkdown::new(doc_valid_idents.clone()))); store.register_late_pass(|| Box::new(neg_multiply::NegMultiply)); @@ -688,14 +725,32 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(multiple_crate_versions::MultipleCrateVersions)); store.register_late_pass(|| Box::new(wildcard_dependencies::WildcardDependencies)); let literal_representation_lint_fraction_readability = conf.unreadable_literal_lint_fractions; - store.register_early_pass(move || Box::new(literal_representation::LiteralDigitGrouping::new(literal_representation_lint_fraction_readability))); + store.register_early_pass(move || { + Box::new(literal_representation::LiteralDigitGrouping::new( + literal_representation_lint_fraction_readability, + )) + }); let literal_representation_threshold = conf.literal_representation_threshold; - store.register_early_pass(move || Box::new(literal_representation::DecimalLiteralRepresentation::new(literal_representation_threshold))); + store.register_early_pass(move || { + Box::new(literal_representation::DecimalLiteralRepresentation::new( + literal_representation_threshold, + )) + }); let enum_variant_name_threshold = conf.enum_variant_name_threshold; - store.register_late_pass(move || Box::new(enum_variants::EnumVariantNames::new(enum_variant_name_threshold, avoid_breaking_exported_api))); + store.register_late_pass(move || { + Box::new(enum_variants::EnumVariantNames::new( + enum_variant_name_threshold, + avoid_breaking_exported_api, + )) + }); store.register_early_pass(|| Box::new(tabs_in_doc_comments::TabsInDocComments)); let upper_case_acronyms_aggressive = conf.upper_case_acronyms_aggressive; - store.register_late_pass(move || Box::new(upper_case_acronyms::UpperCaseAcronyms::new(avoid_breaking_exported_api, upper_case_acronyms_aggressive))); + store.register_late_pass(move || { + Box::new(upper_case_acronyms::UpperCaseAcronyms::new( + avoid_breaking_exported_api, + upper_case_acronyms_aggressive, + )) + }); store.register_late_pass(|| Box::new(default::Default::default())); store.register_late_pass(|| Box::new(unused_self::UnusedSelf)); store.register_late_pass(|| Box::new(mutable_debug_assertion::DebugAssertWithMutCall)); @@ -710,7 +765,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| Box::new(single_component_path_imports::SingleComponentPathImports)); let max_fn_params_bools = conf.max_fn_params_bools; let max_struct_bools = conf.max_struct_bools; - store.register_early_pass(move || Box::new(excessive_bools::ExcessiveBools::new(max_struct_bools, max_fn_params_bools))); + store.register_early_pass(move || { + Box::new(excessive_bools::ExcessiveBools::new( + max_struct_bools, + max_fn_params_bools, + )) + }); store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap)); let warn_on_all_wildcard_imports = conf.warn_on_all_wildcard_imports; store.register_late_pass(move || Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports))); @@ -729,9 +789,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(vec_resize_to_zero::VecResizeToZero)); store.register_late_pass(|| Box::new(panic_in_result_fn::PanicInResultFn)); let single_char_binding_names_threshold = conf.single_char_binding_names_threshold; - store.register_early_pass(move || Box::new(non_expressive_names::NonExpressiveNames { - single_char_binding_names_threshold, - })); + store.register_early_pass(move || { + Box::new(non_expressive_names::NonExpressiveNames { + single_char_binding_names_threshold, + }) + }); let macro_matcher = conf.standard_macro_braces.iter().cloned().collect::>(); store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(¯o_matcher))); store.register_late_pass(|| Box::new(macro_use::MacroUseImports::default())); @@ -754,7 +816,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(strings::StringToString)); store.register_late_pass(|| Box::new(zero_sized_map_values::ZeroSizedMapValues)); store.register_late_pass(|| Box::new(vec_init_then_push::VecInitThenPush::default())); - store.register_late_pass(|| Box::new(case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons)); + store.register_late_pass(|| { + Box::new(case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons) + }); store.register_late_pass(|| Box::new(redundant_slicing::RedundantSlicing)); store.register_late_pass(|| Box::new(from_str_radix_10::FromStrRadix10)); store.register_late_pass(|| Box::new(manual_map::ManualMap)); @@ -765,7 +829,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: let disallowed_types = conf.disallowed_types.clone(); store.register_late_pass(move || Box::new(disallowed_type::DisallowedType::new(disallowed_types.clone()))); let import_renames = conf.enforced_import_renames.clone(); - store.register_late_pass(move || Box::new(missing_enforced_import_rename::ImportRename::new(import_renames.clone()))); + store.register_late_pass(move || { + Box::new(missing_enforced_import_rename::ImportRename::new( + import_renames.clone(), + )) + }); let scripts = conf.allowed_scripts.clone(); store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(&scripts))); store.register_late_pass(|| Box::new(strlen_on_c_strings::StrlenOnCStrings)); @@ -774,7 +842,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(iter_not_returning_iterator::IterNotReturningIterator)); store.register_late_pass(move || Box::new(manual_assert::ManualAssert)); let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send; - store.register_late_pass(move || Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(enable_raw_pointer_heuristic_for_send))); + store.register_late_pass(move || { + Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new( + enable_raw_pointer_heuristic_for_send, + )) + }); store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::default())); store.register_late_pass(|| Box::new(match_str_case_mismatch::MatchStrCaseMismatch)); store.register_late_pass(move || Box::new(format_args::FormatArgs)); diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index cb0b96e0652..fad3343d128 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::{in_macro, trait_ref_of_method}; +use clippy_utils::trait_ref_of_method; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::intravisit::{ walk_fn_decl, walk_generic_param, walk_generics, walk_item, walk_param_bound, walk_poly_trait_ref, walk_ty, @@ -45,6 +45,7 @@ declare_clippy_lint! { /// x /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub NEEDLESS_LIFETIMES, complexity, "using explicit lifetimes for references in function arguments when elision rules \ @@ -73,6 +74,7 @@ declare_clippy_lint! { /// // ... /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub EXTRA_UNUSED_LIFETIMES, complexity, "unused lifetimes in function definitions" @@ -128,7 +130,7 @@ fn check_fn_inner<'tcx>( span: Span, report_extra_lifetimes: bool, ) { - if in_macro(span) || has_where_lifetimes(cx, &generics.where_clause) { + if span.from_expansion() || has_where_lifetimes(cx, &generics.where_clause) { return; } diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index 0e5121ca3d7..130543bbbee 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -2,11 +2,8 @@ //! floating-point literal expressions. use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::numeric_literal::{NumericLiteral, Radix}; use clippy_utils::source::snippet_opt; -use clippy_utils::{ - in_macro, - numeric_literal::{NumericLiteral, Radix}, -}; use if_chain::if_chain; use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind}; use rustc_errors::Applicability; @@ -31,6 +28,7 @@ declare_clippy_lint! { /// // Good /// let x: u64 = 61_864_918_973_511; /// ``` + #[clippy::version = "pre 1.29.0"] pub UNREADABLE_LITERAL, pedantic, "long literal without underscores" @@ -56,6 +54,7 @@ declare_clippy_lint! { /// // Good /// 2_i32; /// ``` + #[clippy::version = "1.30.0"] pub MISTYPED_LITERAL_SUFFIXES, correctness, "mistyped literal suffix" @@ -78,6 +77,7 @@ declare_clippy_lint! { /// // Good /// let x: u64 = 61_864_918_973_511; /// ``` + #[clippy::version = "pre 1.29.0"] pub INCONSISTENT_DIGIT_GROUPING, style, "integer literals with digits grouped inconsistently" @@ -96,6 +96,7 @@ declare_clippy_lint! { /// let x: u32 = 0xFFF_FFF; /// let y: u8 = 0b01_011_101; /// ``` + #[clippy::version = "1.49.0"] pub UNUSUAL_BYTE_GROUPINGS, style, "binary or hex literals that aren't grouped by four" @@ -114,6 +115,7 @@ declare_clippy_lint! { /// ```rust /// let x: u64 = 6186491_8973511; /// ``` + #[clippy::version = "pre 1.29.0"] pub LARGE_DIGIT_GROUPS, pedantic, "grouping digits into groups that are too large" @@ -131,6 +133,7 @@ declare_clippy_lint! { /// `255` => `0xFF` /// `65_535` => `0xFFFF` /// `4_042_322_160` => `0xF0F0_F0F0` + #[clippy::version = "pre 1.29.0"] pub DECIMAL_LITERAL_REPRESENTATION, restriction, "using decimal representation when hexadecimal would be better" @@ -283,7 +286,7 @@ impl LiteralDigitGrouping { | WarningType::InconsistentDigitGrouping | WarningType::UnusualByteGroupings | WarningType::LargeDigitGroups => { - !in_macro(lit.span) + !lit.span.from_expansion() } WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => { true diff --git a/clippy_lints/src/loops/explicit_counter_loop.rs b/clippy_lints/src/loops/explicit_counter_loop.rs index 98e60f7ed85..bf9326b2901 100644 --- a/clippy_lints/src/loops/explicit_counter_loop.rs +++ b/clippy_lints/src/loops/explicit_counter_loop.rs @@ -1,7 +1,7 @@ use super::{ get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT_COUNTER_LOOP, }; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::{get_enclosing_block, is_integer_const}; use if_chain::if_chain; @@ -9,6 +9,7 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_block, walk_expr}; use rustc_hir::{Expr, Pat}; use rustc_lint::LateContext; +use rustc_middle::ty::{self, UintTy}; // To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be // incremented exactly once in the loop body, and initialized to zero @@ -32,26 +33,61 @@ pub(super) fn check<'tcx>( walk_block(&mut initialize_visitor, block); if_chain! { - if let Some((name, initializer)) = initialize_visitor.get_result(); + if let Some((name, ty, initializer)) = initialize_visitor.get_result(); if is_integer_const(cx, initializer, 0); then { let mut applicability = Applicability::MachineApplicable; let for_span = get_span_of_entire_for_loop(expr); - span_lint_and_sugg( + let int_name = match ty.map(ty::TyS::kind) { + // usize or inferred + Some(ty::Uint(UintTy::Usize)) | None => { + span_lint_and_sugg( + cx, + EXPLICIT_COUNTER_LOOP, + for_span.with_hi(arg.span.hi()), + &format!("the variable `{}` is used as a loop counter", name), + "consider using", + format!( + "for ({}, {}) in {}.enumerate()", + name, + snippet_with_applicability(cx, pat.span, "item", &mut applicability), + make_iterator_snippet(cx, arg, &mut applicability), + ), + applicability, + ); + return; + } + Some(ty::Int(int_ty)) => int_ty.name_str(), + Some(ty::Uint(uint_ty)) => uint_ty.name_str(), + _ => return, + }; + + span_lint_and_then( cx, EXPLICIT_COUNTER_LOOP, for_span.with_hi(arg.span.hi()), &format!("the variable `{}` is used as a loop counter", name), - "consider using", - format!( - "for ({}, {}) in {}.enumerate()", - name, - snippet_with_applicability(cx, pat.span, "item", &mut applicability), - make_iterator_snippet(cx, arg, &mut applicability), - ), - applicability, + |diag| { + diag.span_suggestion( + for_span.with_hi(arg.span.hi()), + "consider using", + format!( + "for ({}, {}) in (0_{}..).zip({})", + name, + snippet_with_applicability(cx, pat.span, "item", &mut applicability), + int_name, + make_iterator_snippet(cx, arg, &mut applicability), + ), + applicability, + ); + + diag.note(&format!( + "`{}` is of type `{}`, making it ineligible for `Iterator::enumerate`", + name, int_name + )); + }, ); } } diff --git a/clippy_lints/src/loops/manual_memcpy.rs b/clippy_lints/src/loops/manual_memcpy.rs index 72027a163af..f6a673f7135 100644 --- a/clippy_lints/src/loops/manual_memcpy.rs +++ b/clippy_lints/src/loops/manual_memcpy.rs @@ -204,11 +204,8 @@ struct MinifyingSugg<'a>(Sugg<'a>); impl<'a> MinifyingSugg<'a> { fn as_str(&self) -> &str { - // HACK: Don't sync to Clippy! Required because something with the `or_patterns` feature - // changed and this would now require parentheses. - match &self.0 { - Sugg::NonParen(s) | Sugg::MaybeParen(s) | Sugg::BinOp(_, s) => s.as_ref(), - } + let (Sugg::NonParen(s) | Sugg::MaybeParen(s) | Sugg::BinOp(_, s)) = &self.0; + s.as_ref() } fn into_sugg(self) -> Sugg<'a> { @@ -445,7 +442,7 @@ fn get_loop_counters<'a, 'tcx>( let mut initialize_visitor = InitializeVisitor::new(cx, expr, var_id); walk_block(&mut initialize_visitor, block); - initialize_visitor.get_result().map(|(_, initializer)| Start { + initialize_visitor.get_result().map(|(_, _, initializer)| Start { id: var_id, kind: StartKind::Counter { initializer }, }) diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 5df1b796401..ccec648fa78 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -47,6 +47,7 @@ declare_clippy_lint! { /// # let mut dst = vec![0; 65]; /// dst[64..(src.len() + 64)].clone_from_slice(&src[..]); /// ``` + #[clippy::version = "pre 1.29.0"] pub MANUAL_MEMCPY, perf, "manually copying items between slices" @@ -75,6 +76,7 @@ declare_clippy_lint! { /// println!("{}", i); /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub NEEDLESS_RANGE_LOOP, style, "for-looping over a range of indices where an iterator over items would do" @@ -107,6 +109,7 @@ declare_clippy_lint! { /// // .. /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub EXPLICIT_ITER_LOOP, pedantic, "for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do" @@ -135,6 +138,7 @@ declare_clippy_lint! { /// // .. /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub EXPLICIT_INTO_ITER_LOOP, pedantic, "for-looping over `_.into_iter()` when `_` would do" @@ -158,6 +162,7 @@ declare_clippy_lint! { /// .. /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub ITER_NEXT_LOOP, correctness, "for-looping over `_.next()` which is probably not intended" @@ -201,6 +206,7 @@ declare_clippy_lint! { /// // .. /// } /// ``` + #[clippy::version = "1.45.0"] pub FOR_LOOPS_OVER_FALLIBLES, suspicious, "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`" @@ -233,6 +239,7 @@ declare_clippy_lint! { /// // .. do something with x /// }; /// ``` + #[clippy::version = "pre 1.29.0"] pub WHILE_LET_LOOP, complexity, "`loop { if let { ... } else break }`, which can be written as a `while let` loop" @@ -254,6 +261,7 @@ declare_clippy_lint! { /// // should be /// let len = iterator.count(); /// ``` + #[clippy::version = "1.30.0"] pub NEEDLESS_COLLECT, perf, "collecting an iterator when collect is not needed" @@ -284,6 +292,7 @@ declare_clippy_lint! { /// # fn bar(bar: usize, baz: usize) {} /// for (i, item) in v.iter().enumerate() { bar(i, *item); } /// ``` + #[clippy::version = "pre 1.29.0"] pub EXPLICIT_COUNTER_LOOP, complexity, "for-looping with an explicit counter when `_.enumerate()` would do" @@ -317,6 +326,7 @@ declare_clippy_lint! { /// ```no_run /// loop {} /// ``` + #[clippy::version = "pre 1.29.0"] pub EMPTY_LOOP, suspicious, "empty `loop {}`, which should block or sleep" @@ -336,6 +346,7 @@ declare_clippy_lint! { /// .. /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub WHILE_LET_ON_ITERATOR, style, "using a `while let` loop instead of a for loop on an iterator" @@ -364,6 +375,7 @@ declare_clippy_lint! { /// .. /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub FOR_KV_MAP, style, "looping on a map using `iter` when `keys` or `values` would do" @@ -385,6 +397,7 @@ declare_clippy_lint! { /// break; /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub NEVER_LOOP, correctness, "any loop that will always `break` or `return`" @@ -420,6 +433,7 @@ declare_clippy_lint! { /// println!("{}", i); // prints numbers from 0 to 42, not 0 to 21 /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub MUT_RANGE_BOUND, suspicious, "for loop over a range where one of the bounds is a mutable variable" @@ -446,6 +460,7 @@ declare_clippy_lint! { /// println!("let me loop forever!"); /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub WHILE_IMMUTABLE_CONDITION, correctness, "variables used within while expression are not mutated in the body" @@ -480,6 +495,7 @@ declare_clippy_lint! { /// let mut vec: Vec = vec![item1; 20]; /// vec.resize(20 + 30, item2); /// ``` + #[clippy::version = "1.47.0"] pub SAME_ITEM_PUSH, style, "the same item is pushed inside of a for loop" @@ -506,6 +522,7 @@ declare_clippy_lint! { /// let item = &item1; /// println!("{}", item); /// ``` + #[clippy::version = "1.49.0"] pub SINGLE_ELEMENT_LOOP, complexity, "there is no reason to have a single element loop" @@ -537,6 +554,7 @@ declare_clippy_lint! { /// println!("{}", n); /// } /// ``` + #[clippy::version = "1.52.0"] pub MANUAL_FLATTEN, complexity, "for loops over `Option`s or `Result`s with a single expression can be simplified" diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index e87f4b66912..6f3acb45ba4 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -3,13 +3,16 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_trait_method, path_to_local_id}; +use clippy_utils::{can_move_expr_to_closure, is_trait_method, path_to_local, path_to_local_id, CaptureKind}; use if_chain::if_chain; +use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; -use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, StmtKind}; +use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, Local, Mutability, Node, PatKind, Stmt, StmtKind}; use rustc_lint::LateContext; use rustc_middle::hir::map::Map; +use rustc_middle::ty::subst::GenericArgKind; +use rustc_middle::ty::{self, TyS}; use rustc_span::sym; use rustc_span::{MultiSpan, Span}; @@ -83,7 +86,8 @@ fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCo is_type_diagnostic_item(cx, ty, sym::VecDeque) || is_type_diagnostic_item(cx, ty, sym::BinaryHeap) || is_type_diagnostic_item(cx, ty, sym::LinkedList); - if let Some(iter_calls) = detect_iter_and_into_iters(block, id); + let iter_ty = cx.typeck_results().expr_ty(iter_source); + if let Some(iter_calls) = detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty)); if let [iter_call] = &*iter_calls; then { let mut used_count_visitor = UsedCountVisitor { @@ -167,37 +171,89 @@ enum IterFunctionKind { Contains(Span), } -struct IterFunctionVisitor { - uses: Vec, +struct IterFunctionVisitor<'a, 'tcx> { + illegal_mutable_capture_ids: HirIdSet, + current_mutably_captured_ids: HirIdSet, + cx: &'a LateContext<'tcx>, + uses: Vec>, + hir_id_uses_map: FxHashMap, + current_statement_hir_id: Option, seen_other: bool, target: HirId, } -impl<'tcx> Visitor<'tcx> for IterFunctionVisitor { +impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { + fn visit_block(&mut self, block: &'tcx Block<'tcx>) { + for (expr, hir_id) in block.stmts.iter().filter_map(get_expr_and_hir_id_from_stmt) { + self.visit_block_expr(expr, hir_id); + } + if let Some(expr) = block.expr { + self.visit_block_expr(expr, None); + } + } + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { // Check function calls on our collection if let ExprKind::MethodCall(method_name, _, [recv, args @ ..], _) = &expr.kind { + if method_name.ident.name == sym!(collect) && is_trait_method(self.cx, expr, sym::Iterator) { + self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv)); + self.visit_expr(recv); + return; + } + if path_to_local_id(recv, self.target) { - match &*method_name.ident.name.as_str() { - "into_iter" => self.uses.push(IterFunction { - func: IterFunctionKind::IntoIter, - span: expr.span, - }), - "len" => self.uses.push(IterFunction { - func: IterFunctionKind::Len, - span: expr.span, - }), - "is_empty" => self.uses.push(IterFunction { - func: IterFunctionKind::IsEmpty, - span: expr.span, - }), - "contains" => self.uses.push(IterFunction { - func: IterFunctionKind::Contains(args[0].span), - span: expr.span, - }), - _ => self.seen_other = true, + if self + .illegal_mutable_capture_ids + .intersection(&self.current_mutably_captured_ids) + .next() + .is_none() + { + if let Some(hir_id) = self.current_statement_hir_id { + self.hir_id_uses_map.insert(hir_id, self.uses.len()); + } + match &*method_name.ident.name.as_str() { + "into_iter" => self.uses.push(Some(IterFunction { + func: IterFunctionKind::IntoIter, + span: expr.span, + })), + "len" => self.uses.push(Some(IterFunction { + func: IterFunctionKind::Len, + span: expr.span, + })), + "is_empty" => self.uses.push(Some(IterFunction { + func: IterFunctionKind::IsEmpty, + span: expr.span, + })), + "contains" => self.uses.push(Some(IterFunction { + func: IterFunctionKind::Contains(args[0].span), + span: expr.span, + })), + _ => { + self.seen_other = true; + if let Some(hir_id) = self.current_statement_hir_id { + self.hir_id_uses_map.remove(&hir_id); + } + }, + } } return; } + + if let Some(hir_id) = path_to_local(recv) { + if let Some(index) = self.hir_id_uses_map.remove(&hir_id) { + if self + .illegal_mutable_capture_ids + .intersection(&self.current_mutably_captured_ids) + .next() + .is_none() + { + if let Some(hir_id) = self.current_statement_hir_id { + self.hir_id_uses_map.insert(hir_id, index); + } + } else { + self.uses[index] = None; + } + } + } } // Check if the collection is used for anything else if path_to_local_id(expr, self.target) { @@ -213,6 +269,28 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor { } } +impl<'tcx> IterFunctionVisitor<'_, 'tcx> { + fn visit_block_expr(&mut self, expr: &'tcx Expr<'tcx>, hir_id: Option) { + self.current_statement_hir_id = hir_id; + self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(expr)); + self.visit_expr(expr); + } +} + +fn get_expr_and_hir_id_from_stmt<'v>(stmt: &'v Stmt<'v>) -> Option<(&'v Expr<'v>, Option)> { + match stmt.kind { + StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some((expr, None)), + StmtKind::Item(..) => None, + StmtKind::Local(Local { init, pat, .. }) => { + if let PatKind::Binding(_, hir_id, ..) = pat.kind { + init.map(|init_expr| (init_expr, Some(hir_id))) + } else { + init.map(|init_expr| (init_expr, None)) + } + }, + } +} + struct UsedCountVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, id: HirId, @@ -237,12 +315,60 @@ impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> { /// Detect the occurrences of calls to `iter` or `into_iter` for the /// given identifier -fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, id: HirId) -> Option> { +fn detect_iter_and_into_iters<'tcx: 'a, 'a>( + block: &'tcx Block<'tcx>, + id: HirId, + cx: &'a LateContext<'tcx>, + captured_ids: HirIdSet, +) -> Option> { let mut visitor = IterFunctionVisitor { uses: Vec::new(), target: id, seen_other: false, + cx, + current_mutably_captured_ids: HirIdSet::default(), + illegal_mutable_capture_ids: captured_ids, + hir_id_uses_map: FxHashMap::default(), + current_statement_hir_id: None, }; visitor.visit_block(block); - if visitor.seen_other { None } else { Some(visitor.uses) } + if visitor.seen_other { + None + } else { + Some(visitor.uses.into_iter().flatten().collect()) + } +} + +fn get_captured_ids(cx: &LateContext<'tcx>, ty: &'_ TyS<'_>) -> HirIdSet { + fn get_captured_ids_recursive(cx: &LateContext<'tcx>, ty: &'_ TyS<'_>, set: &mut HirIdSet) { + match ty.kind() { + ty::Adt(_, generics) => { + for generic in *generics { + if let GenericArgKind::Type(ty) = generic.unpack() { + get_captured_ids_recursive(cx, ty, set); + } + } + }, + ty::Closure(def_id, _) => { + let closure_hir_node = cx.tcx.hir().get_if_local(*def_id).unwrap(); + if let Node::Expr(closure_expr) = closure_hir_node { + can_move_expr_to_closure(cx, closure_expr) + .unwrap() + .into_iter() + .for_each(|(hir_id, capture_kind)| { + if matches!(capture_kind, CaptureKind::Ref(Mutability::Mut)) { + set.insert(hir_id); + } + }); + } + }, + _ => (), + } + } + + let mut set = HirIdSet::default(); + + get_captured_ids_recursive(cx, ty, &mut set); + + set } diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs index f9f515cc40a..c1e367b344a 100644 --- a/clippy_lints/src/loops/utils.rs +++ b/clippy_lints/src/loops/utils.rs @@ -1,14 +1,17 @@ use clippy_utils::ty::{has_iter_method, implements_trait}; use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_local_id, sugg}; use if_chain::if_chain; +use rustc_ast::ast::{LitIntType, LitKind}; use rustc_errors::Applicability; -use rustc_hir::intravisit::{walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; -use rustc_hir::HirIdMap; -use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Stmt, StmtKind}; +use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; +use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt}; use rustc_lint::LateContext; use rustc_middle::hir::map::Map; +use rustc_middle::ty::Ty; use rustc_span::source_map::Span; +use rustc_span::source_map::Spanned; use rustc_span::symbol::{sym, Symbol}; +use rustc_typeck::hir_ty_to_ty; use std::iter::Iterator; #[derive(Debug, PartialEq)] @@ -106,10 +109,11 @@ impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> { } enum InitializeVisitorState<'hir> { - Initial, // Not examined yet - Declared(Symbol), // Declared but not (yet) initialized + Initial, // Not examined yet + Declared(Symbol, Option>), // Declared but not (yet) initialized Initialized { name: Symbol, + ty: Option>, initializer: &'hir Expr<'hir>, }, DontWarn, @@ -138,9 +142,9 @@ impl<'a, 'tcx> InitializeVisitor<'a, 'tcx> { } } - pub(super) fn get_result(&self) -> Option<(Symbol, &'tcx Expr<'tcx>)> { - if let InitializeVisitorState::Initialized { name, initializer } = self.state { - Some((name, initializer)) + pub(super) fn get_result(&self) -> Option<(Symbol, Option>, &'tcx Expr<'tcx>)> { + if let InitializeVisitorState::Initialized { name, ty, initializer } = self.state { + Some((name, ty, initializer)) } else { None } @@ -150,22 +154,25 @@ impl<'a, 'tcx> InitializeVisitor<'a, 'tcx> { impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> { type Map = Map<'tcx>; - fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { + fn visit_local(&mut self, l: &'tcx Local<'_>) { // Look for declarations of the variable if_chain! { - if let StmtKind::Local(local) = stmt.kind; - if local.pat.hir_id == self.var_id; - if let PatKind::Binding(.., ident, _) = local.pat.kind; + if l.pat.hir_id == self.var_id; + if let PatKind::Binding(.., ident, _) = l.pat.kind; then { - self.state = local.init.map_or(InitializeVisitorState::Declared(ident.name), |init| { + let ty = l.ty.map(|ty| hir_ty_to_ty(self.cx.tcx, ty)); + + self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| { InitializeVisitorState::Initialized { initializer: init, + ty, name: ident.name, } }) } } - walk_stmt(self, stmt); + + walk_local(self, l); } fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { @@ -195,15 +202,38 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> { self.state = InitializeVisitorState::DontWarn; }, ExprKind::Assign(lhs, rhs, _) if lhs.hir_id == expr.hir_id => { - self.state = if_chain! { - if self.depth == 0; - if let InitializeVisitorState::Declared(name) - | InitializeVisitorState::Initialized { name, ..} = self.state; - then { - InitializeVisitorState::Initialized { initializer: rhs, name } - } else { - InitializeVisitorState::DontWarn + self.state = if self.depth == 0 { + match self.state { + InitializeVisitorState::Declared(name, mut ty) => { + if ty.is_none() { + if let ExprKind::Lit(Spanned { + node: LitKind::Int(_, LitIntType::Unsuffixed), + .. + }) = rhs.kind + { + ty = None; + } else { + ty = self.cx.typeck_results().expr_ty_opt(rhs); + } + } + + InitializeVisitorState::Initialized { + initializer: rhs, + ty, + name, + } + }, + InitializeVisitorState::Initialized { ty, name, .. } => { + InitializeVisitorState::Initialized { + initializer: rhs, + ty, + name, + } + }, + _ => InitializeVisitorState::DontWarn, } + } else { + InitializeVisitorState::DontWarn } }, ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { diff --git a/clippy_lints/src/macro_use.rs b/clippy_lints/src/macro_use.rs index c38162743a3..262a26951c6 100644 --- a/clippy_lints/src/macro_use.rs +++ b/clippy_lints/src/macro_use.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::in_macro; use clippy_utils::source::snippet; use hir::def::{DefKind, Res}; use if_chain::if_chain; @@ -9,6 +8,7 @@ use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::hygiene::ExpnKind; use rustc_span::{edition::Edition, sym, Span}; declare_clippy_lint! { @@ -24,6 +24,7 @@ declare_clippy_lint! { /// #[macro_use] /// use some_macro; /// ``` + #[clippy::version = "1.44.0"] pub MACRO_USE_IMPORTS, pedantic, "#[macro_use] is no longer needed" @@ -35,7 +36,8 @@ struct PathAndSpan { span: Span, } -/// `MacroRefData` includes the name of the macro. +/// `MacroRefData` includes the name of the macro +/// and the path from `SourceMap::span_to_filename`. #[derive(Debug, Clone)] pub struct MacroRefData { name: String, @@ -212,3 +214,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports { } } } + +fn in_macro(span: Span) -> bool { + span.from_expansion() && !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..)) +} diff --git a/clippy_lints/src/main_recursion.rs b/clippy_lints/src/main_recursion.rs index 23b3ba2296e..fad8fa467d4 100644 --- a/clippy_lints/src/main_recursion.rs +++ b/clippy_lints/src/main_recursion.rs @@ -20,6 +20,7 @@ declare_clippy_lint! { /// main(); /// } /// ``` + #[clippy::version = "1.38.0"] pub MAIN_RECURSION, style, "recursion using the entrypoint" diff --git a/clippy_lints/src/manual_assert.rs b/clippy_lints/src/manual_assert.rs index e55aa3f1850..ed3166086f7 100644 --- a/clippy_lints/src/manual_assert.rs +++ b/clippy_lints/src/manual_assert.rs @@ -26,6 +26,7 @@ declare_clippy_lint! { /// let sad_people: Vec<&str> = vec![]; /// assert!(sad_people.is_empty(), "there are sad people: {:?}", sad_people); /// ``` + #[clippy::version = "1.57.0"] pub MANUAL_ASSERT, pedantic, "`panic!` and only a `panic!` in `if`-then statement" diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index b632af455f8..86819752f90 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -30,6 +30,7 @@ declare_clippy_lint! { /// ```rust /// async fn foo() -> i32 { 42 } /// ``` + #[clippy::version = "1.45.0"] pub MANUAL_ASYNC_FN, style, "manual implementations of `async` functions can be simplified using the dedicated syntax" diff --git a/clippy_lints/src/manual_map.rs b/clippy_lints/src/manual_map.rs index 96df3d0a490..4d8ad566e6b 100644 --- a/clippy_lints/src/manual_map.rs +++ b/clippy_lints/src/manual_map.rs @@ -2,7 +2,7 @@ use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher::IfLetOrMatch; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; -use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable}; +use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function}; use clippy_utils::{ can_move_expr_to_closure, in_constant, is_else_clause, is_lang_ctor, is_lint_allowed, path_to_local_id, peel_hir_expr_refs, peel_hir_expr_while, CaptureKind, @@ -11,7 +11,8 @@ use rustc_ast::util::parser::PREC_POSTFIX; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{ - def::Res, Arm, BindingAnnotation, Block, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath, + def::Res, Arm, BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, + QPath, UnsafeSource, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; @@ -36,6 +37,7 @@ declare_clippy_lint! { /// ```rust /// Some(0).map(|x| x + 1); /// ``` + #[clippy::version = "1.52.0"] pub MANUAL_MAP, style, "reimplementation of `map`" @@ -92,20 +94,20 @@ impl LateLintPass<'_> for ManualMap { return; } - let some_expr = match get_some_expr(cx, some_expr, expr_ctxt) { + let some_expr = match get_some_expr(cx, some_expr, false, expr_ctxt) { Some(expr) => expr, None => return, }; // These two lints will go back and forth with each other. - if cx.typeck_results().expr_ty(some_expr) == cx.tcx.types.unit + if cx.typeck_results().expr_ty(some_expr.expr) == cx.tcx.types.unit && !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id) { return; } // `map` won't perform any adjustments. - if !cx.typeck_results().expr_adjustments(some_expr).is_empty() { + if !cx.typeck_results().expr_adjustments(some_expr.expr).is_empty() { return; } @@ -119,7 +121,7 @@ impl LateLintPass<'_> for ManualMap { None => "", }; - match can_move_expr_to_closure(cx, some_expr) { + match can_move_expr_to_closure(cx, some_expr.expr) { Some(captures) => { // Check if captures the closure will need conflict with borrows made in the scrutinee. // TODO: check all the references made in the scrutinee expression. This will require interacting @@ -157,12 +159,14 @@ impl LateLintPass<'_> for ManualMap { }; let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind { - match can_pass_as_func(cx, id, some_expr) { - Some(func) if func.span.ctxt() == some_expr.span.ctxt() => { + if_chain! { + if !some_expr.needs_unsafe_block; + if let Some(func) = can_pass_as_func(cx, id, some_expr.expr); + if func.span.ctxt() == some_expr.expr.span.ctxt(); + then { snippet_with_applicability(cx, func.span, "..", &mut app).into_owned() - }, - _ => { - if path_to_local_id(some_expr, id) + } else { + if path_to_local_id(some_expr.expr, id) && !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id) && binding_ref.is_some() { @@ -175,21 +179,23 @@ impl LateLintPass<'_> for ManualMap { } else { "" }; - format!( - "|{}{}| {}", - annotation, - some_binding, - snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0 - ) - }, + let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0; + if some_expr.needs_unsafe_block { + format!("|{}{}| unsafe {{ {} }}", annotation, some_binding, expr_snip) + } else { + format!("|{}{}| {}", annotation, some_binding, expr_snip) + } + } } } else if !is_wild_none && explicit_ref.is_none() { // TODO: handle explicit reference annotations. - format!( - "|{}| {}", - snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0, - snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0 - ) + let pat_snip = snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0; + let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0; + if some_expr.needs_unsafe_block { + format!("|{}| unsafe {{ {} }}", pat_snip, expr_snip) + } else { + format!("|{}| {}", pat_snip, expr_snip) + } } else { // Refutable bindings and mixed reference annotations can't be handled by `map`. return; @@ -216,7 +222,9 @@ impl LateLintPass<'_> for ManualMap { fn can_pass_as_func(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { match expr.kind { ExprKind::Call(func, [arg]) - if path_to_local_id(arg, binding) && cx.typeck_results().expr_adjustments(arg).is_empty() => + if path_to_local_id(arg, binding) + && cx.typeck_results().expr_adjustments(arg).is_empty() + && !type_is_unsafe_function(cx, cx.typeck_results().expr_ty(func).peel_refs()) => { Some(func) }, @@ -236,6 +244,11 @@ enum OptionPat<'a> { }, } +struct SomeExpr<'tcx> { + expr: &'tcx Expr<'tcx>, + needs_unsafe_block: bool, +} + // Try to parse into a recognized `Option` pattern. // i.e. `_`, `None`, `Some(..)`, or a reference to any of those. fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxContext) -> Option> { @@ -256,7 +269,12 @@ fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxCon } // Checks for an expression wrapped by the `Some` constructor. Returns the contained expression. -fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ctxt: SyntaxContext) -> Option<&'tcx Expr<'tcx>> { +fn get_some_expr( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + needs_unsafe_block: bool, + ctxt: SyntaxContext, +) -> Option> { // TODO: Allow more complex expressions. match expr.kind { ExprKind::Call( @@ -265,15 +283,24 @@ fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ctxt: SyntaxConte .. }, [arg], - ) if ctxt == expr.span.ctxt() && is_lang_ctor(cx, qpath, OptionSome) => Some(arg), + ) if ctxt == expr.span.ctxt() && is_lang_ctor(cx, qpath, OptionSome) => Some(SomeExpr { + expr: arg, + needs_unsafe_block, + }), ExprKind::Block( Block { stmts: [], expr: Some(expr), + rules, .. }, _, - ) => get_some_expr(cx, expr, ctxt), + ) => get_some_expr( + cx, + expr, + needs_unsafe_block || *rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), + ctxt, + ), _ => None, } } diff --git a/clippy_lints/src/manual_non_exhaustive.rs b/clippy_lints/src/manual_non_exhaustive.rs index 335ea001ee4..63a72d4fdde 100644 --- a/clippy_lints/src/manual_non_exhaustive.rs +++ b/clippy_lints/src/manual_non_exhaustive.rs @@ -52,6 +52,7 @@ declare_clippy_lint! { /// #[non_exhaustive] /// struct T(pub i32, pub i32); /// ``` + #[clippy::version = "1.45.0"] pub MANUAL_NON_EXHAUSTIVE, style, "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]" diff --git a/clippy_lints/src/manual_ok_or.rs b/clippy_lints/src/manual_ok_or.rs index cf641d0ce86..b60e2dc366b 100644 --- a/clippy_lints/src/manual_ok_or.rs +++ b/clippy_lints/src/manual_ok_or.rs @@ -32,6 +32,7 @@ declare_clippy_lint! { /// let foo: Option = None; /// foo.ok_or("error"); /// ``` + #[clippy::version = "1.49.0"] pub MANUAL_OK_OR, pedantic, "finds patterns that can be encoded more concisely with `Option::ok_or`" diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs index 4e040508b6b..f8e28f1671f 100644 --- a/clippy_lints/src/manual_strip.rs +++ b/clippy_lints/src/manual_strip.rs @@ -42,6 +42,7 @@ declare_clippy_lint! { /// assert_eq!(end.to_uppercase(), "WORLD!"); /// } /// ``` + #[clippy::version = "1.48.0"] pub MANUAL_STRIP, complexity, "suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing" diff --git a/clippy_lints/src/manual_unwrap_or.rs b/clippy_lints/src/manual_unwrap_or.rs index 42478e3416e..aac3c6e0de2 100644 --- a/clippy_lints/src/manual_unwrap_or.rs +++ b/clippy_lints/src/manual_unwrap_or.rs @@ -35,6 +35,7 @@ declare_clippy_lint! { /// let foo: Option = None; /// foo.unwrap_or(1); /// ``` + #[clippy::version = "1.49.0"] pub MANUAL_UNWRAP_OR, complexity, "finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`" diff --git a/clippy_lints/src/map_clone.rs b/clippy_lints/src/map_clone.rs index 7db5c7e52ea..c2b78e21861 100644 --- a/clippy_lints/src/map_clone.rs +++ b/clippy_lints/src/map_clone.rs @@ -37,6 +37,7 @@ declare_clippy_lint! { /// let y = x.iter(); /// let z = y.cloned(); /// ``` + #[clippy::version = "pre 1.29.0"] pub MAP_CLONE, style, "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types" diff --git a/clippy_lints/src/map_err_ignore.rs b/clippy_lints/src/map_err_ignore.rs index 82d3732326e..61f21d532c5 100644 --- a/clippy_lints/src/map_err_ignore.rs +++ b/clippy_lints/src/map_err_ignore.rs @@ -97,6 +97,7 @@ declare_clippy_lint! { /// }) /// } /// ``` + #[clippy::version = "1.48.0"] pub MAP_ERR_IGNORE, restriction, "`map_err` should not ignore the original error" diff --git a/clippy_lints/src/map_unit_fn.rs b/clippy_lints/src/map_unit_fn.rs index 40de9ffcd4e..a84de3e079b 100644 --- a/clippy_lints/src/map_unit_fn.rs +++ b/clippy_lints/src/map_unit_fn.rs @@ -47,6 +47,7 @@ declare_clippy_lint! { /// log_err_msg(format_msg(msg)); /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub OPTION_MAP_UNIT_FN, complexity, "using `option.map(f)`, where `f` is a function or closure that returns `()`" @@ -87,6 +88,7 @@ declare_clippy_lint! { /// log_err_msg(format_msg(msg)); /// }; /// ``` + #[clippy::version = "pre 1.29.0"] pub RESULT_MAP_UNIT_FN, complexity, "using `result.map(f)`, where `f` is a function or closure that returns `()`" diff --git a/clippy_lints/src/match_on_vec_items.rs b/clippy_lints/src/match_on_vec_items.rs index 552c9a58897..583b577ffe2 100644 --- a/clippy_lints/src/match_on_vec_items.rs +++ b/clippy_lints/src/match_on_vec_items.rs @@ -40,6 +40,7 @@ declare_clippy_lint! { /// _ => {}, /// } /// ``` + #[clippy::version = "1.45.0"] pub MATCH_ON_VEC_ITEMS, pedantic, "matching on vector elements can panic" diff --git a/clippy_lints/src/match_result_ok.rs b/clippy_lints/src/match_result_ok.rs index ecf6ad316a4..b1839f00aae 100644 --- a/clippy_lints/src/match_result_ok.rs +++ b/clippy_lints/src/match_result_ok.rs @@ -38,6 +38,7 @@ declare_clippy_lint! { /// vec.push(value) /// } /// ``` + #[clippy::version = "1.57.0"] pub MATCH_RESULT_OK, style, "usage of `ok()` in `let Some(pat)` statements is unnecessary, match on `Ok(pat)` instead" diff --git a/clippy_lints/src/match_str_case_mismatch.rs b/clippy_lints/src/match_str_case_mismatch.rs index f501593c518..3316ebf4051 100644 --- a/clippy_lints/src/match_str_case_mismatch.rs +++ b/clippy_lints/src/match_str_case_mismatch.rs @@ -39,6 +39,7 @@ declare_clippy_lint! { /// _ => {}, /// } /// ``` + #[clippy::version = "1.58.0"] pub MATCH_STR_CASE_MISMATCH, correctness, "creation of a case altering match expression with non-compliant arms" diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 7142df98c3f..08c23ca6d32 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -8,9 +8,9 @@ use clippy_utils::sugg::Sugg; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs}; use clippy_utils::visitors::is_local_used; use clippy_utils::{ - get_parent_expr, in_macro, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild, - meets_msrv, msrvs, path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, - remove_blocks, strip_pat_refs, + get_parent_expr, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs, + path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, remove_blocks, + strip_pat_refs, }; use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash}; use core::array; @@ -26,7 +26,6 @@ use rustc_hir::{ }; use rustc_hir::{HirIdMap, HirIdSet}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{self, Ty, TyS, VariantDef}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -34,8 +33,6 @@ use rustc_span::source_map::{Span, Spanned}; use rustc_span::sym; use std::cmp::Ordering; use std::collections::hash_map::Entry; -use std::iter; -use std::ops::Bound; declare_clippy_lint! { /// ### What it does @@ -60,6 +57,7 @@ declare_clippy_lint! { /// bar(foo); /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub SINGLE_MATCH, style, "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`" @@ -101,6 +99,7 @@ declare_clippy_lint! { /// bar(&other_ref); /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub SINGLE_MATCH_ELSE, pedantic, "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern" @@ -132,6 +131,7 @@ declare_clippy_lint! { /// _ => frob(x), /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub MATCH_REF_PATS, style, "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression" @@ -166,6 +166,7 @@ declare_clippy_lint! { /// bar(); /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub MATCH_BOOL, pedantic, "a `match` on a boolean expression instead of an `if..else` block" @@ -188,6 +189,7 @@ declare_clippy_lint! { /// _ => (), /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub MATCH_OVERLAPPING_ARM, style, "a `match` with overlapping arms" @@ -210,6 +212,7 @@ declare_clippy_lint! { /// Err(_) => panic!("err"), /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub MATCH_WILD_ERR_ARM, pedantic, "a `match` with `Err(_)` arm and take drastic actions" @@ -236,6 +239,7 @@ declare_clippy_lint! { /// // Good /// let r: Option<&()> = x.as_ref(); /// ``` + #[clippy::version = "pre 1.29.0"] pub MATCH_AS_REF, complexity, "a `match` on an Option value instead of using `as_ref()` or `as_mut`" @@ -268,6 +272,7 @@ declare_clippy_lint! { /// Foo::B(_) => {}, /// } /// ``` + #[clippy::version = "1.34.0"] pub WILDCARD_ENUM_MATCH_ARM, restriction, "a wildcard enum match arm using `_`" @@ -302,6 +307,7 @@ declare_clippy_lint! { /// Foo::C => {}, /// } /// ``` + #[clippy::version = "1.45.0"] pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS, pedantic, "a wildcard enum match for a single variant" @@ -329,6 +335,7 @@ declare_clippy_lint! { /// _ => {}, /// } /// ``` + #[clippy::version = "1.42.0"] pub WILDCARD_IN_OR_PATTERNS, complexity, "a wildcard pattern used with others patterns in same match arm" @@ -364,6 +371,7 @@ declare_clippy_lint! { /// let wrapper = Wrapper::Data(42); /// let Wrapper::Data(data) = wrapper; /// ``` + #[clippy::version = "pre 1.29.0"] pub INFALLIBLE_DESTRUCTURING_MATCH, style, "a `match` statement with a single infallible arm instead of a `let`" @@ -395,6 +403,7 @@ declare_clippy_lint! { /// // Good /// let (c, d) = (a, b); /// ``` + #[clippy::version = "1.43.0"] pub MATCH_SINGLE_BINDING, complexity, "a match with a single binding instead of using `let` statement" @@ -425,6 +434,7 @@ declare_clippy_lint! { /// _ => {}, /// } /// ``` + #[clippy::version = "1.43.0"] pub REST_PAT_IN_FULLY_BOUND_STRUCTS, restriction, "a match on a struct that binds all fields but still uses the wildcard pattern" @@ -480,6 +490,7 @@ declare_clippy_lint! { /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {} /// Ok::(42).is_ok(); /// ``` + #[clippy::version = "1.31.0"] pub REDUNDANT_PATTERN_MATCHING, style, "use the proper utility function avoiding an `if let`" @@ -516,6 +527,7 @@ declare_clippy_lint! { /// // Good /// let a = matches!(x, Some(0)); /// ``` + #[clippy::version = "1.47.0"] pub MATCH_LIKE_MATCHES_MACRO, style, "a match that could be written with the matches! macro" @@ -560,6 +572,7 @@ declare_clippy_lint! { /// Quz => quz(), /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub MATCH_SAME_ARMS, pedantic, "`match` with identical arm bodies" @@ -602,7 +615,7 @@ impl_lint_pass!(Matches => [ impl<'tcx> LateLintPass<'tcx> for Matches { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if in_external_macro(cx.sess(), expr.span) || in_macro(expr.span) { + if expr.span.from_expansion() { return; } @@ -634,15 +647,11 @@ impl<'tcx> LateLintPass<'tcx> for Matches { if let ExprKind::Match(ex, arms, _) = expr.kind { check_match_ref_pats(cx, ex, arms.iter().map(|el| el.pat), expr); } - if let Some(higher::IfLet { let_pat, let_expr, .. }) = higher::IfLet::hir(cx, expr) { - check_match_ref_pats(cx, let_expr, once(let_pat), expr); - } } fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { if_chain! { - if !in_external_macro(cx.sess(), local.span); - if !in_macro(local.span); + if !local.span.from_expansion(); if let Some(expr) = local.init; if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind; if arms.len() == 1 && arms[0].guard.is_none(); @@ -677,8 +686,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { if_chain! { - if !in_external_macro(cx.sess(), pat.span); - if !in_macro(pat.span); + if !pat.span.from_expansion(); if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind; if let Some(def_id) = path.res.opt_def_id(); let ty = cx.tcx.type_of(def_id); @@ -705,7 +713,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { #[rustfmt::skip] fn check_single_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { - if in_macro(expr.span) { + if expr.span.from_expansion() { // Don't lint match expressions present in // macro_rules! block return; @@ -1451,7 +1459,7 @@ fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option { #[allow(clippy::too_many_lines)] fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) { - if in_macro(expr.span) || arms.len() != 1 || is_refutable(cx, arms[0].pat) { + if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) { return; } @@ -1606,27 +1614,27 @@ fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<' None } -/// Gets all arms that are unbounded `PatRange`s. +/// Gets the ranges for each range pattern arm. Applies `ty` bounds for open ranges. fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec> { arms.iter() .filter_map(|arm| { if let Arm { pat, guard: None, .. } = *arm { if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind { - let lhs = match lhs { + let lhs_const = match lhs { Some(lhs) => constant(cx, cx.typeck_results(), lhs)?.0, None => miri_to_const(ty.numeric_min_val(cx.tcx)?)?, }; - let rhs = match rhs { + let rhs_const = match rhs { Some(rhs) => constant(cx, cx.typeck_results(), rhs)?.0, None => miri_to_const(ty.numeric_max_val(cx.tcx)?)?, }; - let lhs_val = lhs.int_value(cx, ty)?; - let rhs_val = rhs.int_value(cx, ty)?; + let lhs_val = lhs_const.int_value(cx, ty)?; + let rhs_val = rhs_const.int_value(cx, ty)?; let rhs_bound = match range_end { - RangeEnd::Included => Bound::Included(rhs_val), - RangeEnd::Excluded => Bound::Excluded(rhs_val), + RangeEnd::Included => EndBound::Included(rhs_val), + RangeEnd::Excluded => EndBound::Excluded(rhs_val), }; return Some(SpannedRange { span: pat.span, @@ -1638,7 +1646,7 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) let value = constant_full_int(cx, cx.typeck_results(), value)?; return Some(SpannedRange { span: pat.span, - node: (value, Bound::Included(value)), + node: (value, EndBound::Included(value)), }); } } @@ -1647,10 +1655,16 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) .collect() } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum EndBound { + Included(T), + Excluded(T), +} + #[derive(Debug, Eq, PartialEq)] -pub struct SpannedRange { +struct SpannedRange { pub span: Span, - pub node: (T, Bound), + pub node: (T, EndBound), } // Checks if arm has the form `None => None` @@ -1699,82 +1713,63 @@ where ref_count > 1 } -pub fn overlapping(ranges: &[SpannedRange]) -> Option<(&SpannedRange, &SpannedRange)> +fn overlapping(ranges: &[SpannedRange]) -> Option<(&SpannedRange, &SpannedRange)> where T: Copy + Ord, { + #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] + enum BoundKind { + EndExcluded, + Start, + EndIncluded, + } + #[derive(Copy, Clone, Debug, Eq, PartialEq)] - enum Kind<'a, T> { - Start(T, &'a SpannedRange), - End(Bound, &'a SpannedRange), - } + struct RangeBound<'a, T>(T, BoundKind, &'a SpannedRange); - impl<'a, T: Copy> Kind<'a, T> { - fn range(&self) -> &'a SpannedRange { - match *self { - Kind::Start(_, r) | Kind::End(_, r) => r, - } - } - - fn value(self) -> Bound { - match self { - Kind::Start(t, _) => Bound::Included(t), - Kind::End(t, _) => t, - } - } - } - - impl<'a, T: Copy + Ord> PartialOrd for Kind<'a, T> { + impl<'a, T: Copy + Ord> PartialOrd for RangeBound<'a, T> { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } - impl<'a, T: Copy + Ord> Ord for Kind<'a, T> { - fn cmp(&self, other: &Self) -> Ordering { - match (self.value(), other.value()) { - (Bound::Included(a), Bound::Included(b)) | (Bound::Excluded(a), Bound::Excluded(b)) => a.cmp(&b), - // Range patterns cannot be unbounded (yet) - (Bound::Unbounded, _) | (_, Bound::Unbounded) => unimplemented!(), - (Bound::Included(a), Bound::Excluded(b)) => match a.cmp(&b) { - Ordering::Equal => Ordering::Greater, - other => other, - }, - (Bound::Excluded(a), Bound::Included(b)) => match a.cmp(&b) { - Ordering::Equal => Ordering::Less, - other => other, - }, - } + impl<'a, T: Copy + Ord> Ord for RangeBound<'a, T> { + fn cmp(&self, RangeBound(other_value, other_kind, _): &Self) -> Ordering { + let RangeBound(self_value, self_kind, _) = *self; + (self_value, self_kind).cmp(&(*other_value, *other_kind)) } } let mut values = Vec::with_capacity(2 * ranges.len()); - for r in ranges { - values.push(Kind::Start(r.node.0, r)); - values.push(Kind::End(r.node.1, r)); + for r @ SpannedRange { node: (start, end), .. } in ranges { + values.push(RangeBound(*start, BoundKind::Start, r)); + values.push(match end { + EndBound::Excluded(val) => RangeBound(*val, BoundKind::EndExcluded, r), + EndBound::Included(val) => RangeBound(*val, BoundKind::EndIncluded, r), + }); } values.sort(); - for (a, b) in iter::zip(&values, values.iter().skip(1)) { - match (a, b) { - (&Kind::Start(_, ra), &Kind::End(_, rb)) => { - if ra.node != rb.node { - return Some((ra, rb)); - } - }, - (&Kind::End(a, _), &Kind::Start(b, _)) if a != Bound::Included(b) => (), - _ => { - // skip if the range `a` is completely included into the range `b` - if let Ordering::Equal | Ordering::Less = a.cmp(b) { - let kind_a = Kind::End(a.range().node.1, a.range()); - let kind_b = Kind::End(b.range().node.1, b.range()); - if let Ordering::Equal | Ordering::Greater = kind_a.cmp(&kind_b) { - return None; + let mut started = vec![]; + + for RangeBound(_, kind, range) in values { + match kind { + BoundKind::Start => started.push(range), + BoundKind::EndExcluded | BoundKind::EndIncluded => { + let mut overlap = None; + + while let Some(last_started) = started.pop() { + if last_started == range { + break; } + overlap = Some(last_started); + } + + if let Some(first_overlapping) = overlap { + return Some((range, first_overlapping)); } - return Some((a.range(), b.range())); }, } } @@ -1786,7 +1781,8 @@ mod redundant_pattern_match { use super::REDUNDANT_PATTERN_MATCHING; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher; - use clippy_utils::source::{snippet, snippet_with_applicability}; + use clippy_utils::source::snippet; + use clippy_utils::sugg::Sugg; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type}; use clippy_utils::{is_lang_ctor, is_qpath_def_path, is_trait_method, paths}; use if_chain::if_chain; @@ -1796,7 +1792,7 @@ mod redundant_pattern_match { use rustc_hir::LangItem::{OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; use rustc_hir::{ intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor}, - Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, Pat, PatKind, QPath, + Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, Pat, PatKind, QPath, UnOp, }; use rustc_lint::LateContext; use rustc_middle::ty::{self, subst::GenericArgKind, Ty}; @@ -2050,8 +2046,10 @@ mod redundant_pattern_match { let result_expr = match &let_expr.kind { ExprKind::AddrOf(_, _, borrowed) => borrowed, + ExprKind::Unary(UnOp::Deref, deref) => deref, _ => let_expr, }; + span_lint_and_then( cx, REDUNDANT_PATTERN_MATCHING, @@ -2070,12 +2068,15 @@ mod redundant_pattern_match { // ^^^^^^^^^^^^^^^^^^^ let span = expr_span.until(op_span.shrink_to_hi()); - let mut app = if needs_drop { + let app = if needs_drop { Applicability::MaybeIncorrect } else { Applicability::MachineApplicable }; - let sugg = snippet_with_applicability(cx, op_span, "_", &mut app); + + let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_") + .maybe_par() + .to_string(); diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app); @@ -2225,29 +2226,29 @@ fn test_overlapping() { }; assert_eq!(None, overlapping::(&[])); - assert_eq!(None, overlapping(&[sp(1, Bound::Included(4))])); + assert_eq!(None, overlapping(&[sp(1, EndBound::Included(4))])); assert_eq!( None, - overlapping(&[sp(1, Bound::Included(4)), sp(5, Bound::Included(6))]) + overlapping(&[sp(1, EndBound::Included(4)), sp(5, EndBound::Included(6))]) ); assert_eq!( None, overlapping(&[ - sp(1, Bound::Included(4)), - sp(5, Bound::Included(6)), - sp(10, Bound::Included(11)) + sp(1, EndBound::Included(4)), + sp(5, EndBound::Included(6)), + sp(10, EndBound::Included(11)) ],) ); assert_eq!( - Some((&sp(1, Bound::Included(4)), &sp(3, Bound::Included(6)))), - overlapping(&[sp(1, Bound::Included(4)), sp(3, Bound::Included(6))]) + Some((&sp(1, EndBound::Included(4)), &sp(3, EndBound::Included(6)))), + overlapping(&[sp(1, EndBound::Included(4)), sp(3, EndBound::Included(6))]) ); assert_eq!( - Some((&sp(5, Bound::Included(6)), &sp(6, Bound::Included(11)))), + Some((&sp(5, EndBound::Included(6)), &sp(6, EndBound::Included(11)))), overlapping(&[ - sp(1, Bound::Included(4)), - sp(5, Bound::Included(6)), - sp(6, Bound::Included(11)) + sp(1, EndBound::Included(4)), + sp(5, EndBound::Included(6)), + sp(6, EndBound::Included(11)) ],) ); } diff --git a/clippy_lints/src/mem_forget.rs b/clippy_lints/src/mem_forget.rs index eb437dc47af..5ffcfd4d264 100644 --- a/clippy_lints/src/mem_forget.rs +++ b/clippy_lints/src/mem_forget.rs @@ -19,6 +19,7 @@ declare_clippy_lint! { /// # use std::rc::Rc; /// mem::forget(Rc::new(55)) /// ``` + #[clippy::version = "pre 1.29.0"] pub MEM_FORGET, restriction, "`mem::forget` usage on `Drop` types, likely to cause memory leaks" diff --git a/clippy_lints/src/mem_replace.rs b/clippy_lints/src/mem_replace.rs index 1e6057a8fe9..7fc39f17232 100644 --- a/clippy_lints/src/mem_replace.rs +++ b/clippy_lints/src/mem_replace.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::ty::is_non_aggregate_primitive_type; -use clippy_utils::{in_macro, is_default_equivalent, is_lang_ctor, match_def_path, meets_msrv, msrvs, paths}; +use clippy_utils::{is_default_equivalent, is_lang_ctor, match_def_path, meets_msrv, msrvs, paths}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::LangItem::OptionNone; @@ -35,6 +35,7 @@ declare_clippy_lint! { /// let mut an_option = Some(0); /// let taken = an_option.take(); /// ``` + #[clippy::version = "1.31.0"] pub MEM_REPLACE_OPTION_WITH_NONE, style, "replacing an `Option` with `None` instead of `take()`" @@ -66,6 +67,7 @@ declare_clippy_lint! { /// The [take_mut](https://docs.rs/take_mut) crate offers a sound solution, /// at the cost of either lazily creating a replacement value or aborting /// on panic, to ensure that the uninitialized value cannot be observed. + #[clippy::version = "1.39.0"] pub MEM_REPLACE_WITH_UNINIT, correctness, "`mem::replace(&mut _, mem::uninitialized())` or `mem::replace(&mut _, mem::zeroed())`" @@ -90,6 +92,7 @@ declare_clippy_lint! { /// let mut text = String::from("foo"); /// let taken = std::mem::take(&mut text); /// ``` + #[clippy::version = "1.42.0"] pub MEM_REPLACE_WITH_DEFAULT, style, "replacing a value of type `T` with `T::default()` instead of using `std::mem::take`" @@ -213,7 +216,7 @@ fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr< expr_span, "replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`", |diag| { - if !in_macro(expr_span) { + if !expr_span.from_expansion() { let suggestion = format!("std::mem::take({})", snippet(cx, dest.span, "")); diag.span_suggestion( diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs index da428a7b487..b7690cf9222 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -1,7 +1,7 @@ use super::{contains_return, BIND_INSTEAD_OF_MAP}; use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{snippet, snippet_with_macro_callsite}; -use clippy_utils::{in_macro, remove_blocks, visitors::find_all_ret_expressions}; +use clippy_utils::{remove_blocks, visitors::find_all_ret_expressions}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; @@ -106,7 +106,7 @@ pub(crate) trait BindInsteadOfMap { let mut suggs = Vec::new(); let can_sugg: bool = find_all_ret_expressions(cx, closure_expr, |ret_expr| { if_chain! { - if !in_macro(ret_expr.span); + if !ret_expr.span.from_expansion(); if let hir::ExprKind::Call(func_path, [arg]) = ret_expr.kind; if let hir::ExprKind::Path(QPath::Resolved(_, path)) = func_path.kind; if Self::is_variant(cx, path.res); diff --git a/clippy_lints/src/methods/from_iter_instead_of_collect.rs b/clippy_lints/src/methods/from_iter_instead_of_collect.rs index 99c03844f49..8ea9312c0f7 100644 --- a/clippy_lints/src/methods/from_iter_instead_of_collect.rs +++ b/clippy_lints/src/methods/from_iter_instead_of_collect.rs @@ -69,7 +69,7 @@ fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'tcx>) - // i.e.: 2 wildcards in `std::collections::BTreeMap<&i32, &char>` let ty_str = ty.to_string(); let start = ty_str.find('<').unwrap_or(0); - let end = ty_str.find('>').unwrap_or_else(|| ty_str.len()); + let end = ty_str.find('>').unwrap_or(ty_str.len()); let nb_wildcard = ty_str[start..end].split(',').count(); let wildcards = format!("_{}", ", _".repeat(nb_wildcard - 1)); format!("{}<{}>", elements.join("::"), wildcards) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 26c29fbb289..6ab5015ca78 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -33,7 +33,6 @@ mod iter_nth_zero; mod iter_skip_next; mod iterator_step_by_zero; mod manual_saturating_arithmetic; -mod manual_split_once; mod manual_str_repeat; mod map_collect_result_unit; mod map_flatten; @@ -50,6 +49,7 @@ mod single_char_insert_string; mod single_char_pattern; mod single_char_push_string; mod skip_while_next; +mod str_splitn; mod string_extend_chars; mod suspicious_map; mod suspicious_splitn; @@ -68,7 +68,7 @@ use bind_instead_of_map::BindInsteadOfMap; use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item}; -use clippy_utils::{contains_return, get_trait_def_id, in_macro, iter_input_pats, meets_msrv, msrvs, paths, return_ty}; +use clippy_utils::{contains_return, get_trait_def_id, iter_input_pats, meets_msrv, msrvs, paths, return_ty}; use if_chain::if_chain; use rustc_hir as hir; use rustc_hir::def::Res; @@ -99,6 +99,7 @@ declare_clippy_lint! { /// ```rust /// [1, 2, 3].iter().copied(); /// ``` + #[clippy::version = "1.53.0"] pub CLONED_INSTEAD_OF_COPIED, pedantic, "used `cloned` where `copied` could be used instead" @@ -121,6 +122,7 @@ declare_clippy_lint! { /// ```rust /// let nums: Vec = ["1", "2", "whee!"].iter().filter_map(|x| x.parse().ok()).collect(); /// ``` + #[clippy::version = "1.53.0"] pub FLAT_MAP_OPTION, pedantic, "used `flat_map` where `filter_map` could be used instead" @@ -166,6 +168,7 @@ declare_clippy_lint! { /// // Good /// res.expect("more helpful message"); /// ``` + #[clippy::version = "1.45.0"] pub UNWRAP_USED, restriction, "using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`" @@ -208,6 +211,7 @@ declare_clippy_lint! { /// res?; /// # Ok::<(), ()>(()) /// ``` + #[clippy::version = "1.45.0"] pub EXPECT_USED, restriction, "using `.expect()` on `Result` or `Option`, which might be better handled" @@ -237,6 +241,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub SHOULD_IMPLEMENT_TRAIT, style, "defining a method that should be implementing a std trait" @@ -284,6 +289,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub WRONG_SELF_CONVENTION, style, "defining a method named with an established prefix (like \"into_\") that takes `self` with the wrong convention" @@ -310,6 +316,7 @@ declare_clippy_lint! { /// // Good /// x.expect("why did I do this again?"); /// ``` + #[clippy::version = "pre 1.29.0"] pub OK_EXPECT, style, "using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result" @@ -335,6 +342,7 @@ declare_clippy_lint! { /// // Good /// x.unwrap_or_default(); /// ``` + #[clippy::version = "1.56.0"] pub UNWRAP_OR_ELSE_DEFAULT, style, "using `.unwrap_or_else(Default::default)`, which is more succinctly expressed as `.unwrap_or_default()`" @@ -375,6 +383,7 @@ declare_clippy_lint! { /// // Good /// x.map_or_else(some_function, |a| a + 1); /// ``` + #[clippy::version = "1.45.0"] pub MAP_UNWRAP_OR, pedantic, "using `.map(f).unwrap_or(a)` or `.map(f).unwrap_or_else(func)`, which are more succinctly expressed as `map_or(a, f)` or `map_or_else(a, f)`" @@ -401,6 +410,7 @@ declare_clippy_lint! { /// // Good /// opt.and_then(|a| Some(a + 1)); /// ``` + #[clippy::version = "pre 1.29.0"] pub OPTION_MAP_OR_NONE, style, "using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`" @@ -426,6 +436,7 @@ declare_clippy_lint! { /// # let r: Result = Ok(1); /// assert_eq!(Some(1), r.ok()); /// ``` + #[clippy::version = "1.44.0"] pub RESULT_MAP_OR_INTO_OPTION, style, "using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`" @@ -458,6 +469,7 @@ declare_clippy_lint! { /// let _ = res().map(|s| if s.len() == 42 { 10 } else { 20 }); /// let _ = res().map_err(|s| if s.len() == 42 { 10 } else { 20 }); /// ``` + #[clippy::version = "1.45.0"] pub BIND_INSTEAD_OF_MAP, complexity, "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`" @@ -481,6 +493,7 @@ declare_clippy_lint! { /// # let vec = vec![1]; /// vec.iter().find(|x| **x == 0); /// ``` + #[clippy::version = "pre 1.29.0"] pub FILTER_NEXT, complexity, "using `filter(p).next()`, which is more succinctly expressed as `.find(p)`" @@ -504,6 +517,7 @@ declare_clippy_lint! { /// # let vec = vec![1]; /// vec.iter().find(|x| **x != 0); /// ``` + #[clippy::version = "1.42.0"] pub SKIP_WHILE_NEXT, complexity, "using `skip_while(p).next()`, which is more succinctly expressed as `.find(!p)`" @@ -527,6 +541,7 @@ declare_clippy_lint! { /// // Good /// vec.iter().flat_map(|x| x.iter()); /// ``` + #[clippy::version = "1.31.0"] pub MAP_FLATTEN, pedantic, "using combinations of `flatten` and `map` which can usually be written as a single method call" @@ -553,6 +568,7 @@ declare_clippy_lint! { /// ```rust /// (0_i32..10).filter_map(|n| n.checked_add(1)); /// ``` + #[clippy::version = "1.51.0"] pub MANUAL_FILTER_MAP, complexity, "using `_.filter(_).map(_)` in a way that can be written more simply as `filter_map(_)`" @@ -579,6 +595,7 @@ declare_clippy_lint! { /// ```rust /// (0_i32..10).find_map(|n| n.checked_add(1)); /// ``` + #[clippy::version = "1.51.0"] pub MANUAL_FIND_MAP, complexity, "using `_.find(_).map(_)` in a way that can be written more simply as `find_map(_)`" @@ -601,6 +618,7 @@ declare_clippy_lint! { /// ```rust /// (0..3).find_map(|x| if x == 2 { Some(x) } else { None }); /// ``` + #[clippy::version = "1.36.0"] pub FILTER_MAP_NEXT, pedantic, "using combination of `filter_map` and `next` which can usually be written as a single method call" @@ -623,6 +641,7 @@ declare_clippy_lint! { /// # let iter = vec![vec![0]].into_iter(); /// iter.flatten(); /// ``` + #[clippy::version = "1.39.0"] pub FLAT_MAP_IDENTITY, complexity, "call to `flat_map` where `flatten` is sufficient" @@ -652,6 +671,7 @@ declare_clippy_lint! { /// /// let _ = !"hello world".contains("world"); /// ``` + #[clippy::version = "pre 1.29.0"] pub SEARCH_IS_SOME, complexity, "using an iterator or string search followed by `is_some()` or `is_none()`, which is more succinctly expressed as a call to `any()` or `contains()` (with negation in case of `is_none()`)" @@ -676,6 +696,7 @@ declare_clippy_lint! { /// let name = "foo"; /// if name.starts_with('_') {}; /// ``` + #[clippy::version = "pre 1.29.0"] pub CHARS_NEXT_CMP, style, "using `.chars().next()` to check if a string starts with a char" @@ -710,6 +731,7 @@ declare_clippy_lint! { /// # let foo = Some(String::new()); /// foo.unwrap_or_default(); /// ``` + #[clippy::version = "pre 1.29.0"] pub OR_FUN_CALL, perf, "using any `*or` method with a function call, which suggests `*or_else`" @@ -748,6 +770,7 @@ declare_clippy_lint! { /// # let err_msg = "I'm a teapot"; /// foo.unwrap_or_else(|| panic!("Err {}: {}", err_code, err_msg)); /// ``` + #[clippy::version = "pre 1.29.0"] pub EXPECT_FUN_CALL, perf, "using any `expect` method with a function call" @@ -765,6 +788,7 @@ declare_clippy_lint! { /// ```rust /// 42u64.clone(); /// ``` + #[clippy::version = "pre 1.29.0"] pub CLONE_ON_COPY, complexity, "using `clone` on a `Copy` type" @@ -792,6 +816,7 @@ declare_clippy_lint! { /// // Good /// Rc::clone(&x); /// ``` + #[clippy::version = "pre 1.29.0"] pub CLONE_ON_REF_PTR, restriction, "using 'clone' on a ref-counted pointer" @@ -814,6 +839,7 @@ declare_clippy_lint! { /// println!("{:p} {:p}", *y, z); // prints out the same pointer /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub CLONE_DOUBLE_REF, correctness, "using `clone` on `&&T`" @@ -837,6 +863,7 @@ declare_clippy_lint! { /// // OK, the specialized impl is used /// ["foo", "bar"].iter().map(|&s| s.to_string()); /// ``` + #[clippy::version = "1.40.0"] pub INEFFICIENT_TO_STRING, pedantic, "using `to_string` on `&&T` where `T: ToString`" @@ -898,6 +925,7 @@ declare_clippy_lint! { /// fn new() -> Self; /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub NEW_RET_NO_SELF, style, "not returning type containing `Self` in a `new` method" @@ -922,6 +950,7 @@ declare_clippy_lint! { /// /// // Good /// _.split('x'); + #[clippy::version = "pre 1.29.0"] pub SINGLE_CHAR_PATTERN, perf, "using a single-character str where a char could be used, e.g., `_.split(\"x\")`" @@ -941,6 +970,7 @@ declare_clippy_lint! { /// //.. /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub ITERATOR_STEP_BY_ZERO, correctness, "using `Iterator::step_by(0)`, which will panic at runtime" @@ -962,6 +992,7 @@ declare_clippy_lint! { /// ```rust /// let _ = std::iter::empty::>().flatten(); /// ``` + #[clippy::version = "1.53.0"] pub OPTION_FILTER_MAP, complexity, "filtering `Option` for `Some` then force-unwrapping, which can be one type-safe operation" @@ -989,6 +1020,7 @@ declare_clippy_lint! { /// # s.insert(1); /// let x = s.iter().next(); /// ``` + #[clippy::version = "1.42.0"] pub ITER_NTH_ZERO, style, "replace `iter.nth(0)` with `iter.next()`" @@ -1015,6 +1047,7 @@ declare_clippy_lint! { /// let bad_vec = some_vec.get(3); /// let bad_slice = &some_vec[..].get(3); /// ``` + #[clippy::version = "pre 1.29.0"] pub ITER_NTH, perf, "using `.iter().nth()` on a standard library type with O(1) element access" @@ -1039,6 +1072,7 @@ declare_clippy_lint! { /// let bad_vec = some_vec.iter().nth(3); /// let bad_slice = &some_vec[..].iter().nth(3); /// ``` + #[clippy::version = "pre 1.29.0"] pub ITER_SKIP_NEXT, style, "using `.skip(x).next()` on an iterator" @@ -1075,6 +1109,7 @@ declare_clippy_lint! { /// let last = some_vec[3]; /// some_vec[0] = 1; /// ``` + #[clippy::version = "pre 1.29.0"] pub GET_UNWRAP, restriction, "using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead" @@ -1098,6 +1133,7 @@ declare_clippy_lint! { /// // Good /// a.append(&mut b); /// ``` + #[clippy::version = "1.55.0"] pub EXTEND_WITH_DRAIN, perf, "using vec.append(&mut vec) to move the full range of a vecor to another" @@ -1127,6 +1163,7 @@ declare_clippy_lint! { /// s.push_str(abc); /// s.push_str(&def); /// ``` + #[clippy::version = "pre 1.29.0"] pub STRING_EXTEND_CHARS, style, "using `x.extend(s.chars())` where s is a `&str` or `String`" @@ -1150,6 +1187,7 @@ declare_clippy_lint! { /// let s = [1, 2, 3, 4, 5]; /// let s2: Vec = s.to_vec(); /// ``` + #[clippy::version = "pre 1.29.0"] pub ITER_CLONED_COLLECT, style, "using `.cloned().collect()` on slice to create a `Vec`" @@ -1174,6 +1212,7 @@ declare_clippy_lint! { /// // Good /// name.ends_with('_') || name.ends_with('-'); /// ``` + #[clippy::version = "pre 1.29.0"] pub CHARS_LAST_CMP, style, "using `.chars().last()` or `.chars().next_back()` to check if a string ends with a char" @@ -1199,6 +1238,7 @@ declare_clippy_lint! { /// let x: &[i32] = &[1, 2, 3, 4, 5]; /// do_stuff(x); /// ``` + #[clippy::version = "pre 1.29.0"] pub USELESS_ASREF, complexity, "using `as_ref` where the types before and after the call are the same" @@ -1221,6 +1261,7 @@ declare_clippy_lint! { /// ```rust /// let _ = (0..3).any(|x| x > 2); /// ``` + #[clippy::version = "pre 1.29.0"] pub UNNECESSARY_FOLD, style, "using `fold` when a more succinct alternative exists" @@ -1250,6 +1291,7 @@ declare_clippy_lint! { /// // As there is no conditional check on the argument this could be written as: /// let _ = (0..4).map(|x| x + 1); /// ``` + #[clippy::version = "1.31.0"] pub UNNECESSARY_FILTER_MAP, complexity, "using `filter_map` when a more succinct alternative exists" @@ -1273,6 +1315,7 @@ declare_clippy_lint! { /// // Good /// let _ = (&vec![3, 4, 5]).iter(); /// ``` + #[clippy::version = "1.32.0"] pub INTO_ITER_ON_REF, style, "using `.into_iter()` on a reference" @@ -1292,6 +1335,7 @@ declare_clippy_lint! { /// ```rust /// let _ = (0..3).map(|x| x + 2).count(); /// ``` + #[clippy::version = "1.39.0"] pub SUSPICIOUS_MAP, suspicious, "suspicious usage of map" @@ -1326,6 +1370,7 @@ declare_clippy_lint! { /// MaybeUninit::uninit().assume_init() /// }; /// ``` + #[clippy::version = "1.39.0"] pub UNINIT_ASSUMED_INIT, correctness, "`MaybeUninit::uninit().assume_init()`" @@ -1354,6 +1399,7 @@ declare_clippy_lint! { /// let add = x.saturating_add(y); /// let sub = x.saturating_sub(y); /// ``` + #[clippy::version = "1.39.0"] pub MANUAL_SATURATING_ARITHMETIC, style, "`.chcked_add/sub(x).unwrap_or(MAX/MIN)`" @@ -1371,6 +1417,7 @@ declare_clippy_lint! { /// ```rust /// unsafe { (&() as *const ()).offset(1) }; /// ``` + #[clippy::version = "1.41.0"] pub ZST_OFFSET, correctness, "Check for offset calculations on raw pointers to zero-sized types" @@ -1412,6 +1459,7 @@ declare_clippy_lint! { /// # Ok::<_, std::io::Error>(()) /// # }; /// ``` + #[clippy::version = "1.42.0"] pub FILETYPE_IS_FILE, restriction, "`FileType::is_file` is not recommended to test for readable file type" @@ -1437,6 +1485,7 @@ declare_clippy_lint! { /// opt.as_deref() /// # ; /// ``` + #[clippy::version = "1.42.0"] pub OPTION_AS_REF_DEREF, complexity, "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`" @@ -1463,6 +1512,7 @@ declare_clippy_lint! { /// a.get(2); /// b.get(0); /// ``` + #[clippy::version = "1.46.0"] pub ITER_NEXT_SLICE, style, "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`" @@ -1488,6 +1538,7 @@ declare_clippy_lint! { /// string.insert(0, 'R'); /// string.push('R'); /// ``` + #[clippy::version = "1.49.0"] pub SINGLE_CHAR_ADD_STR, style, "`push_str()` or `insert_str()` used with a single-character string literal as parameter" @@ -1526,6 +1577,7 @@ declare_clippy_lint! { /// /// opt.unwrap_or(42); /// ``` + #[clippy::version = "1.48.0"] pub UNNECESSARY_LAZY_EVALUATIONS, style, "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation" @@ -1546,6 +1598,7 @@ declare_clippy_lint! { /// ```rust /// (0..3).try_for_each(|t| Err(t)); /// ``` + #[clippy::version = "1.49.0"] pub MAP_COLLECT_RESULT_UNIT, style, "using `.map(_).collect::()`, which can be replaced with `try_for_each`" @@ -1578,6 +1631,7 @@ declare_clippy_lint! { /// /// assert_eq!(v, vec![5, 5, 5, 5, 5]); /// ``` + #[clippy::version = "1.49.0"] pub FROM_ITER_INSTEAD_OF_COLLECT, pedantic, "use `.collect()` instead of `::from_iter()`" @@ -1607,6 +1661,7 @@ declare_clippy_lint! { /// assert!(x >= 0); /// }); /// ``` + #[clippy::version = "1.51.0"] pub INSPECT_FOR_EACH, complexity, "using `.inspect().for_each()`, which can be replaced with `.for_each()`" @@ -1629,6 +1684,7 @@ declare_clippy_lint! { /// # let iter = vec![Some(1)].into_iter(); /// iter.flatten(); /// ``` + #[clippy::version = "1.52.0"] pub FILTER_MAP_IDENTITY, complexity, "call to `filter_map` where `flatten` is sufficient" @@ -1651,6 +1707,7 @@ declare_clippy_lint! { /// let x = [1, 2, 3]; /// let y: Vec<_> = x.iter().map(|x| 2*x).collect(); /// ``` + #[clippy::version = "1.52.0"] pub MAP_IDENTITY, complexity, "using iterator.map(|x| x)" @@ -1672,6 +1729,7 @@ declare_clippy_lint! { /// // Good /// let _ = "Hello".as_bytes().get(3); /// ``` + #[clippy::version = "1.52.0"] pub BYTES_NTH, style, "replace `.bytes().nth()` with `.as_bytes().get()`" @@ -1697,6 +1755,7 @@ declare_clippy_lint! { /// let b = a.clone(); /// let c = a.clone(); /// ``` + #[clippy::version = "1.52.0"] pub IMPLICIT_CLONE, pedantic, "implicitly cloning a value by invoking a function on its dereferenced type" @@ -1722,6 +1781,7 @@ declare_clippy_lint! { /// let _ = some_vec.len(); /// let _ = &some_vec[..].len(); /// ``` + #[clippy::version = "1.52.0"] pub ITER_COUNT, complexity, "replace `.iter().count()` with `.len()`" @@ -1751,6 +1811,7 @@ declare_clippy_lint! { /// // use x /// } /// ``` + #[clippy::version = "1.54.0"] pub SUSPICIOUS_SPLITN, correctness, "checks for `.splitn(0, ..)` and `.splitn(1, ..)`" @@ -1771,6 +1832,7 @@ declare_clippy_lint! { /// // Good /// let x: String = "x".repeat(10); /// ``` + #[clippy::version = "1.54.0"] pub MANUAL_STR_REPEAT, perf, "manual implementation of `str::repeat`" @@ -1793,11 +1855,36 @@ declare_clippy_lint! { /// let (key, value) = _.split_once('=')?; /// let value = _.split_once('=')?.1; /// ``` + #[clippy::version = "1.57.0"] pub MANUAL_SPLIT_ONCE, complexity, "replace `.splitn(2, pat)` with `.split_once(pat)`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for usages of `str::splitn` (or `str::rsplitn`) where using `str::split` would be the same. + /// ### Why is this bad? + /// The function `split` is simpler and there is no performance difference in these cases, considering + /// that both functions return a lazy iterator. + /// ### Example + /// ```rust + /// // Bad + /// let str = "key=value=add"; + /// let _ = str.splitn(3, '=').next().unwrap(); + /// ``` + /// Use instead: + /// ```rust + /// // Good + /// let str = "key=value=add"; + /// let _ = str.split('=').next().unwrap(); + /// ``` + #[clippy::version = "1.58.0"] + pub NEEDLESS_SPLITN, + complexity, + "usages of `str::splitn` that can be replaced with `str::split`" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Option, @@ -1876,7 +1963,8 @@ impl_lint_pass!(Methods => [ SUSPICIOUS_SPLITN, MANUAL_STR_REPEAT, EXTEND_WITH_DRAIN, - MANUAL_SPLIT_ONCE + MANUAL_SPLIT_ONCE, + NEEDLESS_SPLITN ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -1900,7 +1988,7 @@ macro_rules! method_call { impl<'tcx> LateLintPass<'tcx> for Methods { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - if in_macro(expr.span) { + if expr.span.from_expansion() { return; } @@ -2208,7 +2296,10 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) { suspicious_splitn::check(cx, name, expr, recv, count); if count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE) { - manual_split_once::check(cx, name, expr, recv, pat_arg); + str_splitn::check_manual_split_once(cx, name, expr, recv, pat_arg); + } + if count >= 2 { + str_splitn::check_needless_splitn(cx, name, expr, recv, pat_arg, count); } } }, diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints/src/methods/option_map_or_none.rs index e99b6b07d15..5e5c1038e82 100644 --- a/clippy_lints/src/methods/option_map_or_none.rs +++ b/clippy_lints/src/methods/option_map_or_none.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_lang_ctor; use clippy_utils::source::snippet; use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{is_lang_ctor, single_segment_path}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::LangItem::{OptionNone, OptionSome}; @@ -11,6 +11,28 @@ use rustc_span::symbol::sym; use super::OPTION_MAP_OR_NONE; use super::RESULT_MAP_OR_INTO_OPTION; +// The expression inside a closure may or may not have surrounding braces +// which causes problems when generating a suggestion. +fn reduce_unit_expression<'a>( + cx: &LateContext<'_>, + expr: &'a hir::Expr<'_>, +) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> { + match expr.kind { + hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)), + hir::ExprKind::Block(block, _) => { + match (block.stmts, block.expr) { + (&[], Some(inner_expr)) => { + // If block only contains an expression, + // reduce `|x| { x + 1 }` to `|x| x + 1` + reduce_unit_expression(cx, inner_expr) + }, + _ => None, + } + }, + _ => None, + } +} + /// lint use of `_.map_or(None, _)` for `Option`s and `Result`s pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, @@ -31,58 +53,75 @@ pub(super) fn check<'tcx>( return; } - let (lint_name, msg, instead, hint) = { - let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = def_arg.kind { - is_lang_ctor(cx, qpath, OptionNone) - } else { - return; - }; - - if !default_arg_is_none { - // nothing to lint! - return; - } - - let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_arg.kind { - is_lang_ctor(cx, qpath, OptionSome) - } else { - false - }; - - if is_option { - let self_snippet = snippet(cx, recv.span, ".."); - let func_snippet = snippet(cx, map_arg.span, ".."); - let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \ - `and_then(..)` instead"; - ( - OPTION_MAP_OR_NONE, - msg, - "try using `and_then` instead", - format!("{0}.and_then({1})", self_snippet, func_snippet), - ) - } else if f_arg_is_some { - let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \ - `ok()` instead"; - let self_snippet = snippet(cx, recv.span, ".."); - ( - RESULT_MAP_OR_INTO_OPTION, - msg, - "try using `ok` instead", - format!("{0}.ok()", self_snippet), - ) - } else { - // nothing to lint! - return; - } + let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = def_arg.kind { + is_lang_ctor(cx, qpath, OptionNone) + } else { + return; }; - span_lint_and_sugg( - cx, - lint_name, - expr.span, - msg, - instead, - hint, - Applicability::MachineApplicable, - ); + if !default_arg_is_none { + // nothing to lint! + return; + } + + let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_arg.kind { + is_lang_ctor(cx, qpath, OptionSome) + } else { + false + }; + + if is_option { + let self_snippet = snippet(cx, recv.span, ".."); + if_chain! { + if let hir::ExprKind::Closure(_, _, id, span, _) = map_arg.kind; + let arg_snippet = snippet(cx, span, ".."); + let body = cx.tcx.hir().body(id); + if let Some((func, arg_char)) = reduce_unit_expression(cx, &body.value); + if arg_char.len() == 1; + if let hir::ExprKind::Path(ref qpath) = func.kind; + if let Some(segment) = single_segment_path(qpath); + if segment.ident.name == sym::Some; + then { + let func_snippet = snippet(cx, arg_char[0].span, ".."); + let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \ + `map(..)` instead"; + return span_lint_and_sugg( + cx, + OPTION_MAP_OR_NONE, + expr.span, + msg, + "try using `map` instead", + format!("{0}.map({1} {2})", self_snippet, arg_snippet,func_snippet), + Applicability::MachineApplicable, + ); + } + + } + + let func_snippet = snippet(cx, map_arg.span, ".."); + let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \ + `and_then(..)` instead"; + return span_lint_and_sugg( + cx, + OPTION_MAP_OR_NONE, + expr.span, + msg, + "try using `and_then` instead", + format!("{0}.and_then({1})", self_snippet, func_snippet), + Applicability::MachineApplicable, + ); + } else if f_arg_is_some { + let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \ + `ok()` instead"; + let self_snippet = snippet(cx, recv.span, ".."); + return span_lint_and_sugg( + cx, + RESULT_MAP_OR_INTO_OPTION, + expr.span, + msg, + "try using `ok` instead", + format!("{0}.ok()", self_snippet), + Applicability::MachineApplicable, + ); + } } diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index fe9ffde0d33..4e4653dadca 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -1,16 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::eager_or_lazy::is_lazyness_candidate; -use clippy_utils::is_trait_item; +use clippy_utils::eager_or_lazy::switch_to_lazy_eval; use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_macro_callsite}; -use clippy_utils::ty::implements_trait; -use clippy_utils::ty::{is_type_diagnostic_item, match_type}; -use clippy_utils::{contains_return, last_path_segment, paths}; +use clippy_utils::ty::{implements_trait, match_type}; +use clippy_utils::{contains_return, is_trait_item, last_path_segment, paths}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::{BlockCheckMode, UnsafeSource}; use rustc_lint::LateContext; -use rustc_middle::ty; use rustc_span::source_map::Span; use rustc_span::symbol::{kw, sym}; use std::borrow::Cow; @@ -96,25 +92,10 @@ pub(super) fn check<'tcx>( (&paths::RESULT, true, &["or", "unwrap_or"], "else"), ]; - if let hir::ExprKind::MethodCall(path, _, [self_arg, ..], _) = &arg.kind { - if path.ident.name == sym::len { - let ty = cx.typeck_results().expr_ty(self_arg).peel_refs(); - - match ty.kind() { - ty::Slice(_) | ty::Array(_, _) | ty::Str => return, - _ => (), - } - - if is_type_diagnostic_item(cx, ty, sym::Vec) { - return; - } - } - } - if_chain! { if KNOW_TYPES.iter().any(|k| k.2.contains(&name)); - if is_lazyness_candidate(cx, arg); + if switch_to_lazy_eval(cx, arg); if !contains_return(arg); let self_ty = cx.typeck_results().expr_ty(self_expr); @@ -166,26 +147,30 @@ pub(super) fn check<'tcx>( } } - if args.len() == 2 { - match args[1].kind { + if let [self_arg, arg] = args { + let inner_arg = if let hir::ExprKind::Block( + hir::Block { + stmts: [], + expr: Some(expr), + .. + }, + _, + ) = arg.kind + { + expr + } else { + arg + }; + match inner_arg.kind { hir::ExprKind::Call(fun, or_args) => { let or_has_args = !or_args.is_empty(); - if !check_unwrap_or_default(cx, name, fun, &args[0], &args[1], or_has_args, expr.span) { + if !check_unwrap_or_default(cx, name, fun, self_arg, arg, or_has_args, expr.span) { let fun_span = if or_has_args { None } else { Some(fun.span) }; - check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, fun_span); + check_general_case(cx, name, method_span, self_arg, arg, expr.span, fun_span); } }, hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => { - check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, None); - }, - hir::ExprKind::Block(block, _) - if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) => - { - if let Some(block_expr) = block.expr { - if let hir::ExprKind::MethodCall(..) = block_expr.kind { - check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, None); - } - } + check_general_case(cx, name, method_span, self_arg, arg, expr.span, None); }, _ => (), } diff --git a/clippy_lints/src/methods/manual_split_once.rs b/clippy_lints/src/methods/str_splitn.rs similarity index 60% rename from clippy_lints/src/methods/manual_split_once.rs rename to clippy_lints/src/methods/str_splitn.rs index 13eb72251bb..2595f734f11 100644 --- a/clippy_lints/src/methods/manual_split_once.rs +++ b/clippy_lints/src/methods/str_splitn.rs @@ -11,7 +11,13 @@ use rustc_span::{symbol::sym, Span, SyntaxContext}; use super::MANUAL_SPLIT_ONCE; -pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) { +pub(super) fn check_manual_split_once( + cx: &LateContext<'_>, + method_name: &str, + expr: &Expr<'_>, + self_arg: &Expr<'_>, + pat_arg: &Expr<'_>, +) { if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() { return; } @@ -36,7 +42,7 @@ pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, se format!("{}.{}({})", self_snip, method_name, pat_snip) }, IterUsageKind::RNextTuple => format!("{}.{}({}).map(|(x, y)| (y, x))", self_snip, method_name, pat_snip), - IterUsageKind::Next => { + IterUsageKind::Next | IterUsageKind::Second => { let self_deref = { let adjust = cx.typeck_results().expr_adjustments(self_arg); if adjust.is_empty() { @@ -51,26 +57,49 @@ pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, se "*".repeat(adjust.len() - 2) } }; - if usage.unwrap_kind.is_some() { - format!( - "{}.{}({}).map_or({}{}, |x| x.0)", - &self_snip, method_name, pat_snip, self_deref, &self_snip - ) + if matches!(usage.kind, IterUsageKind::Next) { + match usage.unwrap_kind { + Some(UnwrapKind::Unwrap) => { + if reverse { + format!("{}.{}({}).unwrap().0", self_snip, method_name, pat_snip) + } else { + format!( + "{}.{}({}).map_or({}{}, |x| x.0)", + self_snip, method_name, pat_snip, self_deref, &self_snip + ) + } + }, + Some(UnwrapKind::QuestionMark) => { + format!( + "{}.{}({}).map_or({}{}, |x| x.0)", + self_snip, method_name, pat_snip, self_deref, &self_snip + ) + }, + None => { + format!( + "Some({}.{}({}).map_or({}{}, |x| x.0))", + &self_snip, method_name, pat_snip, self_deref, &self_snip + ) + }, + } } else { - format!( - "Some({}.{}({}).map_or({}{}, |x| x.0))", - &self_snip, method_name, pat_snip, self_deref, &self_snip - ) + match usage.unwrap_kind { + Some(UnwrapKind::Unwrap) => { + if reverse { + // In this case, no better suggestion is offered. + return; + } + format!("{}.{}({}).unwrap().1", self_snip, method_name, pat_snip) + }, + Some(UnwrapKind::QuestionMark) => { + format!("{}.{}({})?.1", self_snip, method_name, pat_snip) + }, + None => { + format!("{}.{}({}).map(|x| x.1)", self_snip, method_name, pat_snip) + }, + } } }, - IterUsageKind::Second => { - let access_str = match usage.unwrap_kind { - Some(UnwrapKind::Unwrap) => ".unwrap().1", - Some(UnwrapKind::QuestionMark) => "?.1", - None => ".map(|x| x.1)", - }; - format!("{}.{}({}){}", self_snip, method_name, pat_snip, access_str) - }, }; span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app); @@ -209,3 +238,86 @@ fn parse_iter_usage( span, }) } + +use super::NEEDLESS_SPLITN; + +pub(super) fn check_needless_splitn( + cx: &LateContext<'_>, + method_name: &str, + expr: &Expr<'_>, + self_arg: &Expr<'_>, + pat_arg: &Expr<'_>, + count: u128, +) { + if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() { + return; + } + let ctxt = expr.span.ctxt(); + let mut app = Applicability::MachineApplicable; + let (reverse, message) = if method_name == "splitn" { + (false, "unnecessary use of `splitn`") + } else { + (true, "unnecessary use of `rsplitn`") + }; + if_chain! { + if count >= 2; + if check_iter(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), count); + then { + span_lint_and_sugg( + cx, + NEEDLESS_SPLITN, + expr.span, + message, + "try this", + format!( + "{}.{}({})", + snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0, + if reverse {"rsplit"} else {"split"}, + snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0 + ), + app, + ); + } + } +} + +fn check_iter( + cx: &LateContext<'tcx>, + ctxt: SyntaxContext, + mut iter: impl Iterator)>, + count: u128, +) -> bool { + match iter.next() { + Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => { + let (name, args) = if let ExprKind::MethodCall(name, _, [_, args @ ..], _) = e.kind { + (name, args) + } else { + return false; + }; + if_chain! { + if let Some(did) = cx.typeck_results().type_dependent_def_id(e.hir_id); + if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator); + then { + match (&*name.ident.as_str(), args) { + ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => { + return true; + }, + ("next_tuple", []) if count > 2 => { + return true; + }, + ("nth", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => { + if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) { + if count > idx + 1 { + return true; + } + } + }, + _ => return false, + } + } + } + }, + _ => return false, + }; + false +} diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs index 740af750b48..1e2765263c8 100644 --- a/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -30,7 +30,7 @@ pub(super) fn check<'tcx>( return; } - if eager_or_lazy::is_eagerness_candidate(cx, body_expr) { + if eager_or_lazy::switch_to_eager_eval(cx, body_expr) { let msg = if is_option { "unnecessary closure used to substitute value for `Option::None`" } else { diff --git a/clippy_lints/src/minmax.rs b/clippy_lints/src/minmax.rs index dc2dd45e4ed..a6450aec4f7 100644 --- a/clippy_lints/src/minmax.rs +++ b/clippy_lints/src/minmax.rs @@ -26,6 +26,7 @@ declare_clippy_lint! { /// ``` /// It will always be equal to `0`. Probably the author meant to clamp the value /// between 0 and 100, but has erroneously swapped `min` and `max`. + #[clippy::version = "pre 1.29.0"] pub MIN_MAX, correctness, "`min(_, max(_, _))` (or vice versa) with bounds clamping the result to a constant" diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index 0f32cd9164e..a93537104bf 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -56,6 +56,7 @@ declare_clippy_lint! { /// true /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub TOPLEVEL_REF_ARG, style, "an entire binding declared as `ref`, in a function argument or a `let` statement" @@ -79,6 +80,7 @@ declare_clippy_lint! { /// // Good /// 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" @@ -112,6 +114,7 @@ declare_clippy_lint! { /// 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" @@ -139,6 +142,7 @@ declare_clippy_lint! { /// # 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()`" @@ -162,6 +166,7 @@ declare_clippy_lint! { /// 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" @@ -187,6 +192,7 @@ declare_clippy_lint! { /// let y = _x + 1; // Here we are using `_x`, even though it has a leading /// // underscore. We should rename `_x` to `x` /// ``` + #[clippy::version = "pre 1.29.0"] pub USED_UNDERSCORE_BINDING, pedantic, "using a binding which is prefixed with an underscore" @@ -207,6 +213,7 @@ declare_clippy_lint! { /// ```rust,ignore /// f() && g(); // We should write `if f() { g(); }`. /// ``` + #[clippy::version = "pre 1.29.0"] pub SHORT_CIRCUIT_STATEMENT, complexity, "using a short circuit boolean condition as a statement" @@ -228,6 +235,7 @@ declare_clippy_lint! { /// // Good /// let a = std::ptr::null::(); /// ``` + #[clippy::version = "pre 1.29.0"] pub ZERO_PTR, style, "using `0 as *{const, mut} T`" @@ -259,6 +267,7 @@ declare_clippy_lint! { /// // 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" diff --git a/clippy_lints/src/misc_early/mod.rs b/clippy_lints/src/misc_early/mod.rs index 7c3f5f22ade..6e09e25109f 100644 --- a/clippy_lints/src/misc_early/mod.rs +++ b/clippy_lints/src/misc_early/mod.rs @@ -46,6 +46,7 @@ declare_clippy_lint! { /// Foo { .. } => {}, /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub UNNEEDED_FIELD_PATTERN, restriction, "struct fields bound to a wildcard instead of using `..`" @@ -67,6 +68,7 @@ declare_clippy_lint! { /// // Good /// fn bar(a: i32, _b: i32) {} /// ``` + #[clippy::version = "pre 1.29.0"] pub DUPLICATE_UNDERSCORE_ARGUMENT, style, "function arguments having names which only differ by an underscore" @@ -85,6 +87,7 @@ declare_clippy_lint! { /// let mut x = 3; /// --x; /// ``` + #[clippy::version = "pre 1.29.0"] pub DOUBLE_NEG, style, "`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++" @@ -106,6 +109,7 @@ declare_clippy_lint! { /// // Good /// let y = 0x1A9BACD; /// ``` + #[clippy::version = "pre 1.29.0"] pub MIXED_CASE_HEX_LITERALS, style, "hex literals whose letter digits are not consistently upper- or lowercased" @@ -129,6 +133,7 @@ declare_clippy_lint! { /// // Good /// let y = 123832_i32; /// ``` + #[clippy::version = "pre 1.29.0"] pub UNSEPARATED_LITERAL_SUFFIX, restriction, "literals whose suffix is not separated by an underscore" @@ -151,6 +156,7 @@ declare_clippy_lint! { /// // Good /// let y = 123832i32; /// ``` + #[clippy::version = "1.58.0"] pub SEPARATED_LITERAL_SUFFIX, restriction, "literals whose suffix is separated by an underscore" @@ -189,6 +195,7 @@ declare_clippy_lint! { /// ``` /// /// prints `83` (as `83 == 0o123` while `123 == 0o173`). + #[clippy::version = "pre 1.29.0"] pub ZERO_PREFIXED_LITERAL, complexity, "integer literals starting with `0`" @@ -210,6 +217,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub BUILTIN_TYPE_SHADOW, style, "shadowing a builtin type" @@ -239,6 +247,7 @@ declare_clippy_lint! { /// y => (), /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub REDUNDANT_PATTERN, style, "using `name @ _` in a pattern" @@ -273,6 +282,7 @@ declare_clippy_lint! { /// _ => (), /// } /// ``` + #[clippy::version = "1.40.0"] pub UNNEEDED_WILDCARD_PATTERN, complexity, "tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)" diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index 5b2584d43a1..a8d41050856 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -63,6 +63,7 @@ declare_clippy_lint! { /// } /// # } /// ``` + #[clippy::version = "1.34.0"] pub MISSING_CONST_FOR_FN, nursery, "Lint functions definitions that could be made `const fn`" diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index 564f021268c..db6aab0671b 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -7,7 +7,8 @@ use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::span_lint; -use rustc_ast::ast; +use if_chain::if_chain; +use rustc_ast::ast::{self, MetaItem, MetaItemKind}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty; @@ -26,6 +27,7 @@ declare_clippy_lint! { /// allowed-by-default lint for /// public members, but has no way to enforce documentation of private items. /// This lint fixes that. + #[clippy::version = "pre 1.29.0"] pub MISSING_DOCS_IN_PRIVATE_ITEMS, restriction, "detects missing documentation for public and private members" @@ -56,6 +58,20 @@ impl MissingDoc { *self.doc_hidden_stack.last().expect("empty doc_hidden_stack") } + fn has_include(meta: Option) -> bool { + if_chain! { + if let Some(meta) = meta; + if let MetaItemKind::List(list) = meta.kind; + if let Some(meta) = list.get(0); + if let Some(name) = meta.ident(); + then { + name.name == sym::include + } else { + false + } + } + } + fn check_missing_docs_attrs( &self, cx: &LateContext<'_>, @@ -79,7 +95,9 @@ impl MissingDoc { return; } - let has_doc = attrs.iter().any(|a| a.doc_str().is_some()); + let has_doc = attrs + .iter() + .any(|a| a.doc_str().is_some() || Self::has_include(a.meta())); if !has_doc { span_lint( cx, diff --git a/clippy_lints/src/missing_enforced_import_rename.rs b/clippy_lints/src/missing_enforced_import_rename.rs index 448bfc2fdd6..68d49e0f150 100644 --- a/clippy_lints/src/missing_enforced_import_rename.rs +++ b/clippy_lints/src/missing_enforced_import_rename.rs @@ -33,6 +33,7 @@ declare_clippy_lint! { /// ```rust,ignore /// use serde_json::Value as JsonValue; /// ``` + #[clippy::version = "1.55.0"] pub MISSING_ENFORCED_IMPORT_RENAMES, restriction, "enforce import renames" diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index b593c747498..ac2f16b49e3 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -52,6 +52,7 @@ declare_clippy_lint! { /// fn def_bar() {} // missing #[inline] /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub MISSING_INLINE_IN_PUBLIC_ITEMS, restriction, "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)" diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index d41b5474564..3b65f80cba2 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -32,6 +32,7 @@ declare_clippy_lint! { /// stuff.rs /// lib.rs /// ``` + #[clippy::version = "1.57.0"] pub MOD_MODULE_FILES, restriction, "checks that module layout is consistent" @@ -61,6 +62,7 @@ declare_clippy_lint! { /// lib.rs /// ``` + #[clippy::version = "1.57.0"] pub SELF_NAMED_MODULE_FILES, restriction, "checks that module layout is consistent" diff --git a/clippy_lints/src/modulo_arithmetic.rs b/clippy_lints/src/modulo_arithmetic.rs index f45e68233a1..d182a7d5249 100644 --- a/clippy_lints/src/modulo_arithmetic.rs +++ b/clippy_lints/src/modulo_arithmetic.rs @@ -24,6 +24,7 @@ declare_clippy_lint! { /// ```rust /// let x = -17 % 3; /// ``` + #[clippy::version = "1.42.0"] pub MODULO_ARITHMETIC, restriction, "any modulo arithmetic statement" diff --git a/clippy_lints/src/multiple_crate_versions.rs b/clippy_lints/src/multiple_crate_versions.rs index 816b2f275fb..e45cc86d417 100644 --- a/clippy_lints/src/multiple_crate_versions.rs +++ b/clippy_lints/src/multiple_crate_versions.rs @@ -33,6 +33,7 @@ declare_clippy_lint! { /// ctrlc = "=3.1.0" /// ansi_term = "=0.11.0" /// ``` + #[clippy::version = "pre 1.29.0"] pub MULTIPLE_CRATE_VERSIONS, cargo, "multiple versions of the same crate being used" diff --git a/clippy_lints/src/mut_key.rs b/clippy_lints/src/mut_key.rs index 8476257f086..5fe887a4573 100644 --- a/clippy_lints/src/mut_key.rs +++ b/clippy_lints/src/mut_key.rs @@ -72,6 +72,7 @@ declare_clippy_lint! { /// let _: HashSet = HashSet::new(); /// } /// ``` + #[clippy::version = "1.42.0"] pub MUTABLE_KEY_TYPE, suspicious, "Check for mutable `Map`/`Set` key type" diff --git a/clippy_lints/src/mut_mut.rs b/clippy_lints/src/mut_mut.rs index 7c4cac29ba8..bcbea8f1e66 100644 --- a/clippy_lints/src/mut_mut.rs +++ b/clippy_lints/src/mut_mut.rs @@ -22,6 +22,7 @@ declare_clippy_lint! { /// # let mut y = 1; /// let x = &mut &mut y; /// ``` + #[clippy::version = "pre 1.29.0"] pub MUT_MUT, pedantic, "usage of double-mut refs, e.g., `&mut &mut ...`" diff --git a/clippy_lints/src/mut_mutex_lock.rs b/clippy_lints/src/mut_mutex_lock.rs index b96fa4774cb..b1e6308d2e1 100644 --- a/clippy_lints/src/mut_mutex_lock.rs +++ b/clippy_lints/src/mut_mutex_lock.rs @@ -38,6 +38,7 @@ declare_clippy_lint! { /// let value = value_mutex.get_mut().unwrap(); /// *value += 1; /// ``` + #[clippy::version = "1.49.0"] pub MUT_MUTEX_LOCK, style, "`&mut Mutex::lock` does unnecessary locking" diff --git a/clippy_lints/src/mut_reference.rs b/clippy_lints/src/mut_reference.rs index 8d5d7951fc5..63a1cf7b7d5 100644 --- a/clippy_lints/src/mut_reference.rs +++ b/clippy_lints/src/mut_reference.rs @@ -23,6 +23,7 @@ declare_clippy_lint! { /// // Good /// my_vec.push(&value) /// ``` + #[clippy::version = "pre 1.29.0"] pub UNNECESSARY_MUT_PASSED, style, "an argument passed as a mutable reference although the callee only demands an immutable reference" diff --git a/clippy_lints/src/mutable_debug_assertion.rs b/clippy_lints/src/mutable_debug_assertion.rs index 9b44cf9d43a..fa7274990db 100644 --- a/clippy_lints/src/mutable_debug_assertion.rs +++ b/clippy_lints/src/mutable_debug_assertion.rs @@ -26,6 +26,7 @@ declare_clippy_lint! { /// fn take_a_mut_parameter(_: &mut u32) -> bool { unimplemented!() } /// debug_assert!(take_a_mut_parameter(&mut 5)); /// ``` + #[clippy::version = "1.40.0"] pub DEBUG_ASSERT_WITH_MUT_CALL, nursery, "mutable arguments in `debug_assert{,_ne,_eq}!`" @@ -87,10 +88,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> { self.found = true; return; }, - ExprKind::If(..) => { - self.found = true; - return; - }, ExprKind::Path(_) => { if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) { if adj diff --git a/clippy_lints/src/mutex_atomic.rs b/clippy_lints/src/mutex_atomic.rs index 5feddcbfc61..816377fe65e 100644 --- a/clippy_lints/src/mutex_atomic.rs +++ b/clippy_lints/src/mutex_atomic.rs @@ -36,6 +36,7 @@ declare_clippy_lint! { /// # use std::sync::atomic::AtomicBool; /// let x = AtomicBool::new(y); /// ``` + #[clippy::version = "pre 1.29.0"] pub MUTEX_ATOMIC, perf, "using a mutex where an atomic value could be used instead" @@ -64,6 +65,7 @@ declare_clippy_lint! { /// # use std::sync::atomic::AtomicUsize; /// let x = AtomicUsize::new(0usize); /// ``` + #[clippy::version = "pre 1.29.0"] pub MUTEX_INTEGER, nursery, "using a mutex for an integer type" diff --git a/clippy_lints/src/needless_arbitrary_self_type.rs b/clippy_lints/src/needless_arbitrary_self_type.rs index 9a3d9383cd9..9838d3cad9f 100644 --- a/clippy_lints/src/needless_arbitrary_self_type.rs +++ b/clippy_lints/src/needless_arbitrary_self_type.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::in_macro; use if_chain::if_chain; use rustc_ast::ast::{BindingMode, Lifetime, Mutability, Param, PatKind, Path, TyKind}; use rustc_errors::Applicability; @@ -53,6 +52,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.47.0"] pub NEEDLESS_ARBITRARY_SELF_TYPE, complexity, "type of `self` parameter is already by default `Self`" @@ -78,7 +78,7 @@ fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mod let self_param = match (binding_mode, mutbl) { (Mode::Ref(None), Mutability::Mut) => "&mut self".to_string(), (Mode::Ref(Some(lifetime)), Mutability::Mut) => { - if in_macro(lifetime.ident.span) { + if lifetime.ident.span.from_expansion() { applicability = Applicability::HasPlaceholders; "&'_ mut self".to_string() } else { @@ -87,7 +87,7 @@ fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mod }, (Mode::Ref(None), Mutability::Not) => "&self".to_string(), (Mode::Ref(Some(lifetime)), Mutability::Not) => { - if in_macro(lifetime.ident.span) { + if lifetime.ident.span.from_expansion() { applicability = Applicability::HasPlaceholders; "&'_ self".to_string() } else { @@ -114,7 +114,7 @@ fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mod impl EarlyLintPass for NeedlessArbitrarySelfType { fn check_param(&mut self, cx: &EarlyContext<'_>, p: &Param) { // Bail out if the parameter it's not a receiver or was not written by the user - if !p.is_self() || in_macro(p.span) { + if !p.is_self() || p.span.from_expansion() { return; } diff --git a/clippy_lints/src/needless_bitwise_bool.rs b/clippy_lints/src/needless_bitwise_bool.rs index 203da29cb91..a8a8d174a82 100644 --- a/clippy_lints/src/needless_bitwise_bool.rs +++ b/clippy_lints/src/needless_bitwise_bool.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::in_macro; use clippy_utils::source::snippet_opt; use if_chain::if_chain; use rustc_errors::Applicability; @@ -31,6 +30,7 @@ declare_clippy_lint! { /// 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" @@ -41,7 +41,7 @@ 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 !in_macro(expr.span); + 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; diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index 91944653500..3709f3948c2 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -41,6 +41,7 @@ declare_clippy_lint! { /// ```rust,ignore /// !x /// ``` + #[clippy::version = "pre 1.29.0"] pub NEEDLESS_BOOL, complexity, "if-statements with plain booleans in the then- and else-clause, e.g., `if p { true } else { false }`" @@ -65,6 +66,7 @@ declare_clippy_lint! { /// if x {} /// if !y {} /// ``` + #[clippy::version = "pre 1.29.0"] pub BOOL_COMPARISON, complexity, "comparing a variable to a boolean, e.g., `if x == true` or `if x != true`" diff --git a/clippy_lints/src/needless_borrow.rs b/clippy_lints/src/needless_borrow.rs index f1be90c44f9..6709fed91bd 100644 --- a/clippy_lints/src/needless_borrow.rs +++ b/clippy_lints/src/needless_borrow.rs @@ -4,7 +4,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{snippet_opt, snippet_with_applicability, snippet_with_context}; -use clippy_utils::{get_parent_expr, in_macro, path_to_local}; +use clippy_utils::{get_parent_expr, path_to_local}; use if_chain::if_chain; use rustc_ast::util::parser::PREC_POSTFIX; use rustc_data_structures::fx::FxIndexMap; @@ -37,6 +37,7 @@ declare_clippy_lint! { /// let x: &i32 = &5; /// fun(x); /// ``` + #[clippy::version = "pre 1.29.0"] pub NEEDLESS_BORROW, style, "taking a reference that is going to be automatically dereferenced" @@ -63,6 +64,7 @@ declare_clippy_lint! { /// // use `&x` here /// } /// ``` + #[clippy::version = "1.54.0"] pub REF_BINDING_TO_REFERENCE, pedantic, "`ref` binding to a reference" @@ -157,7 +159,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrow { if let Some(opt_prev_pat) = self.ref_locals.get_mut(&id) { // This binding id has been seen before. Add this pattern to the list of changes. if let Some(prev_pat) = opt_prev_pat { - if in_macro(pat.span) { + if pat.span.from_expansion() { // Doesn't match the context of the previous pattern. Can't lint here. *opt_prev_pat = None; } else { @@ -174,7 +176,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrow { } if_chain! { - if !in_macro(pat.span); + if !pat.span.from_expansion(); if let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind(); // only lint immutable refs, because borrowed `&mut T` cannot be moved out if let ty::Ref(_, _, Mutability::Not) = *tam.kind(); @@ -248,12 +250,12 @@ impl NeedlessBorrow { span, kind: ExprKind::Unary(UnOp::Deref, _), .. - }) if !in_macro(span) => { + }) if !span.from_expansion() => { // Remove explicit deref. let snip = snippet_with_context(cx, e.span, span.ctxt(), "..", &mut pat.app).0; pat.replacements.push((span, snip.into())); }, - Some(parent) if !in_macro(parent.span) => { + Some(parent) if !parent.span.from_expansion() => { // Double reference might be needed at this point. if parent.precedence().order() == PREC_POSTFIX { // Parentheses would be needed here, don't lint. @@ -264,7 +266,7 @@ impl NeedlessBorrow { pat.replacements.push((e.span, format!("&{}", snip))); } }, - _ if !in_macro(e.span) => { + _ if !e.span.from_expansion() => { // Double reference might be needed at this point. pat.always_deref = false; let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app); diff --git a/clippy_lints/src/needless_borrowed_ref.rs b/clippy_lints/src/needless_borrowed_ref.rs index 36879eda7c0..0fcc419e722 100644 --- a/clippy_lints/src/needless_borrowed_ref.rs +++ b/clippy_lints/src/needless_borrowed_ref.rs @@ -38,6 +38,7 @@ declare_clippy_lint! { /// let mut v = Vec::::new(); /// let _ = v.iter_mut().filter(|a| a.is_empty()); /// ``` + #[clippy::version = "pre 1.29.0"] pub NEEDLESS_BORROWED_REFERENCE, complexity, "destructuring a reference and borrowing the inner value" diff --git a/clippy_lints/src/needless_continue.rs b/clippy_lints/src/needless_continue.rs index 7aa93ed7839..98a3bce1ff3 100644 --- a/clippy_lints/src/needless_continue.rs +++ b/clippy_lints/src/needless_continue.rs @@ -110,6 +110,7 @@ declare_clippy_lint! { /// # break; /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub NEEDLESS_CONTINUE, pedantic, "`continue` statements that can be replaced by a rearrangement of code" diff --git a/clippy_lints/src/needless_for_each.rs b/clippy_lints/src/needless_for_each.rs index 9a6ddc72ce5..0c1da035173 100644 --- a/clippy_lints/src/needless_for_each.rs +++ b/clippy_lints/src/needless_for_each.rs @@ -40,6 +40,7 @@ declare_clippy_lint! { /// println!("{}", elem); /// } /// ``` + #[clippy::version = "1.53.0"] pub NEEDLESS_FOR_EACH, pedantic, "using `for_each` where a `for` loop would be simpler" diff --git a/clippy_lints/src/needless_option_as_deref.rs b/clippy_lints/src/needless_option_as_deref.rs index fbdaaf51f74..a28b08c33ec 100644 --- a/clippy_lints/src/needless_option_as_deref.rs +++ b/clippy_lints/src/needless_option_as_deref.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::in_macro; use clippy_utils::source::snippet_opt; use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; @@ -27,6 +26,7 @@ declare_clippy_lint! { /// let a = Some(&1); /// let b = a; /// ``` + #[clippy::version = "1.57.0"] pub NEEDLESS_OPTION_AS_DEREF, complexity, "no-op use of `deref` or `deref_mut` method to `Option`." @@ -38,7 +38,7 @@ declare_lint_pass!(OptionNeedlessDeref=> [ impl<'tcx> LateLintPass<'tcx> for OptionNeedlessDeref { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if expr.span.from_expansion() || in_macro(expr.span) { + if expr.span.from_expansion() { return; } let typeck = cx.typeck_results(); diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 352dc6f8bec..35877d51c0c 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -51,6 +51,7 @@ declare_clippy_lint! { /// assert_eq!(v.len(), 42); /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub NEEDLESS_PASS_BY_VALUE, pedantic, "functions taking arguments by value, but not consuming them in its body" diff --git a/clippy_lints/src/needless_question_mark.rs b/clippy_lints/src/needless_question_mark.rs index 42e48336e15..3ca933ea8fb 100644 --- a/clippy_lints/src/needless_question_mark.rs +++ b/clippy_lints/src/needless_question_mark.rs @@ -53,6 +53,7 @@ declare_clippy_lint! { /// tr.and_then(|t| t.magic) /// } /// ``` + #[clippy::version = "1.51.0"] pub NEEDLESS_QUESTION_MARK, complexity, "Suggest `value.inner_option` instead of `Some(value.inner_option?)`. The same goes for `Result`." diff --git a/clippy_lints/src/needless_update.rs b/clippy_lints/src/needless_update.rs index 2a33b7392ca..ed315efaa2f 100644 --- a/clippy_lints/src/needless_update.rs +++ b/clippy_lints/src/needless_update.rs @@ -40,6 +40,7 @@ declare_clippy_lint! { /// ..zero_point /// }; /// ``` + #[clippy::version = "pre 1.29.0"] pub NEEDLESS_UPDATE, complexity, "using `Foo { ..base }` when there are no missing fields" diff --git a/clippy_lints/src/neg_cmp_op_on_partial_ord.rs b/clippy_lints/src/neg_cmp_op_on_partial_ord.rs index 6ad49b70605..efe31a15441 100644 --- a/clippy_lints/src/neg_cmp_op_on_partial_ord.rs +++ b/clippy_lints/src/neg_cmp_op_on_partial_ord.rs @@ -36,6 +36,7 @@ declare_clippy_lint! { /// _ => false, /// }; /// ``` + #[clippy::version = "pre 1.29.0"] pub NEG_CMP_OP_ON_PARTIAL_ORD, complexity, "The use of negated comparison operators on partially ordered types may produce confusing code." diff --git a/clippy_lints/src/neg_multiply.rs b/clippy_lints/src/neg_multiply.rs index 1b15d29439f..cb67fab1740 100644 --- a/clippy_lints/src/neg_multiply.rs +++ b/clippy_lints/src/neg_multiply.rs @@ -20,6 +20,7 @@ declare_clippy_lint! { /// ```ignore /// x * -1 /// ``` + #[clippy::version = "pre 1.29.0"] pub NEG_MULTIPLY, style, "multiplying integers with `-1`" diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index 0ac27f1cba2..f0c0c89ca8f 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -45,6 +45,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub NEW_WITHOUT_DEFAULT, style, "`fn new() -> Self` method without `Default` implementation" diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 6dae8f32043..cd54de3ee4b 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -22,6 +22,7 @@ declare_clippy_lint! { /// ```rust /// 0; /// ``` + #[clippy::version = "pre 1.29.0"] pub NO_EFFECT, complexity, "statements with no effect" @@ -44,6 +45,7 @@ declare_clippy_lint! { /// ```rust,ignore /// let _i_serve_no_purpose = 1; /// ``` + #[clippy::version = "1.58.0"] pub NO_EFFECT_UNDERSCORE_BINDING, pedantic, "binding to `_` prefixed variable with no side-effect" @@ -62,6 +64,7 @@ declare_clippy_lint! { /// ```rust,ignore /// compute_array()[0]; /// ``` + #[clippy::version = "pre 1.29.0"] pub UNNECESSARY_OPERATION, complexity, "outer expressions with no effect" @@ -130,9 +133,12 @@ fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { }, ExprKind::Call(callee, args) => { if let ExprKind::Path(ref qpath) = callee.kind { - let res = cx.qpath_res(qpath, callee.hir_id); + if cx.typeck_results().type_dependent_def(expr.hir_id).is_some() { + // type-dependent function call like `impl FnOnce for X` + return false; + } let def_matched = matches!( - res, + cx.qpath_res(qpath, callee.hir_id), Res::Def(DefKind::Struct | DefKind::Variant | DefKind::Ctor(..), ..) ); if def_matched || is_range_literal(expr) { @@ -235,6 +241,10 @@ fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option { if let ExprKind::Path(ref qpath) = callee.kind { + if cx.typeck_results().type_dependent_def(expr.hir_id).is_some() { + // type-dependent function call like `impl FnOnce for X` + return None; + } let res = cx.qpath_res(qpath, callee.hir_id); match res { Res::Def(DefKind::Struct | DefKind::Variant | DefKind::Ctor(..), ..) diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 2a85a67fa09..df82775d507 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -69,6 +69,7 @@ declare_clippy_lint! { /// STATIC_ATOM.store(9, SeqCst); /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance /// ``` + #[clippy::version = "pre 1.29.0"] pub DECLARE_INTERIOR_MUTABLE_CONST, style, "declaring `const` with interior mutability" @@ -113,6 +114,7 @@ declare_clippy_lint! { /// STATIC_ATOM.store(9, SeqCst); /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance /// ``` + #[clippy::version = "pre 1.29.0"] pub BORROW_INTERIOR_MUTABLE_CONST, style, "referencing `const` with interior mutability" diff --git a/clippy_lints/src/non_expressive_names.rs b/clippy_lints/src/non_expressive_names.rs index e28cc49bf2a..5559fac0a8a 100644 --- a/clippy_lints/src/non_expressive_names.rs +++ b/clippy_lints/src/non_expressive_names.rs @@ -24,6 +24,7 @@ declare_clippy_lint! { /// let checked_exp = something; /// let checked_expr = something_else; /// ``` + #[clippy::version = "pre 1.29.0"] pub SIMILAR_NAMES, pedantic, "similarly named items and bindings" @@ -42,6 +43,7 @@ declare_clippy_lint! { /// ```ignore /// let (a, b, c, d, e, f, g) = (...); /// ``` + #[clippy::version = "pre 1.29.0"] pub MANY_SINGLE_CHAR_NAMES, pedantic, "too many single character bindings" @@ -62,6 +64,7 @@ declare_clippy_lint! { /// let ___1 = 1; /// let __1___2 = 11; /// ``` + #[clippy::version = "pre 1.29.0"] pub JUST_UNDERSCORES_AND_DIGITS, style, "unclear name" @@ -357,7 +360,12 @@ impl EarlyLintPass for NonExpressiveNames { return; } - if let ItemKind::Fn(box ast::Fn { ref sig, body: Some(ref blk), .. }) = item.kind { + if let ItemKind::Fn(box ast::Fn { + ref sig, + body: Some(ref blk), + .. + }) = item.kind + { do_check(self, cx, &item.attrs, &sig.decl, blk); } } @@ -367,7 +375,12 @@ impl EarlyLintPass for NonExpressiveNames { return; } - if let AssocItemKind::Fn(box ast::Fn { ref sig, body: Some(ref blk), .. }) = item.kind { + if let AssocItemKind::Fn(box ast::Fn { + ref sig, + body: Some(ref blk), + .. + }) = item.kind + { do_check(self, cx, &item.attrs, &sig.decl, blk); } } diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 3b74f69d375..4b57dbc4c41 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -32,6 +32,7 @@ declare_clippy_lint! { /// let mut options = OpenOptions::new(); /// options.mode(0o644); /// ``` + #[clippy::version = "1.53.0"] pub NON_OCTAL_UNIX_PERMISSIONS, correctness, "use of non-octal value to set unix file permissions, which will be translated into octal" diff --git a/clippy_lints/src/non_send_fields_in_send_ty.rs b/clippy_lints/src/non_send_fields_in_send_ty.rs index 7ebf84d400f..9c07488bfe6 100644 --- a/clippy_lints/src/non_send_fields_in_send_ty.rs +++ b/clippy_lints/src/non_send_fields_in_send_ty.rs @@ -43,6 +43,7 @@ declare_clippy_lint! { /// ``` /// Use thread-safe types like [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) /// or specify correct bounds on generic type parameters (`T: Send`). + #[clippy::version = "1.57.0"] pub NON_SEND_FIELDS_IN_SEND_TY, suspicious, "there is field that does not implement `Send` in a `Send` struct" diff --git a/clippy_lints/src/nonstandard_macro_braces.rs b/clippy_lints/src/nonstandard_macro_braces.rs index ca660a9250d..3dcc9e26c9e 100644 --- a/clippy_lints/src/nonstandard_macro_braces.rs +++ b/clippy_lints/src/nonstandard_macro_braces.rs @@ -3,14 +3,16 @@ use std::{ hash::{Hash, Hasher}, }; -use clippy_utils::{diagnostics::span_lint_and_help, in_macro, is_direct_expn_of, source::snippet_opt}; +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::source::snippet_opt; use if_chain::if_chain; use rustc_ast::ast; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def_id::DefId; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::Span; +use rustc_span::hygiene::{ExpnKind, MacroKind}; +use rustc_span::{Span, Symbol}; use serde::{de, Deserialize}; declare_clippy_lint! { @@ -29,6 +31,7 @@ declare_clippy_lint! { /// ```rust /// vec![1, 2, 3]; /// ``` + #[clippy::version = "1.55.0"] pub NONSTANDARD_MACRO_BRACES, nursery, "check consistent use of braces in macro" @@ -37,7 +40,7 @@ declare_clippy_lint! { const BRACES: &[(&str, &str)] = &[("(", ")"), ("{", "}"), ("[", "]")]; /// The (name, (open brace, close brace), source snippet) -type MacroInfo<'a> = (&'a str, &'a (String, String), String); +type MacroInfo<'a> = (Symbol, &'a (String, String), String); #[derive(Clone, Debug, Default)] pub struct MacroBraces { @@ -93,17 +96,16 @@ impl EarlyLintPass for MacroBraces { fn is_offending_macro<'a>(cx: &EarlyContext<'_>, span: Span, mac_braces: &'a MacroBraces) -> Option> { let unnested_or_local = || { - let nested = in_macro(span.ctxt().outer_expn_data().call_site); - !nested + !span.ctxt().outer_expn_data().call_site.from_expansion() || span .macro_backtrace() .last() .map_or(false, |e| e.macro_def_id.map_or(false, DefId::is_local)) }; if_chain! { - // Make sure we are only one level deep otherwise there are to many FP's - if in_macro(span); - if let Some((name, braces)) = find_matching_macro(span, &mac_braces.macro_braces); + if let ExpnKind::Macro(MacroKind::Bang, mac_name) = span.ctxt().outer_expn_data().kind; + let name = &*mac_name.as_str(); + if let Some(braces) = mac_braces.macro_braces.get(name); if let Some(snip) = snippet_opt(cx, span.ctxt().outer_expn_data().call_site); // we must check only invocation sites // https://github.com/rust-lang/rust-clippy/issues/7422 @@ -114,14 +116,14 @@ fn is_offending_macro<'a>(cx: &EarlyContext<'_>, span: Span, mac_braces: &'a Mac if !c.starts_with(&format!("{}!{}", name, braces.0)); if !mac_braces.done.contains(&span.ctxt().outer_expn_data().call_site); then { - Some((name, braces, snip)) + Some((mac_name, braces, snip)) } else { None } } } -fn emit_help(cx: &EarlyContext<'_>, snip: String, braces: &(String, String), name: &str, span: Span) { +fn emit_help(cx: &EarlyContext<'_>, snip: String, braces: &(String, String), name: Symbol, span: Span) { let with_space = &format!("! {}", braces.0); let without_space = &format!("!{}", braces.0); let mut help = snip; @@ -144,15 +146,6 @@ fn emit_help(cx: &EarlyContext<'_>, snip: String, braces: &(String, String), nam ); } -fn find_matching_macro( - span: Span, - braces: &FxHashMap, -) -> Option<(&String, &(String, String))> { - braces - .iter() - .find(|(macro_name, _)| is_direct_expn_of(span, macro_name).is_some()) -} - fn macro_braces(conf: FxHashSet) -> FxHashMap { let mut braces = vec![ macro_matcher!( diff --git a/clippy_lints/src/open_options.rs b/clippy_lints/src/open_options.rs index 5752342cf62..2c77100bdcf 100644 --- a/clippy_lints/src/open_options.rs +++ b/clippy_lints/src/open_options.rs @@ -22,6 +22,7 @@ declare_clippy_lint! { /// /// OpenOptions::new().read(true).truncate(true); /// ``` + #[clippy::version = "pre 1.29.0"] pub NONSENSICAL_OPEN_OPTIONS, correctness, "nonsensical combination of options for opening a file" diff --git a/clippy_lints/src/option_env_unwrap.rs b/clippy_lints/src/option_env_unwrap.rs index d7306628030..3f5286ba097 100644 --- a/clippy_lints/src/option_env_unwrap.rs +++ b/clippy_lints/src/option_env_unwrap.rs @@ -26,6 +26,7 @@ declare_clippy_lint! { /// ```rust,no_run /// let _ = env!("HOME"); /// ``` + #[clippy::version = "1.43.0"] pub OPTION_ENV_UNWRAP, correctness, "using `option_env!(...).unwrap()` to get environment variable" diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index cbe1c5d44d5..262be17f617 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -3,7 +3,7 @@ use clippy_utils::higher; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{ - can_move_expr_to_closure, eager_or_lazy, in_constant, in_macro, is_else_clause, is_lang_ctor, peel_hir_expr_while, + can_move_expr_to_closure, eager_or_lazy, in_constant, is_else_clause, is_lang_ctor, peel_hir_expr_while, CaptureKind, }; use if_chain::if_chain; @@ -59,6 +59,7 @@ declare_clippy_lint! { /// y*y /// }, |foo| foo); /// ``` + #[clippy::version = "1.47.0"] pub OPTION_IF_LET_ELSE, nursery, "reimplementation of Option::map_or" @@ -110,7 +111,7 @@ fn extract_body_from_expr<'a>(expr: &'a Expr<'a>) -> Option<&'a Expr<'a>> { fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: bool, as_mut: bool) -> String { format!( "{}{}", - Sugg::hir(cx, cond_expr, "..").maybe_par(), + Sugg::hir_with_macro_callsite(cx, cond_expr, "..").maybe_par(), if as_mut { ".as_mut()" } else if as_ref { @@ -126,7 +127,7 @@ fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: boo /// this construct is found, or None if this construct is not found. fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option { if_chain! { - if !in_macro(expr.span); // Don't lint macros, because it behaves weirdly + if !expr.span.from_expansion(); // Don't lint macros, because it behaves weirdly if !in_constant(cx, expr.hir_id); if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) }) = higher::IfLet::hir(cx, expr); @@ -146,11 +147,7 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> let capture_mut = if bind_annotation == &BindingAnnotation::Mutable { "mut " } else { "" }; let some_body = extract_body_from_expr(if_then)?; let none_body = extract_body_from_expr(if_else)?; - let method_sugg = if eager_or_lazy::is_eagerness_candidate(cx, none_body) { - "map_or" - } else { - "map_or_else" - }; + let method_sugg = if eager_or_lazy::switch_to_eager_eval(cx, none_body) { "map_or" } else { "map_or_else" }; let capture_name = id.name.to_ident_string(); let (as_ref, as_mut) = match &let_expr.kind { ExprKind::AddrOf(_, Mutability::Not, _) => (true, false), @@ -183,8 +180,8 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Some(OptionIfLetElseOccurence { option: format_option_in_sugg(cx, cond_expr, as_ref, as_mut), method_sugg: method_sugg.to_string(), - some_expr: format!("|{}{}| {}", capture_mut, capture_name, Sugg::hir(cx, some_body, "..")), - none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "|| " }, Sugg::hir(cx, none_body, "..")), + some_expr: format!("|{}{}| {}", capture_mut, capture_name, Sugg::hir_with_macro_callsite(cx, some_body, "..")), + none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "|| " }, Sugg::hir_with_macro_callsite(cx, none_body, "..")), }) } else { None diff --git a/clippy_lints/src/overflow_check_conditional.rs b/clippy_lints/src/overflow_check_conditional.rs index 0f9e5ada3a8..6dabbd48031 100644 --- a/clippy_lints/src/overflow_check_conditional.rs +++ b/clippy_lints/src/overflow_check_conditional.rs @@ -19,6 +19,7 @@ declare_clippy_lint! { /// # let b = 2; /// a + b < a; /// ``` + #[clippy::version = "pre 1.29.0"] pub OVERFLOW_CHECK_CONDITIONAL, complexity, "overflow checks inspired by C which are likely to panic" diff --git a/clippy_lints/src/panic_in_result_fn.rs b/clippy_lints/src/panic_in_result_fn.rs index 583c42b6563..8769c045214 100644 --- a/clippy_lints/src/panic_in_result_fn.rs +++ b/clippy_lints/src/panic_in_result_fn.rs @@ -30,6 +30,7 @@ declare_clippy_lint! { /// Err(String::from("error")) /// } /// ``` + #[clippy::version = "1.48.0"] pub PANIC_IN_RESULT_FN, restriction, "functions of type `Result<..>` that contain `panic!()`, `todo!()`, `unreachable()`, `unimplemented()` or assertion" diff --git a/clippy_lints/src/panic_unimplemented.rs b/clippy_lints/src/panic_unimplemented.rs index d8d9081d6f1..edfac824ded 100644 --- a/clippy_lints/src/panic_unimplemented.rs +++ b/clippy_lints/src/panic_unimplemented.rs @@ -17,6 +17,7 @@ declare_clippy_lint! { /// ```no_run /// panic!("even with a good reason"); /// ``` + #[clippy::version = "1.40.0"] pub PANIC, restriction, "usage of the `panic!` macro" @@ -33,6 +34,7 @@ declare_clippy_lint! { /// ```no_run /// unimplemented!(); /// ``` + #[clippy::version = "pre 1.29.0"] pub UNIMPLEMENTED, restriction, "`unimplemented!` should not be present in production code" @@ -49,6 +51,7 @@ declare_clippy_lint! { /// ```no_run /// todo!(); /// ``` + #[clippy::version = "1.40.0"] pub TODO, restriction, "`todo!` should not be present in production code" @@ -65,6 +68,7 @@ declare_clippy_lint! { /// ```no_run /// unreachable!(); /// ``` + #[clippy::version = "1.40.0"] pub UNREACHABLE, restriction, "usage of the `unreachable!` macro" diff --git a/clippy_lints/src/partialeq_ne_impl.rs b/clippy_lints/src/partialeq_ne_impl.rs index 4ec493e5f45..e827cdaae87 100644 --- a/clippy_lints/src/partialeq_ne_impl.rs +++ b/clippy_lints/src/partialeq_ne_impl.rs @@ -25,6 +25,7 @@ declare_clippy_lint! { /// fn ne(&self, other: &Foo) -> bool { !(self == other) } /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub PARTIALEQ_NE_IMPL, complexity, "re-implementing `PartialEq::ne`" diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index 6229b9608b3..3092ab8392a 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -65,6 +65,7 @@ declare_clippy_lint! { /// // Better /// fn foo(v: u32) {} /// ``` + #[clippy::version = "pre 1.29.0"] pub TRIVIALLY_COPY_PASS_BY_REF, pedantic, "functions taking small copyable arguments by reference" @@ -98,6 +99,7 @@ declare_clippy_lint! { /// // Good /// fn foo(v: &TooLarge) {} /// ``` + #[clippy::version = "1.49.0"] pub LARGE_TYPES_PASSED_BY_VALUE, pedantic, "functions taking large arguments by value" diff --git a/clippy_lints/src/path_buf_push_overwrite.rs b/clippy_lints/src/path_buf_push_overwrite.rs index 3df7a72d295..8ebee9bd04d 100644 --- a/clippy_lints/src/path_buf_push_overwrite.rs +++ b/clippy_lints/src/path_buf_push_overwrite.rs @@ -35,6 +35,7 @@ declare_clippy_lint! { /// x.push("bar"); /// assert_eq!(x, PathBuf::from("/foo/bar")); /// ``` + #[clippy::version = "1.36.0"] pub PATH_BUF_PUSH_OVERWRITE, nursery, "calling `push` with file system root on `PathBuf` can overwrite it" diff --git a/clippy_lints/src/pattern_type_mismatch.rs b/clippy_lints/src/pattern_type_mismatch.rs index e7bc2446590..3e7eef4edac 100644 --- a/clippy_lints/src/pattern_type_mismatch.rs +++ b/clippy_lints/src/pattern_type_mismatch.rs @@ -77,6 +77,7 @@ declare_clippy_lint! { /// *a += b; /// } /// ``` + #[clippy::version = "1.47.0"] pub PATTERN_TYPE_MISMATCH, restriction, "type of pattern does not match the expression type" diff --git a/clippy_lints/src/precedence.rs b/clippy_lints/src/precedence.rs index 1a8da00d9d6..cc0533c9f5d 100644 --- a/clippy_lints/src/precedence.rs +++ b/clippy_lints/src/precedence.rs @@ -42,6 +42,7 @@ declare_clippy_lint! { /// ### Example /// * `1 << 2 + 3` equals 32, while `(1 << 2) + 3` equals 7 /// * `-1i32.abs()` equals -1, while `(-1i32).abs()` equals 1 + #[clippy::version = "pre 1.29.0"] pub PRECEDENCE, complexity, "operations where precedence may be unclear" diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 8a36e20fc97..c08a19d520b 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -70,6 +70,7 @@ declare_clippy_lint! { /// // Good /// fn foo(&[u32]) { .. } /// ``` + #[clippy::version = "pre 1.29.0"] pub PTR_ARG, style, "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively" @@ -96,6 +97,7 @@ declare_clippy_lint! { /// .. /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub CMP_NULL, style, "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead" @@ -121,6 +123,7 @@ declare_clippy_lint! { /// ```ignore /// fn foo(&Foo) -> &mut Bar { .. } /// ``` + #[clippy::version = "pre 1.29.0"] pub MUT_FROM_REF, correctness, "fns that create mutable refs from immutable ref args" @@ -143,6 +146,7 @@ declare_clippy_lint! { /// // Good /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); } /// ``` + #[clippy::version = "1.53.0"] pub INVALID_NULL_PTR_USAGE, correctness, "invalid usage of a null pointer, suggesting `NonNull::dangling()` instead" diff --git a/clippy_lints/src/ptr_eq.rs b/clippy_lints/src/ptr_eq.rs index 3258c9fb3fe..3c126fc1ca6 100644 --- a/clippy_lints/src/ptr_eq.rs +++ b/clippy_lints/src/ptr_eq.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::in_macro; use clippy_utils::source::snippet_opt; use if_chain::if_chain; use rustc_errors::Applicability; @@ -30,6 +29,7 @@ declare_clippy_lint! { /// /// assert!(std::ptr::eq(a, b)); /// ``` + #[clippy::version = "1.49.0"] pub PTR_EQ, style, "use `std::ptr::eq` when comparing raw pointers" @@ -41,7 +41,7 @@ static LINT_MSG: &str = "use `std::ptr::eq` when comparing raw pointers"; impl LateLintPass<'_> for PtrEq { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if in_macro(expr.span) { + if expr.span.from_expansion() { return; } diff --git a/clippy_lints/src/ptr_offset_with_cast.rs b/clippy_lints/src/ptr_offset_with_cast.rs index cfb5287c667..964564b5794 100644 --- a/clippy_lints/src/ptr_offset_with_cast.rs +++ b/clippy_lints/src/ptr_offset_with_cast.rs @@ -38,6 +38,7 @@ declare_clippy_lint! { /// ptr.add(offset); /// } /// ``` + #[clippy::version = "1.30.0"] pub PTR_OFFSET_WITH_CAST, complexity, "unneeded pointer offset cast" diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index f63ef163bcb..a5531993ee6 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -32,6 +32,7 @@ declare_clippy_lint! { /// ```ignore /// option?; /// ``` + #[clippy::version = "pre 1.29.0"] pub QUESTION_MARK, style, "checks for expressions that could be replaced by the question mark operator" diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 4fa361fedaf..09c64485bbb 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -35,6 +35,7 @@ declare_clippy_lint! { /// # let x = vec![1]; /// x.iter().enumerate(); /// ``` + #[clippy::version = "pre 1.29.0"] pub RANGE_ZIP_WITH_LEN, complexity, "zipping iterator with a range when `enumerate()` would do" @@ -72,6 +73,7 @@ declare_clippy_lint! { /// ```rust,ignore /// for x..=y { .. } /// ``` + #[clippy::version = "pre 1.29.0"] pub RANGE_PLUS_ONE, pedantic, "`x..(y+1)` reads better as `x..=y`" @@ -100,6 +102,7 @@ declare_clippy_lint! { /// ```rust,ignore /// for x..y { .. } /// ``` + #[clippy::version = "pre 1.29.0"] pub RANGE_MINUS_ONE, pedantic, "`x..=(y-1)` reads better as `x..y`" @@ -132,6 +135,7 @@ declare_clippy_lint! { /// let sub = &arr[1..3]; /// } /// ``` + #[clippy::version = "1.45.0"] pub REVERSED_EMPTY_RANGES, correctness, "reversing the limits of range expressions, resulting in empty ranges" @@ -158,6 +162,7 @@ declare_clippy_lint! { ///# let x = 6; /// assert!((3..8).contains(&x)); /// ``` + #[clippy::version = "1.49.0"] pub MANUAL_RANGE_CONTAINS, style, "manually reimplementing {`Range`, `RangeInclusive`}`::contains`" @@ -337,7 +342,7 @@ fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args: // `.iter()` and `.len()` called on same `Path` if let ExprKind::Path(QPath::Resolved(_, iter_path)) = iter_args[0].kind; if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind; - if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments); + if SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments); then { span_lint(cx, RANGE_ZIP_WITH_LEN, diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index f7711b6fe94..1a2c86a7686 100644 --- a/clippy_lints/src/redundant_clone.rs +++ b/clippy_lints/src/redundant_clone.rs @@ -62,6 +62,7 @@ declare_clippy_lint! { /// /// Path::new("/a/b").join("c").to_path_buf(); /// ``` + #[clippy::version = "1.32.0"] pub REDUNDANT_CLONE, perf, "`clone()` of an owned value that is going to be dropped immediately" diff --git a/clippy_lints/src/redundant_closure_call.rs b/clippy_lints/src/redundant_closure_call.rs index 90e3c3f4b3e..0de282542fc 100644 --- a/clippy_lints/src/redundant_closure_call.rs +++ b/clippy_lints/src/redundant_closure_call.rs @@ -30,6 +30,7 @@ declare_clippy_lint! { /// // Good /// let a = 42 /// ``` + #[clippy::version = "pre 1.29.0"] pub REDUNDANT_CLOSURE_CALL, complexity, "throwaway closures called in the expression they are defined" diff --git a/clippy_lints/src/redundant_else.rs b/clippy_lints/src/redundant_else.rs index 68b256d2944..93dbe936d58 100644 --- a/clippy_lints/src/redundant_else.rs +++ b/clippy_lints/src/redundant_else.rs @@ -36,6 +36,7 @@ declare_clippy_lint! { /// print!("Moving on..."); /// } /// ``` + #[clippy::version = "1.50.0"] pub REDUNDANT_ELSE, pedantic, "`else` branch that can be removed without changing semantics" diff --git a/clippy_lints/src/redundant_field_names.rs b/clippy_lints/src/redundant_field_names.rs index 47df4917510..0dea4a784b2 100644 --- a/clippy_lints/src/redundant_field_names.rs +++ b/clippy_lints/src/redundant_field_names.rs @@ -30,6 +30,7 @@ declare_clippy_lint! { /// ```ignore /// let foo = Foo { bar }; /// ``` + #[clippy::version = "pre 1.29.0"] pub REDUNDANT_FIELD_NAMES, style, "checks for fields in struct literals where shorthands could be used" diff --git a/clippy_lints/src/redundant_pub_crate.rs b/clippy_lints/src/redundant_pub_crate.rs index 919d4e11e5a..2cee3c14d7f 100644 --- a/clippy_lints/src/redundant_pub_crate.rs +++ b/clippy_lints/src/redundant_pub_crate.rs @@ -26,6 +26,7 @@ declare_clippy_lint! { /// pub fn internal_fn() { } /// } /// ``` + #[clippy::version = "1.44.0"] pub REDUNDANT_PUB_CRATE, nursery, "Using `pub(crate)` visibility on items that are not crate visible due to the visibility of the module that contains them." diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index 290348c4509..b2bd0103d11 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::get_parent_expr; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::is_type_lang_item; -use clippy_utils::{get_parent_expr, in_macro}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability}; @@ -34,6 +34,7 @@ declare_clippy_lint! { /// x /// } /// ``` + #[clippy::version = "1.51.0"] pub REDUNDANT_SLICING, complexity, "redundant slicing of the whole range of a type" @@ -43,7 +44,7 @@ declare_lint_pass!(RedundantSlicing => [REDUNDANT_SLICING]); impl LateLintPass<'_> for RedundantSlicing { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if in_macro(expr.span) { + if expr.span.from_expansion() { return; } diff --git a/clippy_lints/src/redundant_static_lifetimes.rs b/clippy_lints/src/redundant_static_lifetimes.rs index d5a1a61da6b..ea5064217ab 100644 --- a/clippy_lints/src/redundant_static_lifetimes.rs +++ b/clippy_lints/src/redundant_static_lifetimes.rs @@ -27,6 +27,7 @@ declare_clippy_lint! { /// const FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...] /// static FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...] /// ``` + #[clippy::version = "1.37.0"] pub REDUNDANT_STATIC_LIFETIMES, style, "Using explicit `'static` lifetime for constants or statics when elision rules would allow omitting them." diff --git a/clippy_lints/src/ref_option_ref.rs b/clippy_lints/src/ref_option_ref.rs index d543832e314..909d6971a54 100644 --- a/clippy_lints/src/ref_option_ref.rs +++ b/clippy_lints/src/ref_option_ref.rs @@ -28,6 +28,7 @@ declare_clippy_lint! { /// ```rust,ignore /// let x: Option<&u32> = Some(&0u32); /// ``` + #[clippy::version = "1.49.0"] pub REF_OPTION_REF, pedantic, "use `Option<&T>` instead of `&Option<&T>`" diff --git a/clippy_lints/src/reference.rs b/clippy_lints/src/reference.rs index 77b6e60d893..22ae7a291d0 100644 --- a/clippy_lints/src/reference.rs +++ b/clippy_lints/src/reference.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::in_macro; use clippy_utils::source::{snippet_opt, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use if_chain::if_chain; @@ -31,6 +30,7 @@ declare_clippy_lint! { /// let a = f(b); /// let c = d; /// ``` + #[clippy::version = "pre 1.29.0"] pub DEREF_ADDROF, complexity, "use of `*&` or `*&mut` in an expression" @@ -50,7 +50,7 @@ impl EarlyLintPass for DerefAddrOf { if_chain! { if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind; if let ExprKind::AddrOf(_, ref mutability, ref addrof_target) = without_parens(deref_target).kind; - if !in_macro(addrof_target.span); + if !addrof_target.span.from_expansion(); then { let mut applicability = Applicability::MachineApplicable; let sugg = if e.span.from_expansion() { @@ -125,6 +125,7 @@ declare_clippy_lint! { /// # let point = Point(30, 20); /// let x = point.0; /// ``` + #[clippy::version = "pre 1.29.0"] pub REF_IN_DEREF, complexity, "Use of reference in auto dereference expression." diff --git a/clippy_lints/src/regex.rs b/clippy_lints/src/regex.rs index 5d08aee1e5f..8e5983b4773 100644 --- a/clippy_lints/src/regex.rs +++ b/clippy_lints/src/regex.rs @@ -22,6 +22,7 @@ declare_clippy_lint! { /// ```ignore /// Regex::new("|") /// ``` + #[clippy::version = "pre 1.29.0"] pub INVALID_REGEX, correctness, "invalid regular expressions" @@ -46,6 +47,7 @@ declare_clippy_lint! { /// ```ignore /// Regex::new("^foobar") /// ``` + #[clippy::version = "pre 1.29.0"] pub TRIVIAL_REGEX, nursery, "trivial regular expressions" diff --git a/clippy_lints/src/repeat_once.rs b/clippy_lints/src/repeat_once.rs index cf94c0e97d9..b5dd2de6337 100644 --- a/clippy_lints/src/repeat_once.rs +++ b/clippy_lints/src/repeat_once.rs @@ -1,6 +1,5 @@ use clippy_utils::consts::{constant_context, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::in_macro; use clippy_utils::source::snippet; use clippy_utils::ty::is_type_diagnostic_item; use if_chain::if_chain; @@ -36,6 +35,7 @@ declare_clippy_lint! { /// let x = String::from("hello world").clone(); /// } /// ``` + #[clippy::version = "1.47.0"] pub REPEAT_ONCE, complexity, "using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` " @@ -49,7 +49,7 @@ impl<'tcx> LateLintPass<'tcx> for RepeatOnce { if let ExprKind::MethodCall(path, _, [receiver, count], _) = &expr.kind; if path.ident.name == sym!(repeat); if constant_context(cx, cx.typeck_results()).expr(count) == Some(Constant::Int(1)); - if !in_macro(receiver.span); + if !receiver.span.from_expansion(); then { let ty = cx.typeck_results().expr_ty(receiver).peel_refs(); if ty.is_str() { diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index ae85b7087e7..494bc7dda18 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_opt; -use clippy_utils::{fn_def_id, in_macro, path_to_local_id}; +use clippy_utils::{fn_def_id, path_to_local_id}; use if_chain::if_chain; use rustc_ast::ast::Attribute; use rustc_errors::Applicability; @@ -37,6 +37,7 @@ declare_clippy_lint! { /// String::new() /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub LET_AND_RETURN, style, "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block" @@ -62,6 +63,7 @@ declare_clippy_lint! { /// x /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub NEEDLESS_RETURN, style, "using a return statement like `return expr;` where an expression would suffice" @@ -90,8 +92,7 @@ impl<'tcx> LateLintPass<'tcx> for Return { if !last_statement_borrows(cx, initexpr); if !in_external_macro(cx.sess(), initexpr.span); if !in_external_macro(cx.sess(), retexpr.span); - if !in_external_macro(cx.sess(), local.span); - if !in_macro(local.span); + if !local.span.from_expansion(); then { span_lint_and_then( cx, diff --git a/clippy_lints/src/same_name_method.rs b/clippy_lints/src/same_name_method.rs index 737ff634e44..3f38d12fb55 100644 --- a/clippy_lints/src/same_name_method.rs +++ b/clippy_lints/src/same_name_method.rs @@ -33,6 +33,7 @@ declare_clippy_lint! { /// fn foo(&self) {} /// } /// ``` + #[clippy::version = "1.57.0"] pub SAME_NAME_METHOD, restriction, "two method with same name" diff --git a/clippy_lints/src/self_assignment.rs b/clippy_lints/src/self_assignment.rs index fbd65fef7d1..b14f0518bdb 100644 --- a/clippy_lints/src/self_assignment.rs +++ b/clippy_lints/src/self_assignment.rs @@ -30,6 +30,7 @@ declare_clippy_lint! { /// a.y = a.y; /// } /// ``` + #[clippy::version = "1.48.0"] pub SELF_ASSIGNMENT, correctness, "explicit self-assignment" diff --git a/clippy_lints/src/self_named_constructors.rs b/clippy_lints/src/self_named_constructors.rs index 4ba5e1a0f53..dd73dbfe09e 100644 --- a/clippy_lints/src/self_named_constructors.rs +++ b/clippy_lints/src/self_named_constructors.rs @@ -32,6 +32,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.55.0"] pub SELF_NAMED_CONSTRUCTORS, style, "method should not have the same name as the type it is implemented for" diff --git a/clippy_lints/src/semicolon_if_nothing_returned.rs b/clippy_lints/src/semicolon_if_nothing_returned.rs index c0e4914efe0..0b3bbbc8155 100644 --- a/clippy_lints/src/semicolon_if_nothing_returned.rs +++ b/clippy_lints/src/semicolon_if_nothing_returned.rs @@ -29,6 +29,7 @@ declare_clippy_lint! { /// println!("Hello world"); /// } /// ``` + #[clippy::version = "1.52.0"] pub SEMICOLON_IF_NOTHING_RETURNED, pedantic, "add a semicolon if nothing is returned" @@ -44,7 +45,7 @@ impl LateLintPass<'_> for SemicolonIfNothingReturned { let t_expr = cx.typeck_results().expr_ty(expr); if t_expr.is_unit(); if let snippet = snippet_with_macro_callsite(cx, expr.span, "}"); - if !snippet.ends_with('}'); + if !snippet.ends_with('}') && !snippet.ends_with(';'); if cx.sess().source_map().is_multiline(block.span); then { // filter out the desugared `for` loop diff --git a/clippy_lints/src/serde_api.rs b/clippy_lints/src/serde_api.rs index 2cd0f85999c..a38b3c4ab69 100644 --- a/clippy_lints/src/serde_api.rs +++ b/clippy_lints/src/serde_api.rs @@ -15,6 +15,7 @@ declare_clippy_lint! { /// ### Example /// Implementing `Visitor::visit_string` but not /// `Visitor::visit_str`. + #[clippy::version = "pre 1.29.0"] pub SERDE_API_MISUSE, correctness, "various things that will negatively affect your serde experience" diff --git a/clippy_lints/src/shadow.rs b/clippy_lints/src/shadow.rs index 64841f33cc3..5f82aed872c 100644 --- a/clippy_lints/src/shadow.rs +++ b/clippy_lints/src/shadow.rs @@ -29,6 +29,7 @@ declare_clippy_lint! { /// // Good /// let y = &x; // use different variable name /// ``` + #[clippy::version = "pre 1.29.0"] pub SHADOW_SAME, restriction, "rebinding a name to itself, e.g., `let mut x = &mut x`" @@ -55,6 +56,7 @@ declare_clippy_lint! { /// let x = 2; /// let y = x + 1; /// ``` + #[clippy::version = "pre 1.29.0"] pub SHADOW_REUSE, restriction, "rebinding a name to an expression that re-uses the original value, e.g., `let x = x + 1`" @@ -84,6 +86,7 @@ declare_clippy_lint! { /// // Good /// let w = z; // use different variable name /// ``` + #[clippy::version = "pre 1.29.0"] pub SHADOW_UNRELATED, restriction, "rebinding a name without even using the original value" diff --git a/clippy_lints/src/single_component_path_imports.rs b/clippy_lints/src/single_component_path_imports.rs index f6487b8c46b..28d32203da9 100644 --- a/clippy_lints/src/single_component_path_imports.rs +++ b/clippy_lints/src/single_component_path_imports.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; -use clippy_utils::in_macro; use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind, VisibilityKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; @@ -28,6 +27,7 @@ declare_clippy_lint! { /// regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(); /// } /// ``` + #[clippy::version = "1.43.0"] pub SINGLE_COMPONENT_PATH_IMPORTS, style, "imports with single component path are redundant" @@ -110,7 +110,7 @@ fn track_uses( single_use_usages: &mut Vec<(Symbol, Span, bool)>, macros: &mut Vec, ) { - if in_macro(item.span) || item.vis.kind.is_pub() { + if item.span.from_expansion() || item.vis.kind.is_pub() { return; } diff --git a/clippy_lints/src/size_of_in_element_count.rs b/clippy_lints/src/size_of_in_element_count.rs index 3e4e4a8d0c0..df1e85afdd7 100644 --- a/clippy_lints/src/size_of_in_element_count.rs +++ b/clippy_lints/src/size_of_in_element_count.rs @@ -29,6 +29,7 @@ declare_clippy_lint! { /// let mut y = [2u8; SIZE]; /// unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::() * SIZE) }; /// ``` + #[clippy::version = "1.50.0"] pub SIZE_OF_IN_ELEMENT_COUNT, correctness, "using `size_of::` or `size_of_val::` where a count of elements of `T` is expected" diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index 3608fe1472d..1ae772ef70b 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -36,6 +36,7 @@ declare_clippy_lint! { /// let mut vec1 = vec![0; len]; /// let mut vec2 = vec![0; len]; /// ``` + #[clippy::version = "1.32.0"] pub SLOW_VECTOR_INITIALIZATION, perf, "slow vector initialization" diff --git a/clippy_lints/src/stable_sort_primitive.rs b/clippy_lints/src/stable_sort_primitive.rs index 4ea1293d504..953d21e07a3 100644 --- a/clippy_lints/src/stable_sort_primitive.rs +++ b/clippy_lints/src/stable_sort_primitive.rs @@ -28,6 +28,7 @@ declare_clippy_lint! { /// let mut vec = vec![2, 1, 3]; /// vec.sort_unstable(); /// ``` + #[clippy::version = "1.47.0"] pub STABLE_SORT_PRIMITIVE, perf, "use of sort() when sort_unstable() is equivalent" diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 6435107b8b4..368274440d5 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -31,6 +31,7 @@ declare_clippy_lint! { /// x += ", World"; /// x.push_str(", World"); /// ``` + #[clippy::version = "pre 1.29.0"] pub STRING_ADD_ASSIGN, pedantic, "using `x = x + ..` where x is a `String` instead of `push_str()`" @@ -58,6 +59,7 @@ declare_clippy_lint! { /// let x = "Hello".to_owned(); /// x + ", World"; /// ``` + #[clippy::version = "pre 1.29.0"] pub STRING_ADD, restriction, "using `x + ..` where x is a `String` instead of `push_str()`" @@ -102,6 +104,7 @@ declare_clippy_lint! { /// // Good /// let bs = b"a byte string"; /// ``` + #[clippy::version = "pre 1.29.0"] pub STRING_LIT_AS_BYTES, nursery, "calling `as_bytes` on a string literal instead of using a byte string literal" @@ -125,6 +128,7 @@ declare_clippy_lint! { /// ```rust,should_panic /// &"Ölkanne"[1..]; /// ``` + #[clippy::version = "1.58.0"] pub STRING_SLICE, restriction, "slicing a string" @@ -227,6 +231,7 @@ declare_clippy_lint! { /// ```rust /// let _ = &"Hello World!"[6..11]; /// ``` + #[clippy::version = "1.50.0"] pub STRING_FROM_UTF8_AS_BYTES, complexity, "casting string slices to byte slices and back" @@ -371,6 +376,7 @@ declare_clippy_lint! { /// // example code which does not raise clippy warning /// let _ = "str".to_owned(); /// ``` + #[clippy::version = "pre 1.29.0"] pub STR_TO_STRING, restriction, "using `to_string()` on a `&str`, which should be `to_owned()`" @@ -420,6 +426,7 @@ declare_clippy_lint! { /// let msg = String::from("Hello World"); /// let _ = msg.clone(); /// ``` + #[clippy::version = "pre 1.29.0"] pub STRING_TO_STRING, restriction, "using `to_string()` on a `String`, which should be `clone()`" diff --git a/clippy_lints/src/strlen_on_c_strings.rs b/clippy_lints/src/strlen_on_c_strings.rs index 516fa3d95b4..be7431f11aa 100644 --- a/clippy_lints/src/strlen_on_c_strings.rs +++ b/clippy_lints/src/strlen_on_c_strings.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::in_macro; use clippy_utils::paths; use clippy_utils::source::snippet_with_macro_callsite; use clippy_utils::ty::{is_type_diagnostic_item, is_type_ref_to_diagnostic_item}; @@ -31,6 +30,7 @@ declare_clippy_lint! { /// let cstring = CString::new("foo").expect("CString::new failed"); /// let len = cstring.as_bytes().len(); /// ``` + #[clippy::version = "1.55.0"] pub STRLEN_ON_C_STRINGS, complexity, "using `libc::strlen` on a `CString` or `CStr` value, while `as_bytes().len()` or `to_bytes().len()` respectively can be used instead" @@ -40,7 +40,7 @@ declare_lint_pass!(StrlenOnCStrings => [STRLEN_ON_C_STRINGS]); impl LateLintPass<'tcx> for StrlenOnCStrings { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - if in_macro(expr.span) { + if expr.span.from_expansion() { return; } diff --git a/clippy_lints/src/suspicious_operation_groupings.rs b/clippy_lints/src/suspicious_operation_groupings.rs index 201aa067824..faf43fd9fc1 100644 --- a/clippy_lints/src/suspicious_operation_groupings.rs +++ b/clippy_lints/src/suspicious_operation_groupings.rs @@ -59,6 +59,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.50.0"] pub SUSPICIOUS_OPERATION_GROUPINGS, nursery, "groupings of binary operations that look suspiciously like typos" diff --git a/clippy_lints/src/suspicious_trait_impl.rs b/clippy_lints/src/suspicious_trait_impl.rs index 682fad00a13..a3195de81d1 100644 --- a/clippy_lints/src/suspicious_trait_impl.rs +++ b/clippy_lints/src/suspicious_trait_impl.rs @@ -25,6 +25,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub SUSPICIOUS_ARITHMETIC_IMPL, suspicious, "suspicious use of operators in impl of arithmetic trait" @@ -46,6 +47,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub SUSPICIOUS_OP_ASSIGN_IMPL, suspicious, "suspicious use of operators in impl of OpAssign trait" diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index ef26de5b6b9..4c10b12437d 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{can_mut_borrow_both, differing_macro_contexts, eq_expr_value}; +use clippy_utils::{can_mut_borrow_both, differing_macro_contexts, eq_expr_value, std_or_core}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind}; @@ -35,6 +35,7 @@ declare_clippy_lint! { /// let mut b = 2; /// std::mem::swap(&mut a, &mut b); /// ``` + #[clippy::version = "pre 1.29.0"] pub MANUAL_SWAP, complexity, "manual swap of two variables" @@ -60,6 +61,7 @@ declare_clippy_lint! { /// # let mut b = 2; /// std::mem::swap(&mut a, &mut b); /// ``` + #[clippy::version = "pre 1.29.0"] pub ALMOST_SWAPPED, correctness, "`foo = bar; bar = foo` sequence" @@ -113,6 +115,8 @@ fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, spa let first = Sugg::hir_with_applicability(cx, e1, "..", &mut applicability); let second = Sugg::hir_with_applicability(cx, e2, "..", &mut applicability); + let Some(sugg) = std_or_core(cx) else { return }; + span_lint_and_then( cx, MANUAL_SWAP, @@ -122,11 +126,11 @@ fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, spa diag.span_suggestion( span, "try", - format!("std::mem::swap({}, {})", first.mut_addr(), second.mut_addr()), + format!("{}::mem::swap({}, {})", sugg, first.mut_addr(), second.mut_addr()), applicability, ); if !is_xor_based { - diag.note("or maybe you should use `std::mem::replace`?"); + diag.note(&format!("or maybe you should use `{}::mem::replace`?", sugg)); } }, ); @@ -187,26 +191,30 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) { }; let span = first.span.to(second.span); + let Some(sugg) = std_or_core(cx) else { return }; span_lint_and_then(cx, - ALMOST_SWAPPED, - span, - &format!("this looks like you are trying to swap{}", what), - |diag| { - if !what.is_empty() { - diag.span_suggestion( - span, - "try", - format!( - "std::mem::swap({}, {})", - lhs, - rhs, - ), - Applicability::MaybeIncorrect, - ); - diag.note("or maybe you should use `std::mem::replace`?"); - } - }); + ALMOST_SWAPPED, + span, + &format!("this looks like you are trying to swap{}", what), + |diag| { + if !what.is_empty() { + diag.span_suggestion( + span, + "try", + format!( + "{}::mem::swap({}, {})", + sugg, + lhs, + rhs, + ), + Applicability::MaybeIncorrect, + ); + diag.note( + &format!("or maybe you should use `{}::mem::replace`?", sugg) + ); + } + }); } } } diff --git a/clippy_lints/src/tabs_in_doc_comments.rs b/clippy_lints/src/tabs_in_doc_comments.rs index 4a67cabf323..c9b4b245f4c 100644 --- a/clippy_lints/src/tabs_in_doc_comments.rs +++ b/clippy_lints/src/tabs_in_doc_comments.rs @@ -51,6 +51,7 @@ declare_clippy_lint! { /// second_string: String, ///} /// ``` + #[clippy::version = "1.41.0"] pub TABS_IN_DOC_COMMENTS, style, "using tabs in doc comments is not recommended" diff --git a/clippy_lints/src/temporary_assignment.rs b/clippy_lints/src/temporary_assignment.rs index a9da690339c..3766b8f8ed1 100644 --- a/clippy_lints/src/temporary_assignment.rs +++ b/clippy_lints/src/temporary_assignment.rs @@ -17,6 +17,7 @@ declare_clippy_lint! { /// ```rust /// (0, 0).0 = 1 /// ``` + #[clippy::version = "pre 1.29.0"] pub TEMPORARY_ASSIGNMENT, complexity, "assignments to temporaries" diff --git a/clippy_lints/src/to_digit_is_some.rs b/clippy_lints/src/to_digit_is_some.rs index 1c14a919995..5eb58b47838 100644 --- a/clippy_lints/src/to_digit_is_some.rs +++ b/clippy_lints/src/to_digit_is_some.rs @@ -28,6 +28,7 @@ declare_clippy_lint! { /// # let radix = 10; /// let is_digit = c.is_digit(radix); /// ``` + #[clippy::version = "1.41.0"] pub TO_DIGIT_IS_SOME, style, "`char.is_digit()` is clearer" diff --git a/clippy_lints/src/to_string_in_display.rs b/clippy_lints/src/to_string_in_display.rs index b7414cec87c..f8b6bdcd3e1 100644 --- a/clippy_lints/src/to_string_in_display.rs +++ b/clippy_lints/src/to_string_in_display.rs @@ -39,6 +39,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.48.0"] pub TO_STRING_IN_DISPLAY, correctness, "`to_string` method used while implementing `Display` trait" diff --git a/clippy_lints/src/trailing_empty_array.rs b/clippy_lints/src/trailing_empty_array.rs index c216a1f81ea..47c0a84cd46 100644 --- a/clippy_lints/src/trailing_empty_array.rs +++ b/clippy_lints/src/trailing_empty_array.rs @@ -28,6 +28,7 @@ declare_clippy_lint! { /// last: [u32; 0], /// } /// ``` + #[clippy::version = "1.58.0"] pub TRAILING_EMPTY_ARRAY, nursery, "struct with a trailing zero-sized array but without `#[repr(C)]` or another `repr` attribute" diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 79367c4230c..fb4abceac25 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::source::{snippet, snippet_with_applicability}; -use clippy_utils::{in_macro, SpanlessHash}; +use clippy_utils::SpanlessHash; use if_chain::if_chain; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::unhash::UnhashMap; @@ -28,6 +28,7 @@ declare_clippy_lint! { /// ```rust /// pub fn foo(t: T) where T: Copy + Clone {} /// ``` + #[clippy::version = "1.38.0"] pub TYPE_REPETITION_IN_BOUNDS, pedantic, "Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`" @@ -57,6 +58,7 @@ declare_clippy_lint! { /// ```rust /// fn func(arg: T) where T: Clone + Default {} /// ``` + #[clippy::version = "1.47.0"] pub TRAIT_DUPLICATION_IN_BOUNDS, pedantic, "Check if the same trait bounds are specified twice during a function declaration" @@ -93,7 +95,7 @@ fn get_trait_res_span_from_bound(bound: &GenericBound<'_>) -> Option<(Res, Span) impl TraitBounds { fn check_type_repetition(self, cx: &LateContext<'_>, gen: &'_ Generics<'_>) { - if in_macro(gen.span) { + if gen.span.from_expansion() { return; } let hash = |ty| -> u64 { @@ -107,7 +109,7 @@ impl TraitBounds { if_chain! { if let WherePredicate::BoundPredicate(ref p) = bound; if p.bounds.len() as u64 <= self.max_trait_bounds; - if !in_macro(p.span); + if !p.span.from_expansion(); let h = hash(p.bounded_ty); if let Some(ref v) = map.insert(h, p.bounds.iter().collect::>()); @@ -151,7 +153,7 @@ impl TraitBounds { } fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) { - if in_macro(gen.span) || gen.params.is_empty() || gen.where_clause.predicates.is_empty() { + if gen.span.from_expansion() || gen.params.is_empty() || gen.where_clause.predicates.is_empty() { return; } @@ -170,7 +172,7 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) { for predicate in gen.where_clause.predicates { if_chain! { if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate; - if !in_macro(bound_predicate.span); + if !bound_predicate.span.from_expansion(); if let TyKind::Path(QPath::Resolved(_, Path { segments, .. })) = bound_predicate.bounded_ty.kind; if let Some(segment) = segments.first(); if let Some(trait_resolutions_direct) = map.get(&segment.ident); diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index e6acf1a94c9..3ad4ec74bf5 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -36,6 +36,7 @@ declare_clippy_lint! { /// ```ignore /// let ptr: *const T = core::intrinsics::transmute('x') /// ``` + #[clippy::version = "pre 1.29.0"] pub WRONG_TRANSMUTE, correctness, "transmutes that are confusing at best, undefined behaviour at worst and always useless" @@ -55,6 +56,7 @@ declare_clippy_lint! { /// ```rust,ignore /// core::intrinsics::transmute(t); // where the result type is the same as `t`'s /// ``` + #[clippy::version = "pre 1.29.0"] pub USELESS_TRANSMUTE, nursery, "transmutes that have the same to and from types or could be a cast/coercion" @@ -80,6 +82,7 @@ declare_clippy_lint! { /// # let p: *const [i32] = &[]; /// p as *const [u16]; /// ``` + #[clippy::version = "1.47.0"] pub TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, complexity, "transmutes that could be a pointer cast" @@ -98,6 +101,7 @@ declare_clippy_lint! { /// core::intrinsics::transmute(t) // where the result type is the same as /// // `*t` or `&t`'s /// ``` + #[clippy::version = "pre 1.29.0"] pub CROSSPOINTER_TRANSMUTE, complexity, "transmutes that have to or from types that are a pointer to the other" @@ -125,6 +129,7 @@ declare_clippy_lint! { /// // can be written: /// let _: &T = &*p; /// ``` + #[clippy::version = "pre 1.29.0"] pub TRANSMUTE_PTR_TO_REF, complexity, "transmutes from a pointer to a reference type" @@ -158,6 +163,7 @@ declare_clippy_lint! { /// // should be: /// let _ = std::char::from_u32(x).unwrap(); /// ``` + #[clippy::version = "pre 1.29.0"] pub TRANSMUTE_INT_TO_CHAR, complexity, "transmutes from an integer to a `char`" @@ -191,6 +197,7 @@ declare_clippy_lint! { /// // should be: /// let _ = std::str::from_utf8(b).unwrap(); /// ``` + #[clippy::version = "pre 1.29.0"] pub TRANSMUTE_BYTES_TO_STR, complexity, "transmutes from a `&[u8]` to a `&str`" @@ -213,6 +220,7 @@ declare_clippy_lint! { /// // should be: /// let _: bool = x != 0; /// ``` + #[clippy::version = "pre 1.29.0"] pub TRANSMUTE_INT_TO_BOOL, complexity, "transmutes from an integer to a `bool`" @@ -235,6 +243,7 @@ declare_clippy_lint! { /// // should be: /// let _: f32 = f32::from_bits(1_u32); /// ``` + #[clippy::version = "pre 1.29.0"] pub TRANSMUTE_INT_TO_FLOAT, complexity, "transmutes from an integer to a float" @@ -257,6 +266,7 @@ declare_clippy_lint! { /// // should be: /// let _: u32 = 1f32.to_bits(); /// ``` + #[clippy::version = "1.41.0"] pub TRANSMUTE_FLOAT_TO_INT, complexity, "transmutes from a float to an integer" @@ -279,6 +289,7 @@ declare_clippy_lint! { /// // should be /// let x: [u8; 8] = 0i64.to_ne_bytes(); /// ``` + #[clippy::version = "1.58.0"] pub TRANSMUTE_NUM_TO_BYTES, complexity, "transmutes from a number to an array of `u8`" @@ -306,6 +317,7 @@ declare_clippy_lint! { /// let _ = ptr as *const f32; /// let _ = unsafe{ &*(&1u32 as *const u32 as *const f32) }; /// ``` + #[clippy::version = "pre 1.29.0"] pub TRANSMUTE_PTR_TO_PTR, pedantic, "transmutes from a pointer to a pointer / a reference to a reference" @@ -337,6 +349,7 @@ declare_clippy_lint! { /// ```rust /// vec![2_u16].into_iter().map(u32::from).collect::>(); /// ``` + #[clippy::version = "1.40.0"] pub UNSOUND_COLLECTION_TRANSMUTE, correctness, "transmute between collections of layout-incompatible types" diff --git a/clippy_lints/src/transmuting_null.rs b/clippy_lints/src/transmuting_null.rs index ef80663d1da..7939dfedc3a 100644 --- a/clippy_lints/src/transmuting_null.rs +++ b/clippy_lints/src/transmuting_null.rs @@ -25,6 +25,7 @@ declare_clippy_lint! { /// ```rust /// let null_ref: &u64 = unsafe { std::mem::transmute(0 as *const u64) }; /// ``` + #[clippy::version = "1.35.0"] pub TRANSMUTING_NULL, correctness, "transmutes from a null pointer to a reference, which is undefined behavior" diff --git a/clippy_lints/src/try_err.rs b/clippy_lints/src/try_err.rs index e9ec120a7f9..e0e7ec9a452 100644 --- a/clippy_lints/src/try_err.rs +++ b/clippy_lints/src/try_err.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet, snippet_with_macro_callsite}; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{differing_macro_contexts, get_parent_expr, in_macro, is_lang_ctor, match_def_path, paths}; +use clippy_utils::{get_parent_expr, is_lang_ctor, match_def_path, paths}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::LangItem::ResultErr; @@ -10,7 +10,7 @@ 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::sym; +use rustc_span::{hygiene, sym}; declare_clippy_lint! { /// ### What it does @@ -41,6 +41,7 @@ declare_clippy_lint! { /// Ok(0) /// } /// ``` + #[clippy::version = "1.38.0"] pub TRY_ERR, style, "return errors explicitly rather than hiding them behind a `?`" @@ -93,15 +94,9 @@ impl<'tcx> LateLintPass<'tcx> for TryErr { }; let expr_err_ty = cx.typeck_results().expr_ty(err_arg); - let differing_contexts = differing_macro_contexts(expr.span, err_arg.span); - - let origin_snippet = if in_macro(expr.span) && in_macro(err_arg.span) && differing_contexts { - snippet(cx, err_arg.span.ctxt().outer_expn_data().call_site, "_") - } else if err_arg.span.from_expansion() && !in_macro(expr.span) { - snippet_with_macro_callsite(cx, err_arg.span, "_") - } else { - snippet(cx, err_arg.span, "_") - }; + let span = hygiene::walk_chain(err_arg.span, try_arg.span.ctxt()); + let mut applicability = Applicability::MachineApplicable; + let origin_snippet = snippet_with_applicability(cx, span, "_", &mut applicability); let ret_prefix = if get_parent_expr(cx, expr).map_or(false, |e| matches!(e.kind, ExprKind::Ret(_))) { "" // already returns } else { @@ -120,7 +115,7 @@ impl<'tcx> LateLintPass<'tcx> for TryErr { "returning an `Err(_)` with the `?` operator", "try this", suggestion, - Applicability::MachineApplicable + applicability, ); } } diff --git a/clippy_lints/src/types/mod.rs b/clippy_lints/src/types/mod.rs index bbe07db5358..5a7ef760a30 100644 --- a/clippy_lints/src/types/mod.rs +++ b/clippy_lints/src/types/mod.rs @@ -43,6 +43,7 @@ declare_clippy_lint! { /// values: Vec, /// } /// ``` + #[clippy::version = "1.57.0"] pub BOX_COLLECTION, perf, "usage of `Box>`, vector elements are already on the heap" @@ -75,6 +76,7 @@ declare_clippy_lint! { /// values: Vec, /// } /// ``` + #[clippy::version = "1.33.0"] pub VEC_BOX, complexity, "usage of `Vec>` where T: Sized, vector elements are already on the heap" @@ -113,6 +115,7 @@ declare_clippy_lint! { /// Contents::None /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub OPTION_OPTION, pedantic, "usage of `Option>`" @@ -152,6 +155,7 @@ declare_clippy_lint! { /// # use std::collections::LinkedList; /// let x: LinkedList = LinkedList::new(); /// ``` + #[clippy::version = "pre 1.29.0"] pub LINKEDLIST, pedantic, "usage of LinkedList, usually a vector is faster, or a more specialized data structure like a `VecDeque`" @@ -176,6 +180,7 @@ declare_clippy_lint! { /// ```rust,ignore /// fn foo(bar: &T) { ... } /// ``` + #[clippy::version = "pre 1.29.0"] pub BORROWED_BOX, complexity, "a borrow of a boxed type" @@ -200,6 +205,7 @@ declare_clippy_lint! { /// ```rust /// fn foo(bar: &usize) {} /// ``` + #[clippy::version = "1.44.0"] pub REDUNDANT_ALLOCATION, perf, "redundant allocation" @@ -234,6 +240,7 @@ declare_clippy_lint! { /// ```rust,ignore /// fn foo(interned: Rc) { ... } /// ``` + #[clippy::version = "1.48.0"] pub RC_BUFFER, restriction, "shared ownership of a buffer type" @@ -255,6 +262,7 @@ declare_clippy_lint! { /// inner: Rc>>>, /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub TYPE_COMPLEXITY, complexity, "usage of very complex types that might be better factored into `type` definitions" @@ -287,6 +295,7 @@ declare_clippy_lint! { /// use std::cell::RefCell /// fn foo(interned: Rc>) { ... } /// ``` + #[clippy::version = "1.55.0"] pub RC_MUTEX, restriction, "usage of `Rc>`" diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 11aef50991b..ccc49caf47c 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; +use clippy_utils::is_lint_allowed; use clippy_utils::source::{indent_of, reindent_multiline, snippet}; -use clippy_utils::{in_macro, is_lint_allowed}; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, HirId, Local, UnsafeSource}; @@ -39,6 +39,7 @@ declare_clippy_lint! { /// // Safety: references are guaranteed to be non-null. /// let ptr = unsafe { NonNull::new_unchecked(a) }; /// ``` + #[clippy::version = "1.58.0"] pub UNDOCUMENTED_UNSAFE_BLOCKS, restriction, "creating an unsafe block without explaining why it is safe" @@ -134,12 +135,12 @@ impl UndocumentedUnsafeBlocks { let enclosing_scope_span = map.opt_span(enclosing_hir_id)?; - let between_span = if in_macro(block_span) { + let between_span = if block_span.from_expansion() { self.macro_expansion = true; - enclosing_scope_span.with_hi(block_span.hi()) + enclosing_scope_span.with_hi(block_span.hi()).source_callsite() } else { self.macro_expansion = false; - enclosing_scope_span.to(block_span) + enclosing_scope_span.to(block_span).source_callsite() }; let file_name = source_map.span_to_filename(between_span); @@ -149,6 +150,8 @@ impl UndocumentedUnsafeBlocks { let lex_end = (between_span.hi().0 - source_file.start_pos.0) as usize; let src_str = source_file.src.as_ref()?[lex_start..lex_end].to_string(); + let source_start_pos = source_file.start_pos.0 as usize + lex_start; + let mut pos = 0; let mut comment = false; @@ -171,7 +174,7 @@ impl UndocumentedUnsafeBlocks { if comment { // Get the line number of the "comment" (really wherever the trailing whitespace ended) let comment_line_num = source_file - .lookup_file_pos_with_col_display(BytePos((lex_start + pos).try_into().unwrap())) + .lookup_file_pos(BytePos((source_start_pos + pos).try_into().unwrap())) .0; // Find the block/local's line number let block_line_num = tcx.sess.source_map().lookup_char_pos(block_span.lo()).line; diff --git a/clippy_lints/src/undropped_manually_drops.rs b/clippy_lints/src/undropped_manually_drops.rs index 09570616593..c58fa67a023 100644 --- a/clippy_lints/src/undropped_manually_drops.rs +++ b/clippy_lints/src/undropped_manually_drops.rs @@ -28,6 +28,7 @@ declare_clippy_lint! { /// std::mem::ManuallyDrop::drop(&mut std::mem::ManuallyDrop::new(S)); /// } /// ``` + #[clippy::version = "1.49.0"] pub UNDROPPED_MANUALLY_DROPS, correctness, "use of safe `std::mem::drop` function to drop a std::mem::ManuallyDrop, which will not drop the inner value" diff --git a/clippy_lints/src/unicode.rs b/clippy_lints/src/unicode.rs index f49ce696a04..a514e8c44e2 100644 --- a/clippy_lints/src/unicode.rs +++ b/clippy_lints/src/unicode.rs @@ -20,6 +20,7 @@ declare_clippy_lint! { /// ### Example /// You don't see it, but there may be a zero-width space or soft hyphen /// some­where in this text. + #[clippy::version = "1.49.0"] pub INVISIBLE_CHARACTERS, correctness, "using an invisible character in a string literal, which is confusing" @@ -44,6 +45,7 @@ declare_clippy_lint! { /// ```rust /// let x = String::from("\u{20ac}"); /// ``` + #[clippy::version = "pre 1.29.0"] pub NON_ASCII_LITERAL, restriction, "using any literal non-ASCII chars in a string literal instead of using the `\\u` escape" @@ -62,6 +64,7 @@ declare_clippy_lint! { /// ### Example /// You may not see it, but "à"" and "à"" aren't the same string. The /// former when escaped is actually `"a\u{300}"` while the latter is `"\u{e0}"`. + #[clippy::version = "pre 1.29.0"] pub UNICODE_NOT_NFC, pedantic, "using a Unicode literal not in NFC normal form (see [Unicode tr15](http://www.unicode.org/reports/tr15/) for further information)" diff --git a/clippy_lints/src/uninit_vec.rs b/clippy_lints/src/uninit_vec.rs index f3e8b688105..46cc76b150e 100644 --- a/clippy_lints/src/uninit_vec.rs +++ b/clippy_lints/src/uninit_vec.rs @@ -52,6 +52,7 @@ declare_clippy_lint! { /// // perform initialization with `remaining` /// vec.set_len(...); // Safe to call `set_len()` on initialized part /// ``` + #[clippy::version = "1.58.0"] pub UNINIT_VEC, correctness, "Vec with uninitialized data" diff --git a/clippy_lints/src/unit_hash.rs b/clippy_lints/src/unit_hash.rs index a3a3f2d41c7..26b4e0f58a8 100644 --- a/clippy_lints/src/unit_hash.rs +++ b/clippy_lints/src/unit_hash.rs @@ -39,6 +39,7 @@ declare_clippy_lint! { /// WithValue(x) => x.hash(&mut state), /// } /// ``` + #[clippy::version = "1.58.0"] pub UNIT_HASH, correctness, "hashing a unit value, which does nothing" diff --git a/clippy_lints/src/unit_return_expecting_ord.rs b/clippy_lints/src/unit_return_expecting_ord.rs index db0f412f2a1..9fb8f236899 100644 --- a/clippy_lints/src/unit_return_expecting_ord.rs +++ b/clippy_lints/src/unit_return_expecting_ord.rs @@ -30,6 +30,7 @@ declare_clippy_lint! { /// let mut twins = vec!((1, 1), (2, 2)); /// twins.sort_by_key(|x| { x.1; }); /// ``` + #[clippy::version = "1.47.0"] pub UNIT_RETURN_EXPECTING_ORD, correctness, "fn arguments of type Fn(...) -> Ord returning the unit type ()." diff --git a/clippy_lints/src/unit_types/mod.rs b/clippy_lints/src/unit_types/mod.rs index 66b1abbe50b..d9f5b53b413 100644 --- a/clippy_lints/src/unit_types/mod.rs +++ b/clippy_lints/src/unit_types/mod.rs @@ -21,6 +21,7 @@ declare_clippy_lint! { /// 1; /// }; /// ``` + #[clippy::version = "pre 1.29.0"] pub LET_UNIT_VALUE, pedantic, "creating a `let` binding to a value of unit type, which usually can't be used afterwards" @@ -68,6 +69,7 @@ declare_clippy_lint! { /// assert_eq!({ foo(); }, { bar(); }); /// ``` /// will always succeed + #[clippy::version = "pre 1.29.0"] pub UNIT_CMP, correctness, "comparing unit values" @@ -88,6 +90,7 @@ declare_clippy_lint! { /// baz(a); /// }) /// ``` + #[clippy::version = "pre 1.29.0"] pub UNIT_ARG, complexity, "passing unit to a function" diff --git a/clippy_lints/src/unnamed_address.rs b/clippy_lints/src/unnamed_address.rs index 1eafdee0352..0bcafde658a 100644 --- a/clippy_lints/src/unnamed_address.rs +++ b/clippy_lints/src/unnamed_address.rs @@ -24,6 +24,7 @@ declare_clippy_lint! { /// // ... /// } /// ``` + #[clippy::version = "1.44.0"] pub FN_ADDRESS_COMPARISONS, correctness, "comparison with an address of a function item" @@ -47,6 +48,7 @@ declare_clippy_lint! { /// ... /// } /// ``` + #[clippy::version = "1.44.0"] pub VTABLE_ADDRESS_COMPARISONS, correctness, "comparison with an address of a trait vtable" diff --git a/clippy_lints/src/unnecessary_self_imports.rs b/clippy_lints/src/unnecessary_self_imports.rs index 4cfd2df551f..839a4bdab09 100644 --- a/clippy_lints/src/unnecessary_self_imports.rs +++ b/clippy_lints/src/unnecessary_self_imports.rs @@ -26,6 +26,7 @@ declare_clippy_lint! { /// ```rust /// use std::io; /// ``` + #[clippy::version = "1.53.0"] pub UNNECESSARY_SELF_IMPORTS, restriction, "imports ending in `::{self}`, which can be omitted" diff --git a/clippy_lints/src/unnecessary_sort_by.rs b/clippy_lints/src/unnecessary_sort_by.rs index 26b56e0f2f3..d024577f485 100644 --- a/clippy_lints/src/unnecessary_sort_by.rs +++ b/clippy_lints/src/unnecessary_sort_by.rs @@ -39,6 +39,7 @@ declare_clippy_lint! { /// # let mut vec: Vec = Vec::new(); /// vec.sort_by_key(|a| a.foo()); /// ``` + #[clippy::version = "1.46.0"] pub UNNECESSARY_SORT_BY, complexity, "Use of `Vec::sort_by` when `Vec::sort_by_key` or `Vec::sort` would be clearer" diff --git a/clippy_lints/src/unnecessary_wraps.rs b/clippy_lints/src/unnecessary_wraps.rs index c940cf077d1..1728533f18b 100644 --- a/clippy_lints/src/unnecessary_wraps.rs +++ b/clippy_lints/src/unnecessary_wraps.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; -use clippy_utils::{contains_return, in_macro, is_lang_ctor, return_ty, visitors::find_all_ret_expressions}; +use clippy_utils::{contains_return, is_lang_ctor, return_ty, visitors::find_all_ret_expressions}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; @@ -49,6 +49,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "1.50.0"] pub UNNECESSARY_WRAPS, pedantic, "functions that only return `Ok` or `Some`" @@ -116,7 +117,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { let mut suggs = Vec::new(); let can_sugg = find_all_ret_expressions(cx, &body.value, |ret_expr| { if_chain! { - if !in_macro(ret_expr.span); + if !ret_expr.span.from_expansion(); // Check if a function call. if let ExprKind::Call(func, [arg]) = ret_expr.kind; // Check if OPTION_SOME or RESULT_OK, depending on return type. diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index d6cf7190abb..0bd151fed91 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -39,6 +39,7 @@ declare_clippy_lint! { /// if let Some(0 | 2) = Some(0) {} /// } /// ``` + #[clippy::version = "1.46.0"] pub UNNESTED_OR_PATTERNS, pedantic, "unnested or-patterns, e.g., `Foo(Bar) | Foo(Baz) instead of `Foo(Bar | Baz)`" diff --git a/clippy_lints/src/unsafe_removed_from_name.rs b/clippy_lints/src/unsafe_removed_from_name.rs index 3c694af2b9d..44b1989dbc6 100644 --- a/clippy_lints/src/unsafe_removed_from_name.rs +++ b/clippy_lints/src/unsafe_removed_from_name.rs @@ -21,6 +21,7 @@ declare_clippy_lint! { /// extern crate crossbeam; /// use crossbeam::{spawn_unsafe as spawn}; /// ``` + #[clippy::version = "pre 1.29.0"] pub UNSAFE_REMOVED_FROM_NAME, style, "`unsafe` removed from API names on import" diff --git a/clippy_lints/src/unused_async.rs b/clippy_lints/src/unused_async.rs index f4808682b69..1ccb78425c2 100644 --- a/clippy_lints/src/unused_async.rs +++ b/clippy_lints/src/unused_async.rs @@ -29,6 +29,7 @@ declare_clippy_lint! { /// } /// let number_future = async { get_random_number_improved() }; /// ``` + #[clippy::version = "1.54.0"] pub UNUSED_ASYNC, pedantic, "finds async functions with no await statements" diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index 031b182bd2f..d4b5c9770a2 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -29,6 +29,7 @@ declare_clippy_lint! { /// Ok(()) /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub UNUSED_IO_AMOUNT, correctness, "unused written/read amount" diff --git a/clippy_lints/src/unused_self.rs b/clippy_lints/src/unused_self.rs index e7e249c79a2..fd9d5b52e50 100644 --- a/clippy_lints/src/unused_self.rs +++ b/clippy_lints/src/unused_self.rs @@ -29,6 +29,7 @@ declare_clippy_lint! { /// fn method() {} /// } /// ``` + #[clippy::version = "1.40.0"] pub UNUSED_SELF, pedantic, "methods that contain a `self` argument but don't use it" diff --git a/clippy_lints/src/unused_unit.rs b/clippy_lints/src/unused_unit.rs index 1164ac4938f..48c17fa2a40 100644 --- a/clippy_lints/src/unused_unit.rs +++ b/clippy_lints/src/unused_unit.rs @@ -28,6 +28,7 @@ declare_clippy_lint! { /// ```rust /// fn return_unit() {} /// ``` + #[clippy::version = "1.31.0"] pub UNUSED_UNIT, style, "needless unit expression" diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index ebaa9dcbbf8..71771aae44b 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -39,6 +39,7 @@ declare_clippy_lint! { /// do_something_with(value) /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub UNNECESSARY_UNWRAP, complexity, "checks for calls of `unwrap[_err]()` that cannot fail" @@ -65,6 +66,7 @@ declare_clippy_lint! { /// ``` /// /// This code will always panic. The if condition should probably be inverted. + #[clippy::version = "pre 1.29.0"] pub PANICKING_UNWRAP, correctness, "checks for calls of `unwrap[_err]()` that will always fail" @@ -229,8 +231,8 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> { } else { // find `unwrap[_err]()` calls: if_chain! { - if let ExprKind::MethodCall(method_name, _, args, _) = expr.kind; - if let Some(id) = path_to_local(&args[0]); + if let ExprKind::MethodCall(method_name, _, [self_arg, ..], _) = expr.kind; + if let Some(id) = path_to_local(self_arg); if [sym::unwrap, sym::expect, sym!(unwrap_err)].contains(&method_name.ident.name); let call_to_unwrap = [sym::unwrap, sym::expect].contains(&method_name.ident.name); if let Some(unwrappable) = self.unwrappables.iter() diff --git a/clippy_lints/src/unwrap_in_result.rs b/clippy_lints/src/unwrap_in_result.rs index 6447e3fa2ca..994df85cb8a 100644 --- a/clippy_lints/src/unwrap_in_result.rs +++ b/clippy_lints/src/unwrap_in_result.rs @@ -51,6 +51,7 @@ declare_clippy_lint! { /// Ok(()) /// } /// ``` + #[clippy::version = "1.48.0"] pub UNWRAP_IN_RESULT, restriction, "functions of type `Result<..>` or `Option`<...> that contain `expect()` or `unwrap()`" diff --git a/clippy_lints/src/upper_case_acronyms.rs b/clippy_lints/src/upper_case_acronyms.rs index dbf335a70c8..4773e350760 100644 --- a/clippy_lints/src/upper_case_acronyms.rs +++ b/clippy_lints/src/upper_case_acronyms.rs @@ -33,6 +33,7 @@ declare_clippy_lint! { /// ```rust /// struct HttpResponse; /// ``` + #[clippy::version = "1.51.0"] pub UPPER_CASE_ACRONYMS, style, "capitalized acronyms are against the naming convention" diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index 9ae50e47ca4..059f7f647f8 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::ty::same_type_and_consts; -use clippy_utils::{in_macro, meets_msrv, msrvs}; +use clippy_utils::{meets_msrv, msrvs}; use if_chain::if_chain; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; @@ -51,6 +51,7 @@ declare_clippy_lint! { /// } /// } /// ``` + #[clippy::version = "pre 1.29.0"] pub USE_SELF, nursery, "unnecessary structure name repetition whereas `Self` is applicable" @@ -197,7 +198,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) { if_chain! { - if !in_macro(hir_ty.span); + if !hir_ty.span.from_expansion(); if meets_msrv(self.msrv.as_ref(), &msrvs::TYPE_ALIAS_ENUM_VARIANTS); if let Some(&StackItem::Check { impl_id, @@ -214,8 +215,8 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { }; if same_type_and_consts(ty, cx.tcx.type_of(impl_id)); let hir = cx.tcx.hir(); - let id = hir.get_parent_node(hir_ty.hir_id); - if !hir.opt_span(id).map_or(false, in_macro); + // prevents false positive on `#[derive(serde::Deserialize)]` + if !hir.span(hir.get_parent_node(hir_ty.hir_id)).in_derive_expansion(); then { span_lint(cx, hir_ty.span); } @@ -224,7 +225,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { if_chain! { - if !in_macro(expr.span); + if !expr.span.from_expansion(); if meets_msrv(self.msrv.as_ref(), &msrvs::TYPE_ALIAS_ENUM_VARIANTS); if let Some(&StackItem::Check { impl_id, .. }) = self.stack.last(); if cx.typeck_results().expr_ty(expr) == cx.tcx.type_of(impl_id); diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 88f11542072..0e4b32541c9 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -28,6 +28,7 @@ declare_clippy_lint! { /// // Good /// let s: String = format!("hello"); /// ``` + #[clippy::version = "1.45.0"] pub USELESS_CONVERSION, complexity, "calls to `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` which perform useless conversions to the same type" diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index f93d7782e25..d20bf341318 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -1,16 +1,16 @@ //! A group of attributes that can be attached to Rust code in order //! to generate a clippy lint detecting said code automatically. -use clippy_utils::get_attr; +use clippy_utils::{get_attr, higher}; use rustc_ast::ast::{LitFloatType, LitKind}; -use rustc_ast::walk_list; +use rustc_ast::LitIntType; use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; -use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; -use rustc_hir::{Block, Expr, ExprKind, Pat, PatKind, QPath, Stmt, StmtKind, TyKind}; +use rustc_hir::{ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::hir::map::Map; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::{Ident, Symbol}; +use std::fmt::{Display, Formatter, Write as _}; declare_clippy_lint! { /// ### What it does @@ -53,6 +53,42 @@ declare_clippy_lint! { declare_lint_pass!(Author => [LINT_AUTHOR]); +/// Writes a line of output with indentation added +macro_rules! out { + ($($t:tt)*) => { + println!(" {}", format_args!($($t)*)) + }; +} + +/// The variables passed in are replaced with `&Binding`s where the `value` field is set +/// to the original value of the variable. The `name` field is set to the name of the variable +/// (using `stringify!`) and is adjusted to avoid duplicate names. +/// Note that the `Binding` may be printed directly to output the `name`. +macro_rules! bind { + ($self:ident $(, $name:ident)+) => { + $(let $name = & $self.bind(stringify!($name), $name);)+ + }; +} + +/// Transforms the given `Option` varibles into `OptionPat>`. +/// This displays as `Some($name)` or `None` when printed. The name of the inner binding +/// is set to the name of the variable passed to the macro. +macro_rules! opt_bind { + ($self:ident $(, $name:ident)+) => { + $(let $name = OptionPat::new($name.map(|o| $self.bind(stringify!($name), o)));)+ + }; +} + +/// Creates a `Binding` that accesses the field of an existing `Binding` +macro_rules! field { + ($binding:ident.$field:ident) => { + &Binding { + name: $binding.name.to_string() + stringify!(.$field), + value: $binding.value.$field, + } + }; +} + fn prelude() { println!("if_chain! {{"); } @@ -66,674 +102,594 @@ fn done() { impl<'tcx> LateLintPass<'tcx> for Author { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { - if !has_attr(cx, item.hir_id()) { - return; - } - prelude(); - PrintVisitor::new("item").visit_item(item); - done(); + check_item(cx, item.hir_id()); } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { - if !has_attr(cx, item.hir_id()) { - return; - } - prelude(); - PrintVisitor::new("item").visit_impl_item(item); - done(); + check_item(cx, item.hir_id()); } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { - if !has_attr(cx, item.hir_id()) { - return; - } - prelude(); - PrintVisitor::new("item").visit_trait_item(item); - done(); - } - - fn check_variant(&mut self, cx: &LateContext<'tcx>, var: &'tcx hir::Variant<'_>) { - if !has_attr(cx, var.id) { - return; - } - prelude(); - let parent_hir_id = cx.tcx.hir().get_parent_node(var.id); - PrintVisitor::new("var").visit_variant(var, &hir::Generics::empty(), parent_hir_id); - done(); - } - - fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'_>) { - if !has_attr(cx, field.hir_id) { - return; - } - prelude(); - PrintVisitor::new("field").visit_field_def(field); - done(); - } - - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - if !has_attr(cx, expr.hir_id) { - return; - } - prelude(); - PrintVisitor::new("expr").visit_expr(expr); - done(); + check_item(cx, item.hir_id()); } fn check_arm(&mut self, cx: &LateContext<'tcx>, arm: &'tcx hir::Arm<'_>) { - if !has_attr(cx, arm.hir_id) { - return; - } - prelude(); - PrintVisitor::new("arm").visit_arm(arm); - done(); + check_node(cx, arm.hir_id, |v| { + v.arm(&v.bind("arm", arm)); + }); + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + check_node(cx, expr.hir_id, |v| { + v.expr(&v.bind("expr", expr)); + }); } fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'_>) { - if !has_attr(cx, stmt.hir_id) { - return; - } match stmt.kind { StmtKind::Expr(e) | StmtKind::Semi(e) if has_attr(cx, e.hir_id) => return, _ => {}, } - prelude(); - PrintVisitor::new("stmt").visit_stmt(stmt); - done(); + check_node(cx, stmt.hir_id, |v| { + v.stmt(&v.bind("stmt", stmt)); + }); } +} - fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ForeignItem<'_>) { - if !has_attr(cx, item.hir_id()) { - return; - } +fn check_item(cx: &LateContext<'_>, hir_id: HirId) { + let hir = cx.tcx.hir(); + if let Some(body_id) = hir.maybe_body_owned_by(hir_id) { + check_node(cx, hir_id, |v| { + v.expr(&v.bind("expr", &hir.body(body_id).value)); + }); + } +} + +fn check_node(cx: &LateContext<'_>, hir_id: HirId, f: impl Fn(&PrintVisitor<'_, '_>)) { + if has_attr(cx, hir_id) { prelude(); - PrintVisitor::new("item").visit_foreign_item(item); + f(&PrintVisitor::new(cx)); done(); } } -impl PrintVisitor { - #[must_use] - fn new(s: &'static str) -> Self { - Self { - ids: FxHashMap::default(), - current: s.to_owned(), - } +struct Binding { + name: String, + value: T, +} + +impl Display for Binding { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.name) + } +} + +struct OptionPat { + pub opt: Option, +} + +impl OptionPat { + fn new(opt: Option) -> Self { + Self { opt } } - fn next(&mut self, s: &'static str) -> String { - use std::collections::hash_map::Entry::{Occupied, Vacant}; - match self.ids.entry(s) { - // already there: start numbering from `1` - Occupied(mut occ) => { - let val = occ.get_mut(); - *val += 1; - format!("{}{}", s, *val) - }, - // not there: insert and return name as given - Vacant(vac) => { - vac.insert(0); - s.to_owned() - }, - } - } - - fn print_qpath(&mut self, path: &QPath<'_>) { - if let QPath::LangItem(lang_item, _) = *path { - println!( - " if matches!({}, QPath::LangItem(LangItem::{:?}, _));", - self.current, lang_item, - ); - } else { - print!(" if match_qpath({}, &[", self.current); - print_path(path, &mut true); - println!("]);"); + fn if_some(&self, f: impl Fn(&T)) { + if let Some(t) = &self.opt { + f(t); } } } -struct PrintVisitor { +impl Display for OptionPat { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match &self.opt { + None => f.write_str("None"), + Some(node) => write!(f, "Some({node})"), + } + } +} + +struct PrintVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, /// Fields are the current index that needs to be appended to pattern /// binding names - ids: FxHashMap<&'static str, usize>, - /// the name that needs to be destructured - current: String, + ids: std::cell::Cell>, } -impl<'tcx> Visitor<'tcx> for PrintVisitor { - type Map = Map<'tcx>; +#[allow(clippy::unused_self)] +impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { + fn new(cx: &'a LateContext<'tcx>) -> Self { + Self { + cx, + ids: std::cell::Cell::default(), + } + } + + fn next(&self, s: &'static str) -> String { + let mut ids = self.ids.take(); + let out = match *ids.entry(s).and_modify(|n| *n += 1).or_default() { + // first usage of the name, use it as is + 0 => s.to_string(), + // append a number starting with 1 + n => format!("{s}{n}"), + }; + self.ids.set(ids); + out + } + + fn bind(&self, name: &'static str, value: T) -> Binding { + let name = self.next(name); + Binding { name, value } + } + + fn option(&self, option: &Binding>, name: &'static str, f: impl Fn(&Binding)) { + match option.value { + None => out!("if {option}.is_none();"), + Some(value) => { + let value = &self.bind(name, value); + out!("if let Some({value}) = {option};"); + f(value); + }, + } + } + + fn slice(&self, slice: &Binding<&[T]>, f: impl Fn(&Binding<&T>)) { + if slice.value.is_empty() { + out!("if {slice}.is_empty();"); + } else { + out!("if {slice}.len() == {};", slice.value.len()); + for (i, value) in slice.value.iter().enumerate() { + let name = format!("{slice}[{i}]"); + f(&Binding { name, value }); + } + } + } + + fn destination(&self, destination: &Binding) { + self.option(field!(destination.label), "label", |label| { + self.ident(field!(label.ident)); + }); + } + + fn ident(&self, ident: &Binding) { + out!("if {ident}.as_str() == {:?};", ident.value.as_str()); + } + + fn symbol(&self, symbol: &Binding) { + out!("if {symbol}.as_str() == {:?};", symbol.value.as_str()); + } + + fn qpath(&self, qpath: &Binding<&QPath<'_>>) { + if let QPath::LangItem(lang_item, _) = *qpath.value { + out!("if matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _));"); + } else { + out!("if match_qpath({qpath}, &[{}]);", path_to_string(qpath.value)); + } + } + + fn lit(&self, lit: &Binding<&Lit>) { + let kind = |kind| out!("if let LitKind::{kind} = {lit}.node;"); + macro_rules! kind { + ($($t:tt)*) => (kind(format_args!($($t)*))); + } + + match lit.value.node { + LitKind::Bool(val) => kind!("Bool({val:?})"), + LitKind::Char(c) => kind!("Char({c:?})"), + LitKind::Err(val) => kind!("Err({val})"), + LitKind::Byte(b) => kind!("Byte({b})"), + LitKind::Int(i, suffix) => { + let int_ty = match suffix { + LitIntType::Signed(int_ty) => format!("LitIntType::Signed(IntTy::{int_ty:?})"), + LitIntType::Unsigned(uint_ty) => format!("LitIntType::Unsigned(UintTy::{uint_ty:?})"), + LitIntType::Unsuffixed => String::from("LitIntType::Unsuffixed"), + }; + kind!("Int({i}, {int_ty})"); + }, + LitKind::Float(_, suffix) => { + let float_ty = match suffix { + LitFloatType::Suffixed(suffix_ty) => format!("LitFloatType::Suffixed(FloatTy::{suffix_ty:?})"), + LitFloatType::Unsuffixed => String::from("LitFloatType::Unsuffixed"), + }; + kind!("Float(_, {float_ty})"); + }, + LitKind::ByteStr(ref vec) => { + bind!(self, vec); + kind!("ByteStr(ref {vec})"); + out!("if let [{:?}] = **{vec};", vec.value); + }, + LitKind::Str(s, _) => { + bind!(self, s); + kind!("Str({s}, _)"); + self.symbol(s); + }, + } + } + + fn arm(&self, arm: &Binding<&hir::Arm<'_>>) { + self.pat(field!(arm.pat)); + match arm.value.guard { + None => out!("if {arm}.guard.is_none();"), + Some(hir::Guard::If(expr)) => { + bind!(self, expr); + out!("if let Some(Guard::If({expr})) = {arm}.guard;"); + self.expr(expr); + }, + Some(hir::Guard::IfLet(pat, expr)) => { + bind!(self, pat, expr); + out!("if let Some(Guard::IfLet({pat}, {expr}) = {arm}.guard;"); + self.pat(pat); + self.expr(expr); + }, + } + self.expr(field!(arm.body)); + } #[allow(clippy::too_many_lines)] - fn visit_expr(&mut self, expr: &Expr<'_>) { - print!(" if let ExprKind::"); - let current = format!("{}.kind", self.current); - match expr.kind { + fn expr(&self, expr: &Binding<&hir::Expr<'_>>) { + if let Some(higher::While { condition, body }) = higher::While::hir(expr.value) { + bind!(self, condition, body); + out!( + "if let Some(higher::While {{ condition: {condition}, body: {body} }}) \ + = higher::While::hir({expr});" + ); + self.expr(condition); + self.expr(body); + return; + } + + if let Some(higher::WhileLet { + let_pat, + let_expr, + if_then, + }) = higher::WhileLet::hir(expr.value) + { + bind!(self, let_pat, let_expr, if_then); + out!( + "if let Some(higher::WhileLet {{ let_pat: {let_pat}, let_expr: {let_expr}, if_then: {if_then} }}) \ + = higher::WhileLet::hir({expr});" + ); + self.pat(let_pat); + self.expr(let_expr); + self.expr(if_then); + return; + } + + if let Some(higher::ForLoop { pat, arg, body, .. }) = higher::ForLoop::hir(expr.value) { + bind!(self, pat, arg, body); + out!( + "if let Some(higher::ForLoop {{ pat: {pat}, arg: {arg}, body: {body}, .. }}) \ + = higher::ForLoop::hir({expr});" + ); + self.pat(pat); + self.expr(arg); + self.expr(body); + return; + } + + let kind = |kind| out!("if let ExprKind::{kind} = {expr}.kind;"); + macro_rules! kind { + ($($t:tt)*) => (kind(format_args!($($t)*))); + } + + match expr.value.kind { ExprKind::Let(pat, expr, _) => { - let let_pat = self.next("pat"); - let let_expr = self.next("expr"); - println!(" Let(ref {}, ref {}, _) = {};", let_pat, let_expr, current); - self.current = let_expr; - self.visit_expr(expr); - self.current = let_pat; - self.visit_pat(pat); + bind!(self, pat, expr); + kind!("Let({pat}, {expr}, _)"); + self.pat(pat); + self.expr(expr); }, ExprKind::Box(inner) => { - let inner_pat = self.next("inner"); - println!("Box(ref {}) = {};", inner_pat, current); - self.current = inner_pat; - self.visit_expr(inner); + bind!(self, inner); + kind!("Box({inner})"); + self.expr(inner); }, ExprKind::Array(elements) => { - let elements_pat = self.next("elements"); - println!("Array(ref {}) = {};", elements_pat, current); - println!(" if {}.len() == {};", elements_pat, elements.len()); - for (i, element) in elements.iter().enumerate() { - self.current = format!("{}[{}]", elements_pat, i); - self.visit_expr(element); - } + bind!(self, elements); + kind!("Array({elements})"); + self.slice(elements, |e| self.expr(e)); }, ExprKind::Call(func, args) => { - let func_pat = self.next("func"); - let args_pat = self.next("args"); - println!("Call(ref {}, ref {}) = {};", func_pat, args_pat, current); - self.current = func_pat; - self.visit_expr(func); - println!(" if {}.len() == {};", args_pat, args.len()); - for (i, arg) in args.iter().enumerate() { - self.current = format!("{}[{}]", args_pat, i); - self.visit_expr(arg); - } + bind!(self, func, args); + kind!("Call({func}, {args})"); + self.expr(func); + self.slice(args, |e| self.expr(e)); }, - ExprKind::MethodCall(_method_name, ref _generics, _args, ref _fn_span) => { - println!( - "MethodCall(ref method_name, ref generics, ref args, ref fn_span) = {};", - current - ); - println!(" // unimplemented: `ExprKind::MethodCall` is not further destructured at the moment"); + ExprKind::MethodCall(method_name, _, args, _) => { + bind!(self, method_name, args); + kind!("MethodCall({method_name}, _, {args}, _)"); + self.ident(field!(method_name.ident)); + self.slice(args, |e| self.expr(e)); }, ExprKind::Tup(elements) => { - let elements_pat = self.next("elements"); - println!("Tup(ref {}) = {};", elements_pat, current); - println!(" if {}.len() == {};", elements_pat, elements.len()); - for (i, element) in elements.iter().enumerate() { - self.current = format!("{}[{}]", elements_pat, i); - self.visit_expr(element); - } + bind!(self, elements); + kind!("Tup({elements})"); + self.slice(elements, |e| self.expr(e)); }, - ExprKind::Binary(ref op, left, right) => { - let op_pat = self.next("op"); - let left_pat = self.next("left"); - let right_pat = self.next("right"); - println!( - "Binary(ref {}, ref {}, ref {}) = {};", - op_pat, left_pat, right_pat, current - ); - println!(" if BinOpKind::{:?} == {}.node;", op.node, op_pat); - self.current = left_pat; - self.visit_expr(left); - self.current = right_pat; - self.visit_expr(right); + ExprKind::Binary(op, left, right) => { + bind!(self, op, left, right); + kind!("Binary({op}, {left}, {right})"); + out!("if BinOpKind::{:?} == {op}.node;", op.value.node); + self.expr(left); + self.expr(right); }, - ExprKind::Unary(ref op, inner) => { - let inner_pat = self.next("inner"); - println!("Unary(UnOp::{:?}, ref {}) = {};", op, inner_pat, current); - self.current = inner_pat; - self.visit_expr(inner); + ExprKind::Unary(op, inner) => { + bind!(self, inner); + kind!("Unary(UnOp::{op:?}, {inner})"); + self.expr(inner); }, ExprKind::Lit(ref lit) => { - let lit_pat = self.next("lit"); - println!("Lit(ref {}) = {};", lit_pat, current); - match lit.node { - LitKind::Bool(val) => println!(" if let LitKind::Bool({:?}) = {}.node;", val, lit_pat), - LitKind::Char(c) => println!(" if let LitKind::Char({:?}) = {}.node;", c, lit_pat), - LitKind::Err(val) => println!(" if let LitKind::Err({}) = {}.node;", val, lit_pat), - LitKind::Byte(b) => println!(" if let LitKind::Byte({}) = {}.node;", b, lit_pat), - // FIXME: also check int type - LitKind::Int(i, _) => println!(" if let LitKind::Int({}, _) = {}.node;", i, lit_pat), - LitKind::Float(_, LitFloatType::Suffixed(_)) => println!( - " if let LitKind::Float(_, LitFloatType::Suffixed(_)) = {}.node;", - lit_pat - ), - LitKind::Float(_, LitFloatType::Unsuffixed) => println!( - " if let LitKind::Float(_, LitFloatType::Unsuffixed) = {}.node;", - lit_pat - ), - LitKind::ByteStr(ref vec) => { - let vec_pat = self.next("vec"); - println!(" if let LitKind::ByteStr(ref {}) = {}.node;", vec_pat, lit_pat); - println!(" if let [{:?}] = **{};", vec, vec_pat); - }, - LitKind::Str(ref text, _) => { - let str_pat = self.next("s"); - println!(" if let LitKind::Str(ref {}, _) = {}.node;", str_pat, lit_pat); - println!(" if {}.as_str() == {:?}", str_pat, &*text.as_str()); - }, - } + bind!(self, lit); + kind!("Lit(ref {lit})"); + self.lit(lit); }, - ExprKind::Cast(expr, ty) => { - let cast_pat = self.next("expr"); - let cast_ty = self.next("cast_ty"); - let qp_label = self.next("qp"); - - println!("Cast(ref {}, ref {}) = {};", cast_pat, cast_ty, current); - if let TyKind::Path(ref qp) = ty.kind { - println!(" if let TyKind::Path(ref {}) = {}.kind;", qp_label, cast_ty); - self.current = qp_label; - self.print_qpath(qp); + ExprKind::Cast(expr, cast_ty) => { + bind!(self, expr, cast_ty); + kind!("Cast({expr}, {cast_ty})"); + if let TyKind::Path(ref qpath) = cast_ty.value.kind { + bind!(self, qpath); + out!("if let TyKind::Path(ref {qpath}) = {cast_ty}.kind;"); + self.qpath(qpath); } - self.current = cast_pat; - self.visit_expr(expr); + self.expr(expr); }, ExprKind::Type(expr, _ty) => { - let cast_pat = self.next("expr"); - println!("Type(ref {}, _) = {};", cast_pat, current); - self.current = cast_pat; - self.visit_expr(expr); + bind!(self, expr); + kind!("Type({expr}, _)"); + self.expr(expr); }, - ExprKind::Loop(body, _, des, _) => { - let body_pat = self.next("body"); - let label_pat = self.next("label"); - println!( - "Loop(ref {}, ref {}, LoopSource::{:?}) = {};", - body_pat, label_pat, des, current - ); - self.current = body_pat; - self.visit_block(body); + ExprKind::Loop(body, label, des, _) => { + bind!(self, body); + opt_bind!(self, label); + kind!("Loop({body}, {label}, LoopSource::{des:?}, _)"); + self.block(body); + label.if_some(|l| self.ident(field!(l.ident))); }, - ExprKind::If(cond, then, ref opt_else) => { - let cond_pat = self.next("cond"); - let then_pat = self.next("then"); - if let Some(else_) = *opt_else { - let else_pat = self.next("else_"); - println!( - "If(ref {}, ref {}, Some(ref {})) = {};", - cond_pat, then_pat, else_pat, current - ); - self.current = else_pat; - self.visit_expr(else_); - } else { - println!("If(ref {}, ref {}, None) = {};", cond_pat, then_pat, current); - } - self.current = cond_pat; - self.visit_expr(cond); - self.current = then_pat; - self.visit_expr(then); + ExprKind::If(cond, then, else_expr) => { + bind!(self, cond, then); + opt_bind!(self, else_expr); + kind!("If({cond}, {then}, {else_expr})"); + self.expr(cond); + self.expr(then); + else_expr.if_some(|e| self.expr(e)); }, - ExprKind::Match(expr, arms, des) => { - let expr_pat = self.next("expr"); - let arms_pat = self.next("arms"); - println!( - "Match(ref {}, ref {}, MatchSource::{:?}) = {};", - expr_pat, arms_pat, des, current - ); - self.current = expr_pat; - self.visit_expr(expr); - println!(" if {}.len() == {};", arms_pat, arms.len()); - for (i, arm) in arms.iter().enumerate() { - self.current = format!("{}[{}].body", arms_pat, i); - self.visit_expr(arm.body); - if let Some(ref guard) = arm.guard { - let guard_pat = self.next("guard"); - println!(" if let Some(ref {}) = {}[{}].guard;", guard_pat, arms_pat, i); - match guard { - hir::Guard::If(if_expr) => { - let if_expr_pat = self.next("expr"); - println!(" if let Guard::If(ref {}) = {};", if_expr_pat, guard_pat); - self.current = if_expr_pat; - self.visit_expr(if_expr); - }, - hir::Guard::IfLet(if_let_pat, if_let_expr) => { - let if_let_pat_pat = self.next("pat"); - let if_let_expr_pat = self.next("expr"); - println!( - " if let Guard::IfLet(ref {}, ref {}) = {};", - if_let_pat_pat, if_let_expr_pat, guard_pat - ); - self.current = if_let_expr_pat; - self.visit_expr(if_let_expr); - self.current = if_let_pat_pat; - self.visit_pat(if_let_pat); - }, - } - } - self.current = format!("{}[{}].pat", arms_pat, i); - self.visit_pat(arm.pat); - } + ExprKind::Match(scrutinee, arms, des) => { + bind!(self, scrutinee, arms); + kind!("Match({scrutinee}, {arms}, MatchSource::{des:?})"); + self.expr(scrutinee); + self.slice(arms, |arm| self.arm(arm)); }, - ExprKind::Closure(ref _capture_clause, _func, _, _, _) => { - println!("Closure(ref capture_clause, ref func, _, _, _) = {};", current); - println!(" // unimplemented: `ExprKind::Closure` is not further destructured at the moment"); + ExprKind::Closure(capture_by, fn_decl, body_id, _, movability) => { + let movability = OptionPat::new(movability.map(|m| format!("Movability::{m:?}"))); + + let ret_ty = match fn_decl.output { + FnRetTy::DefaultReturn(_) => "FnRetTy::DefaultReturn(_)", + FnRetTy::Return(_) => "FnRetTy::Return(_ty)", + }; + + bind!(self, fn_decl, body_id); + kind!("Closure(CaptureBy::{capture_by:?}, {fn_decl}, {body_id}, _, {movability})"); + out!("if let {ret_ty} = {fn_decl}.output;"); + self.body(body_id); }, - ExprKind::Yield(sub, _) => { - let sub_pat = self.next("sub"); - println!("Yield(ref sub) = {};", current); - self.current = sub_pat; - self.visit_expr(sub); + ExprKind::Yield(sub, source) => { + bind!(self, sub); + kind!("Yield(sub, YieldSource::{source:?})"); + self.expr(sub); }, - ExprKind::Block(block, _) => { - let block_pat = self.next("block"); - println!("Block(ref {}) = {};", block_pat, current); - self.current = block_pat; - self.visit_block(block); + ExprKind::Block(block, label) => { + bind!(self, block); + opt_bind!(self, label); + kind!("Block({block}, {label})"); + self.block(block); + label.if_some(|l| self.ident(field!(l.ident))); }, ExprKind::Assign(target, value, _) => { - let target_pat = self.next("target"); - let value_pat = self.next("value"); - println!( - "Assign(ref {}, ref {}, ref _span) = {};", - target_pat, value_pat, current - ); - self.current = target_pat; - self.visit_expr(target); - self.current = value_pat; - self.visit_expr(value); + bind!(self, target, value); + kind!("Assign({target}, {value}, _span)"); + self.expr(target); + self.expr(value); }, - ExprKind::AssignOp(ref op, target, value) => { - let op_pat = self.next("op"); - let target_pat = self.next("target"); - let value_pat = self.next("value"); - println!( - "AssignOp(ref {}, ref {}, ref {}) = {};", - op_pat, target_pat, value_pat, current - ); - println!(" if BinOpKind::{:?} == {}.node;", op.node, op_pat); - self.current = target_pat; - self.visit_expr(target); - self.current = value_pat; - self.visit_expr(value); + ExprKind::AssignOp(op, target, value) => { + bind!(self, op, target, value); + kind!("AssignOp({op}, {target}, {value})"); + out!("if BinOpKind::{:?} == {op}.node;", op.value.node); + self.expr(target); + self.expr(value); }, - ExprKind::Field(object, ref field_ident) => { - let obj_pat = self.next("object"); - let field_name_pat = self.next("field_name"); - println!("Field(ref {}, ref {}) = {};", obj_pat, field_name_pat, current); - println!(" if {}.as_str() == {:?}", field_name_pat, field_ident.as_str()); - self.current = obj_pat; - self.visit_expr(object); + ExprKind::Field(object, field_name) => { + bind!(self, object, field_name); + kind!("Field({object}, {field_name})"); + self.ident(field_name); + self.expr(object); }, ExprKind::Index(object, index) => { - let object_pat = self.next("object"); - let index_pat = self.next("index"); - println!("Index(ref {}, ref {}) = {};", object_pat, index_pat, current); - self.current = object_pat; - self.visit_expr(object); - self.current = index_pat; - self.visit_expr(index); + bind!(self, object, index); + kind!("Index({object}, {index})"); + self.expr(object); + self.expr(index); }, - ExprKind::Path(ref path) => { - let path_pat = self.next("path"); - println!("Path(ref {}) = {};", path_pat, current); - self.current = path_pat; - self.print_qpath(path); + ExprKind::Path(ref qpath) => { + bind!(self, qpath); + kind!("Path(ref {qpath})"); + self.qpath(qpath); }, ExprKind::AddrOf(kind, mutability, inner) => { - let inner_pat = self.next("inner"); - println!( - "AddrOf(BorrowKind::{:?}, Mutability::{:?}, ref {}) = {};", - kind, mutability, inner_pat, current - ); - self.current = inner_pat; - self.visit_expr(inner); + bind!(self, inner); + kind!("AddrOf(BorrowKind::{kind:?}, Mutability::{mutability:?}, {inner})"); + self.expr(inner); }, - ExprKind::Break(ref _destination, ref opt_value) => { - let destination_pat = self.next("destination"); - if let Some(value) = *opt_value { - let value_pat = self.next("value"); - println!("Break(ref {}, Some(ref {})) = {};", destination_pat, value_pat, current); - self.current = value_pat; - self.visit_expr(value); - } else { - println!("Break(ref {}, None) = {};", destination_pat, current); - } - // FIXME: implement label printing + ExprKind::Break(destination, value) => { + bind!(self, destination); + opt_bind!(self, value); + kind!("Break({destination}, {value})"); + self.destination(destination); + value.if_some(|e| self.expr(e)); }, - ExprKind::Continue(ref _destination) => { - let destination_pat = self.next("destination"); - println!("Again(ref {}) = {};", destination_pat, current); - // FIXME: implement label printing + ExprKind::Continue(destination) => { + bind!(self, destination); + kind!("Continue({destination})"); + self.destination(destination); }, - ExprKind::Ret(ref opt_value) => { - if let Some(value) = *opt_value { - let value_pat = self.next("value"); - println!("Ret(Some(ref {})) = {};", value_pat, current); - self.current = value_pat; - self.visit_expr(value); - } else { - println!("Ret(None) = {};", current); - } + ExprKind::Ret(value) => { + opt_bind!(self, value); + kind!("Ret({value})"); + value.if_some(|e| self.expr(e)); }, ExprKind::InlineAsm(_) => { - println!("InlineAsm(_) = {};", current); - println!(" // unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment"); + kind!("InlineAsm(_)"); + out!("// unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment"); }, ExprKind::LlvmInlineAsm(_) => { - println!("LlvmInlineAsm(_) = {};", current); - println!(" // unimplemented: `ExprKind::LlvmInlineAsm` is not further destructured at the moment"); + kind!("LlvmInlineAsm(_)"); + out!("// unimplemented: `ExprKind::LlvmInlineAsm` is not further destructured at the moment"); }, - ExprKind::Struct(path, fields, ref opt_base) => { - let path_pat = self.next("path"); - let fields_pat = self.next("fields"); - if let Some(base) = *opt_base { - let base_pat = self.next("base"); - println!( - "Struct(ref {}, ref {}, Some(ref {})) = {};", - path_pat, fields_pat, base_pat, current - ); - self.current = base_pat; - self.visit_expr(base); - } else { - println!("Struct(ref {}, ref {}, None) = {};", path_pat, fields_pat, current); - } - self.current = path_pat; - self.print_qpath(path); - println!(" if {}.len() == {};", fields_pat, fields.len()); - println!(" // unimplemented: field checks"); + ExprKind::Struct(qpath, fields, base) => { + bind!(self, qpath, fields); + opt_bind!(self, base); + kind!("Struct({qpath}, {fields}, {base})"); + self.qpath(qpath); + self.slice(fields, |field| { + self.ident(field!(field.ident)); + self.expr(field!(field.expr)); + }); + base.if_some(|e| self.expr(e)); }, - ExprKind::ConstBlock(_) => { - let value_pat = self.next("value"); - println!("Const({})", value_pat); - self.current = value_pat; - }, - // FIXME: compute length (needs type info) - ExprKind::Repeat(value, _) => { - let value_pat = self.next("value"); - println!("Repeat(ref {}, _) = {};", value_pat, current); - println!("// unimplemented: repeat count check"); - self.current = value_pat; - self.visit_expr(value); - }, - ExprKind::Err => { - println!("Err = {}", current); + ExprKind::ConstBlock(_) => kind!("ConstBlock(_)"), + ExprKind::Repeat(value, length) => { + bind!(self, value, length); + kind!("Repeat({value}, {length})"); + self.expr(value); + self.body(field!(length.body)); }, + ExprKind::Err => kind!("Err"), ExprKind::DropTemps(expr) => { - let expr_pat = self.next("expr"); - println!("DropTemps(ref {}) = {};", expr_pat, current); - self.current = expr_pat; - self.visit_expr(expr); + bind!(self, expr); + kind!("DropTemps({expr})"); + self.expr(expr); }, } } - fn visit_block(&mut self, block: &Block<'_>) { - println!(" if {}.stmts.len() == {};", self.current, block.stmts.len()); - let block_name = self.current.clone(); - for (i, stmt) in block.stmts.iter().enumerate() { - self.current = format!("{}.stmts[{}]", block_name, i); - self.visit_stmt(stmt); - } - if let Some(expr) = block.expr { - self.current = self.next("trailing_expr"); - println!(" if let Some({}) = &{}.expr;", self.current, block_name); - self.visit_expr(expr); - } else { - println!(" if {}.expr.is_none();", block_name); - } + fn block(&self, block: &Binding<&hir::Block<'_>>) { + self.slice(field!(block.stmts), |stmt| self.stmt(stmt)); + self.option(field!(block.expr), "trailing_expr", |expr| { + self.expr(expr); + }); } - #[allow(clippy::too_many_lines)] - fn visit_pat(&mut self, pat: &Pat<'_>) { - print!(" if let PatKind::"); - let current = format!("{}.kind", self.current); - match pat.kind { - PatKind::Wild => println!("Wild = {};", current), - PatKind::Binding(anno, .., ident, ref sub) => { - let anno_pat = &format!("BindingAnnotation::{:?}", anno); - let name_pat = self.next("name"); - if let Some(sub) = *sub { - let sub_pat = self.next("sub"); - println!( - "Binding({}, _, {}, Some(ref {})) = {};", - anno_pat, name_pat, sub_pat, current - ); - self.current = sub_pat; - self.visit_pat(sub); - } else { - println!("Binding({}, _, {}, None) = {};", anno_pat, name_pat, current); - } - println!(" if {}.as_str() == \"{}\";", name_pat, ident.as_str()); + fn body(&self, body_id: &Binding) { + let expr = &self.cx.tcx.hir().body(body_id.value).value; + bind!(self, expr); + out!("let {expr} = &cx.tcx.hir().body({body_id}).value;"); + self.expr(expr); + } + + fn pat(&self, pat: &Binding<&hir::Pat<'_>>) { + let kind = |kind| out!("if let PatKind::{kind} = {pat}.kind;"); + macro_rules! kind { + ($($t:tt)*) => (kind(format_args!($($t)*))); + } + + match pat.value.kind { + PatKind::Wild => kind!("Wild"), + PatKind::Binding(anno, .., name, sub) => { + bind!(self, name); + opt_bind!(self, sub); + kind!("Binding(BindingAnnotation::{anno:?}, _, {name}, {sub})"); + self.ident(name); + sub.if_some(|p| self.pat(p)); }, - PatKind::Struct(ref path, fields, ignore) => { - let path_pat = self.next("path"); - let fields_pat = self.next("fields"); - println!( - "Struct(ref {}, ref {}, {}) = {};", - path_pat, fields_pat, ignore, current - ); - self.current = path_pat; - self.print_qpath(path); - println!(" if {}.len() == {};", fields_pat, fields.len()); - println!(" // unimplemented: field checks"); + PatKind::Struct(ref qpath, fields, ignore) => { + bind!(self, qpath, fields); + kind!("Struct(ref {qpath}, {fields}, {ignore})"); + self.qpath(qpath); + self.slice(fields, |field| { + self.ident(field!(field.ident)); + self.pat(field!(field.pat)); + }); }, PatKind::Or(fields) => { - let fields_pat = self.next("fields"); - println!("Or(ref {}) = {};", fields_pat, current); - println!(" if {}.len() == {};", fields_pat, fields.len()); - println!(" // unimplemented: field checks"); + bind!(self, fields); + kind!("Or({fields})"); + self.slice(fields, |pat| self.pat(pat)); }, - PatKind::TupleStruct(ref path, fields, skip_pos) => { - let path_pat = self.next("path"); - let fields_pat = self.next("fields"); - println!( - "TupleStruct(ref {}, ref {}, {:?}) = {};", - path_pat, fields_pat, skip_pos, current - ); - self.current = path_pat; - self.print_qpath(path); - println!(" if {}.len() == {};", fields_pat, fields.len()); - println!(" // unimplemented: field checks"); + PatKind::TupleStruct(ref qpath, fields, skip_pos) => { + bind!(self, qpath, fields); + kind!("TupleStruct(ref {qpath}, {fields}, {skip_pos:?})"); + self.qpath(qpath); + self.slice(fields, |pat| self.pat(pat)); }, - PatKind::Path(ref path) => { - let path_pat = self.next("path"); - println!("Path(ref {}) = {};", path_pat, current); - self.current = path_pat; - self.print_qpath(path); + PatKind::Path(ref qpath) => { + bind!(self, qpath); + kind!("Path(ref {qpath})"); + self.qpath(qpath); }, PatKind::Tuple(fields, skip_pos) => { - let fields_pat = self.next("fields"); - println!("Tuple(ref {}, {:?}) = {};", fields_pat, skip_pos, current); - println!(" if {}.len() == {};", fields_pat, fields.len()); - println!(" // unimplemented: field checks"); + bind!(self, fields); + kind!("Tuple({fields}, {skip_pos:?})"); + self.slice(fields, |field| self.pat(field)); }, PatKind::Box(pat) => { - let pat_pat = self.next("pat"); - println!("Box(ref {}) = {};", pat_pat, current); - self.current = pat_pat; - self.visit_pat(pat); + bind!(self, pat); + kind!("Box({pat})"); + self.pat(pat); }, PatKind::Ref(pat, muta) => { - let pat_pat = self.next("pat"); - println!("Ref(ref {}, Mutability::{:?}) = {};", pat_pat, muta, current); - self.current = pat_pat; - self.visit_pat(pat); + bind!(self, pat); + kind!("Ref({pat}, Mutability::{muta:?})"); + self.pat(pat); }, PatKind::Lit(lit_expr) => { - let lit_expr_pat = self.next("lit_expr"); - println!("Lit(ref {}) = {}", lit_expr_pat, current); - self.current = lit_expr_pat; - self.visit_expr(lit_expr); + bind!(self, lit_expr); + kind!("Lit({lit_expr})"); + self.expr(lit_expr); }, - PatKind::Range(ref start, ref end, end_kind) => { - let start_pat = self.next("start"); - let end_pat = self.next("end"); - println!( - "Range(ref {}, ref {}, RangeEnd::{:?}) = {};", - start_pat, end_pat, end_kind, current - ); - self.current = start_pat; - walk_list!(self, visit_expr, start); - self.current = end_pat; - walk_list!(self, visit_expr, end); + PatKind::Range(start, end, end_kind) => { + opt_bind!(self, start, end); + kind!("Range({start}, {end}, RangeEnd::{end_kind:?})"); + start.if_some(|e| self.expr(e)); + end.if_some(|e| self.expr(e)); }, - PatKind::Slice(start, ref middle, end) => { - let start_pat = self.next("start"); - let end_pat = self.next("end"); - if let Some(middle) = middle { - let middle_pat = self.next("middle"); - println!( - "Slice(ref {}, Some(ref {}), ref {}) = {};", - start_pat, middle_pat, end_pat, current - ); - self.current = middle_pat; - self.visit_pat(middle); - } else { - println!("Slice(ref {}, None, ref {}) = {};", start_pat, end_pat, current); - } - println!(" if {}.len() == {};", start_pat, start.len()); - for (i, pat) in start.iter().enumerate() { - self.current = format!("{}[{}]", start_pat, i); - self.visit_pat(pat); - } - println!(" if {}.len() == {};", end_pat, end.len()); - for (i, pat) in end.iter().enumerate() { - self.current = format!("{}[{}]", end_pat, i); - self.visit_pat(pat); - } + PatKind::Slice(start, middle, end) => { + bind!(self, start, end); + opt_bind!(self, middle); + kind!("Slice({start}, {middle}, {end})"); + middle.if_some(|p| self.pat(p)); + self.slice(start, |pat| self.pat(pat)); + self.slice(end, |pat| self.pat(pat)); }, } } - fn visit_stmt(&mut self, s: &Stmt<'_>) { - print!(" if let StmtKind::"); - let current = format!("{}.kind", self.current); - match s.kind { - // A local (let) binding: + fn stmt(&self, stmt: &Binding<&hir::Stmt<'_>>) { + let kind = |kind| out!("if let StmtKind::{kind} = {stmt}.kind;"); + macro_rules! kind { + ($($t:tt)*) => (kind(format_args!($($t)*))); + } + + match stmt.value.kind { StmtKind::Local(local) => { - let local_pat = self.next("local"); - println!("Local(ref {}) = {};", local_pat, current); - if let Some(init) = local.init { - let init_pat = self.next("init"); - println!(" if let Some(ref {}) = {}.init;", init_pat, local_pat); - self.current = init_pat; - self.visit_expr(init); - } - self.current = format!("{}.pat", local_pat); - self.visit_pat(local.pat); + bind!(self, local); + kind!("Local({local})"); + self.option(field!(local.init), "init", |init| { + self.expr(init); + }); + self.pat(field!(local.pat)); }, - // An item binding: - StmtKind::Item(_) => { - println!("Item(item_id) = {};", current); - }, - - // Expr without trailing semi-colon (must have unit type): + StmtKind::Item(_) => kind!("Item(item_id)"), StmtKind::Expr(e) => { - let e_pat = self.next("e"); - println!("Expr(ref {}, _) = {}", e_pat, current); - self.current = e_pat; - self.visit_expr(e); + bind!(self, e); + kind!("Expr({e})"); + self.expr(e); }, - - // Expr with trailing semi-colon (may have any type): StmtKind::Semi(e) => { - let e_pat = self.next("e"); - println!("Semi(ref {}, _) = {}", e_pat, current); - self.current = e_pat; - self.visit_expr(e); + bind!(self, e); + kind!("Semi({e})"); + self.expr(e); }, } } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } } fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool { @@ -741,30 +697,29 @@ fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool { get_attr(cx.sess(), attrs, "author").count() > 0 } -fn print_path(path: &QPath<'_>, first: &mut bool) { - match *path { - QPath::Resolved(_, path) => { - for segment in path.segments { - if *first { - *first = false; - } else { - print!(", "); +fn path_to_string(path: &QPath<'_>) -> String { + fn inner(s: &mut String, path: &QPath<'_>) { + match *path { + QPath::Resolved(_, path) => { + for (i, segment) in path.segments.iter().enumerate() { + if i > 0 { + *s += ", "; + } + write!(s, "{:?}", segment.ident.as_str()).unwrap(); } - print!("{:?}", segment.ident.as_str()); - } - }, - QPath::TypeRelative(ty, segment) => match ty.kind { - hir::TyKind::Path(ref inner_path) => { - print_path(inner_path, first); - if *first { - *first = false; - } else { - print!(", "); - } - print!("{:?}", segment.ident.as_str()); }, - ref other => print!("/* unimplemented: {:?}*/", other), - }, - QPath::LangItem(..) => panic!("print_path: called for lang item qpath"), + QPath::TypeRelative(ty, segment) => match &ty.kind { + hir::TyKind::Path(inner_path) => { + inner(s, inner_path); + *s += ", "; + write!(s, "{:?}", segment.ident.as_str()).unwrap(); + }, + other => write!(s, "/* unimplemented: {:?}*/", other).unwrap(), + }, + QPath::LangItem(..) => panic!("path_to_string: called for lang item qpath"), + } } + let mut s = String::new(); + inner(&mut s, path); + s } diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 122a5ce3fc8..1a170807980 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -148,7 +148,7 @@ define_Conf! { /// /// Suppress lints whenever the suggested change would cause breakage for other crates. (avoid_breaking_exported_api: bool = true), - /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT. + /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE. /// /// The minimum rust version that the project supports (msrv: Option = None), @@ -296,6 +296,12 @@ define_Conf! { /// /// Whether to apply the raw pointer heuristic to determine if a type is `Send`. (enable_raw_pointer_heuristic_for_send: bool = true), + /// Lint: INDEX_REFUTABLE_SLICE. + /// + /// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in + /// the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed. + /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements. + (max_suggested_slice_pattern_length: u64 = 3), } /// Search for the configuration file. diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index 824ec53ab9c..1f97c8ba7e6 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -8,6 +8,7 @@ use clippy_utils::{ paths, SpanlessEq, }; use if_chain::if_chain; +use rustc_ast as ast; use rustc_ast::ast::{Crate, ItemKind, LitKind, ModKind, NodeId}; use rustc_ast::visit::FnKind; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -25,10 +26,11 @@ use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintCon use rustc_middle::hir::map::Map; use rustc_middle::mir::interpret::ConstValue; use rustc_middle::ty; +use rustc_semver::RustcVersion; use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::Spanned; use rustc_span::symbol::{Symbol, SymbolStr}; -use rustc_span::{BytePos, Span}; +use rustc_span::{sym, BytePos, Span}; use rustc_typeck::hir_ty_to_ty; use std::borrow::{Borrow, Cow}; @@ -314,6 +316,27 @@ declare_clippy_lint! { "non-idiomatic `if_chain!` usage" } +declare_clippy_lint! { + /// ### What it does + /// Checks for invalid `clippy::version` attributes. + /// + /// Valid values are: + /// * "pre 1.29.0" + /// * any valid semantic version + pub INVALID_CLIPPY_VERSION_ATTRIBUTE, + internal, + "found an invalid `clippy::version` attribute" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for declared clippy lints without the `clippy::version` attribute. + /// + pub MISSING_CLIPPY_VERSION_ATTRIBUTE, + internal, + "found clippy lint without `clippy::version` attribute" +} + declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]); impl EarlyLintPass for ClippyLintsInternal { @@ -351,7 +374,7 @@ pub struct LintWithoutLintPass { registered_lints: FxHashSet, } -impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS]); +impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE, MISSING_CLIPPY_VERSION_ATTRIBUTE]); impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { @@ -361,6 +384,8 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { if let hir::ItemKind::Static(ty, Mutability::Not, body_id) = item.kind { if is_lint_ref_type(cx, 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; @@ -458,6 +483,57 @@ fn is_lint_ref_type<'tcx>(cx: &LateContext<'tcx>, ty: &Ty<'_>) -> bool { false } +fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'_>) { + if let Some(value) = extract_clippy_version_value(cx, item) { + // The `sym!` macro doesn't work as it only expects a single token. + // It's better to keep it this way and have a direct `Symbol::intern` call here. + if value == Symbol::intern("pre 1.29.0") { + return; + } + + if RustcVersion::parse(&*value.as_str()).is_err() { + span_lint_and_help( + cx, + INVALID_CLIPPY_VERSION_ATTRIBUTE, + item.span, + "this item has an invalid `clippy::version` attribute", + None, + "please use a valid sematic version, see `doc/adding_lints.md`", + ); + } + } else { + span_lint_and_help( + cx, + MISSING_CLIPPY_VERSION_ATTRIBUTE, + item.span, + "this lint is missing the `clippy::version` attribute or version value", + None, + "please use a `clippy::version` attribute, see `doc/adding_lints.md`", + ); + } +} + +/// This function extracts the version value of a `clippy::version` attribute if the given value has +/// one +fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<'_>) -> Option { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + attrs.iter().find_map(|attr| { + if_chain! { + // Identify attribute + if let ast::AttrKind::Normal(ref attr_kind, _) = &attr.kind; + if let [tool_name, attr_name] = &attr_kind.path.segments[..]; + if tool_name.ident.name == sym::clippy; + if attr_name.ident.name == sym::version; + if let Some(version) = attr.value_str(); + then { + Some(version) + } else { + None + } + } + }) +} + struct LintCollector<'a, 'tcx> { output: &'a mut FxHashSet, cx: &'a LateContext<'tcx>, diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 99cf4c1ed40..8051c58bad7 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -25,7 +25,7 @@ use std::fs::{self, OpenOptions}; use std::io::prelude::*; use std::path::Path; -use crate::utils::internal_lints::is_lint_ref_type; +use crate::utils::internal_lints::{extract_clippy_version_value, is_lint_ref_type}; use clippy_utils::{ diagnostics::span_lint, last_path_segment, match_def_path, match_function_call, match_path, paths, ty::match_type, ty::walk_ptrs_ty_depth, @@ -116,6 +116,8 @@ const DEPRECATED_LINT_TYPE: [&str; 3] = ["clippy_lints", "deprecated_lints", "Cl const APPLICABILITY_NAME_INDEX: usize = 2; /// This applicability will be set for unresolved applicability values. const APPLICABILITY_UNRESOLVED_STR: &str = "Unresolved"; +/// The version that will be displayed if none has been defined +const VERION_DEFAULT_STR: &str = "Unknown"; declare_clippy_lint! { /// ### What it does @@ -144,6 +146,7 @@ declare_clippy_lint! { /// "docs": " ### What it does\nCollects metadata about clippy lints for the website. [...] " /// } /// ``` + #[clippy::version = "1.56.0"] pub INTERNAL_METADATA_COLLECTOR, internal_warn, "A busy bee collection metadata about lints" @@ -215,18 +218,27 @@ struct LintMetadata { group: String, level: String, docs: String, + version: String, /// This field is only used in the output and will only be /// mapped shortly before the actual output. applicability: Option, } impl LintMetadata { - fn new(id: String, id_span: SerializableSpan, group: String, level: &'static str, docs: String) -> Self { + fn new( + id: String, + id_span: SerializableSpan, + group: String, + level: &'static str, + version: String, + docs: String, + ) -> Self { Self { id, id_span, group, level: level.to_string(), + version, docs, applicability: None, } @@ -410,12 +422,14 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { if let Some(configuration_section) = self.get_lint_configs(&lint_name) { docs.push_str(&configuration_section); } + let version = get_lint_version(cx, item); self.lints.push(LintMetadata::new( lint_name, SerializableSpan::from_item(cx, item), group, level, + version, docs, )); } @@ -429,11 +443,14 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { // Metadata the little we can get from a deprecated lint if let Some(docs) = extract_attr_docs_or_lint(cx, item); then { + let version = get_lint_version(cx, item); + self.lints.push(LintMetadata::new( lint_name, SerializableSpan::from_item(cx, item), DEPRECATED_LINT_GROUP_STR.to_string(), DEPRECATED_LINT_LEVEL, + version, docs, )); } @@ -552,6 +569,13 @@ fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option { Some(docs) } +fn get_lint_version(cx: &LateContext<'_>, item: &Item<'_>) -> String { + extract_clippy_version_value(cx, item).map_or_else( + || VERION_DEFAULT_STR.to_string(), + |version| version.as_str().to_string(), + ) +} + fn get_lint_group_and_level_or_lint( cx: &LateContext<'_>, lint_name: &str, @@ -663,7 +687,6 @@ fn extract_emission_info<'hir>( applicability = resolve_applicability(cx, arg); } else if arg_ty.is_closure() { multi_part |= check_is_multi_part(cx, arg); - // TODO xFrednet 2021-03-01: don't use or_else but rather a comparison applicability = applicability.or_else(|| resolve_applicability(cx, arg)); } } diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/vec.rs index d3234b5758a..79e7410c3a8 100644 --- a/clippy_lints/src/vec.rs +++ b/clippy_lints/src/vec.rs @@ -36,6 +36,7 @@ declare_clippy_lint! { /// // Good /// foo(&[1, 2]); /// ``` + #[clippy::version = "pre 1.29.0"] pub USELESS_VEC, perf, "useless `vec!`" diff --git a/clippy_lints/src/vec_init_then_push.rs b/clippy_lints/src/vec_init_then_push.rs index b92b6ca4f43..1bc0eb6303c 100644 --- a/clippy_lints/src/vec_init_then_push.rs +++ b/clippy_lints/src/vec_init_then_push.rs @@ -27,6 +27,7 @@ declare_clippy_lint! { /// ```rust /// let v = vec![0]; /// ``` + #[clippy::version = "1.51.0"] pub VEC_INIT_THEN_PUSH, perf, "`push` immediately after `Vec` creation" diff --git a/clippy_lints/src/vec_resize_to_zero.rs b/clippy_lints/src/vec_resize_to_zero.rs index 5c0429db6b8..3441d9ccdfa 100644 --- a/clippy_lints/src/vec_resize_to_zero.rs +++ b/clippy_lints/src/vec_resize_to_zero.rs @@ -20,6 +20,7 @@ declare_clippy_lint! { /// ```rust /// vec!(1, 2, 3, 4, 5).resize(0, 5) /// ``` + #[clippy::version = "1.46.0"] pub VEC_RESIZE_TO_ZERO, correctness, "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake" diff --git a/clippy_lints/src/verbose_file_reads.rs b/clippy_lints/src/verbose_file_reads.rs index e07c12f4f16..ebdaff1e676 100644 --- a/clippy_lints/src/verbose_file_reads.rs +++ b/clippy_lints/src/verbose_file_reads.rs @@ -27,6 +27,7 @@ declare_clippy_lint! { /// # use std::fs; /// let mut bytes = fs::read("foo.txt").unwrap(); /// ``` + #[clippy::version = "1.44.0"] pub VERBOSE_FILE_READS, restriction, "use of `File::read_to_end` or `File::read_to_string`" diff --git a/clippy_lints/src/wildcard_dependencies.rs b/clippy_lints/src/wildcard_dependencies.rs index d0c98b6bd79..80d7b8a1b6d 100644 --- a/clippy_lints/src/wildcard_dependencies.rs +++ b/clippy_lints/src/wildcard_dependencies.rs @@ -20,6 +20,7 @@ declare_clippy_lint! { /// [dependencies] /// regex = "*" /// ``` + #[clippy::version = "1.32.0"] pub WILDCARD_DEPENDENCIES, cargo, "wildcard dependencies being used" diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index bafb9d3e3b1..832da66a536 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_test_module_or_function; use clippy_utils::source::{snippet, snippet_with_applicability}; -use clippy_utils::{in_macro, is_test_module_or_function}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{ @@ -34,6 +34,7 @@ declare_clippy_lint! { /// use std::cmp::Ordering; /// foo(Ordering::Less) /// ``` + #[clippy::version = "pre 1.29.0"] pub ENUM_GLOB_USE, pedantic, "use items that import all variants of an enum" @@ -86,6 +87,7 @@ declare_clippy_lint! { /// /// foo(); /// ``` + #[clippy::version = "1.43.0"] pub WILDCARD_IMPORTS, pedantic, "lint `use _::*` statements" @@ -196,7 +198,7 @@ impl LateLintPass<'_> for WildcardImports { impl WildcardImports { fn check_exceptions(&self, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool { - in_macro(item.span) + item.span.from_expansion() || is_prelude_import(segments) || (is_super_only_import(segments) && self.test_modules_deep > 0) } diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index b412e15ae4f..bfa9407285b 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -31,6 +31,7 @@ declare_clippy_lint! { /// // Good /// println!(); /// ``` + #[clippy::version = "pre 1.29.0"] pub PRINTLN_EMPTY_STRING, style, "using `println!(\"\")` with an empty string" @@ -55,6 +56,7 @@ declare_clippy_lint! { /// # let name = "World"; /// println!("Hello {}!", name); /// ``` + #[clippy::version = "pre 1.29.0"] pub PRINT_WITH_NEWLINE, style, "using `print!()` with a format string that ends in a single newline" @@ -76,6 +78,7 @@ declare_clippy_lint! { /// ```rust /// println!("Hello world!"); /// ``` + #[clippy::version = "pre 1.29.0"] pub PRINT_STDOUT, restriction, "printing on stdout" @@ -97,6 +100,7 @@ declare_clippy_lint! { /// ```rust /// eprintln!("Hello world!"); /// ``` + #[clippy::version = "1.50.0"] pub PRINT_STDERR, restriction, "printing on stderr" @@ -116,6 +120,7 @@ declare_clippy_lint! { /// # let foo = "bar"; /// println!("{:?}", foo); /// ``` + #[clippy::version = "pre 1.29.0"] pub USE_DEBUG, restriction, "use of `Debug`-based formatting" @@ -142,6 +147,7 @@ declare_clippy_lint! { /// ```rust /// println!("foo"); /// ``` + #[clippy::version = "pre 1.29.0"] pub PRINT_LITERAL, style, "printing a literal with a format string" @@ -165,6 +171,7 @@ declare_clippy_lint! { /// // Good /// writeln!(buf); /// ``` + #[clippy::version = "pre 1.29.0"] pub WRITELN_EMPTY_STRING, style, "using `writeln!(buf, \"\")` with an empty string" @@ -191,6 +198,7 @@ declare_clippy_lint! { /// // Good /// writeln!(buf, "Hello {}!", name); /// ``` + #[clippy::version = "pre 1.29.0"] pub WRITE_WITH_NEWLINE, style, "using `write!()` with a format string that ends in a single newline" @@ -219,6 +227,7 @@ declare_clippy_lint! { /// // Good /// writeln!(buf, "foo"); /// ``` + #[clippy::version = "pre 1.29.0"] pub WRITE_LITERAL, style, "writing a literal with a format string" diff --git a/clippy_lints/src/zero_div_zero.rs b/clippy_lints/src/zero_div_zero.rs index e0746ce4d81..641681185a2 100644 --- a/clippy_lints/src/zero_div_zero.rs +++ b/clippy_lints/src/zero_div_zero.rs @@ -20,6 +20,7 @@ declare_clippy_lint! { /// // Good /// let nan = f32::NAN; /// ``` + #[clippy::version = "pre 1.29.0"] pub ZERO_DIVIDED_BY_ZERO, complexity, "usage of `0.0 / 0.0` to obtain NaN instead of `f32::NAN` or `f64::NAN`" diff --git a/clippy_lints/src/zero_sized_map_values.rs b/clippy_lints/src/zero_sized_map_values.rs index aa6b2614bbc..eb8436a501d 100644 --- a/clippy_lints/src/zero_sized_map_values.rs +++ b/clippy_lints/src/zero_sized_map_values.rs @@ -36,6 +36,7 @@ declare_clippy_lint! { /// todo!(); /// } /// ``` + #[clippy::version = "1.50.0"] pub ZERO_SIZED_MAP_VALUES, pedantic, "usage of map with zero-sized value type" diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 1b05a8a3504..8400bfbbd99 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -243,6 +243,7 @@ pub fn eq_item(l: &Item, r: &Item, mut eq_kind: impl FnMut(&K, &K) -> b eq_id(l.ident, r.ident) && over(&l.attrs, &r.attrs, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind) } +#[allow(clippy::too_many_lines)] // Just a big match statement pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { use ItemKind::*; match (l, r) { @@ -250,8 +251,20 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { (Use(l), Use(r)) => eq_use_tree(l, r), (Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), - (Fn(box ast::Fn { defaultness: ld, sig: lf, generics: lg, body: lb }), - Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, body: rb })) => { + ( + Fn(box ast::Fn { + defaultness: ld, + sig: lf, + generics: lg, + body: lb, + }), + Fn(box ast::Fn { + defaultness: rd, + sig: rf, + generics: rg, + body: rb, + }), + ) => { eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r)) }, (Mod(lu, lmk), Mod(ru, rmk)) => { @@ -267,8 +280,20 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { (ForeignMod(l), ForeignMod(r)) => { both(&l.abi, &r.abi, eq_str_lit) && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind)) }, - (TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt }), - TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt })) => { + ( + TyAlias(box ast::TyAlias { + defaultness: ld, + generics: lg, + bounds: lb, + ty: lt, + }), + TyAlias(box ast::TyAlias { + defaultness: rd, + generics: rg, + bounds: rb, + ty: rt, + }), + ) => { eq_defaultness(*ld, *rd) && eq_generics(lg, rg) && over(lb, rb, eq_generic_bound) @@ -278,8 +303,22 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { (Struct(lv, lg), Struct(rv, rg)) | (Union(lv, lg), Union(rv, rg)) => { eq_variant_data(lv, rv) && eq_generics(lg, rg) }, - (Trait(box ast::Trait { is_auto: la, unsafety: lu, generics: lg, bounds: lb, items: li }), - Trait(box ast::Trait { is_auto: ra, unsafety: ru, generics: rg, bounds: rb, items: ri })) => { + ( + Trait(box ast::Trait { + is_auto: la, + unsafety: lu, + generics: lg, + bounds: lb, + items: li, + }), + Trait(box ast::Trait { + is_auto: ra, + unsafety: ru, + generics: rg, + bounds: rb, + items: ri, + }), + ) => { la == ra && matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No) && eq_generics(lg, rg) @@ -328,12 +367,36 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool { use ForeignItemKind::*; match (l, r) { (Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re), - (Fn(box ast::Fn { defaultness: ld, sig: lf, generics: lg, body: lb }), - Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, body: rb })) => { + ( + Fn(box ast::Fn { + defaultness: ld, + sig: lf, + generics: lg, + body: lb, + }), + Fn(box ast::Fn { + defaultness: rd, + sig: rf, + generics: rg, + body: rb, + }), + ) => { eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r)) }, - (TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt }), - TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt })) => { + ( + TyAlias(box ast::TyAlias { + defaultness: ld, + generics: lg, + bounds: lb, + ty: lt, + }), + TyAlias(box ast::TyAlias { + defaultness: rd, + generics: rg, + bounds: rb, + ty: rt, + }), + ) => { eq_defaultness(*ld, *rd) && eq_generics(lg, rg) && over(lb, rb, eq_generic_bound) @@ -348,12 +411,36 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { use AssocItemKind::*; match (l, r) { (Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re), - (Fn(box ast::Fn { defaultness: ld, sig: lf, generics: lg, body: lb }), - Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, body: rb })) => { + ( + Fn(box ast::Fn { + defaultness: ld, + sig: lf, + generics: lg, + body: lb, + }), + Fn(box ast::Fn { + defaultness: rd, + sig: rf, + generics: rg, + body: rb, + }), + ) => { eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r)) }, - (TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt }), - TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt })) => { + ( + TyAlias(box ast::TyAlias { + defaultness: ld, + generics: lg, + bounds: lb, + ty: lt, + }), + TyAlias(box ast::TyAlias { + defaultness: rd, + generics: rg, + bounds: rb, + ty: rt, + }), + ) => { eq_defaultness(*ld, *rd) && eq_generics(lg, rg) && over(lb, rb, eq_generic_bound) diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index c19b558cd8c..7ae9615d560 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -14,15 +14,14 @@ pub enum DeprecationStatus { None, } +#[rustfmt::skip] pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[ - ("author", DeprecationStatus::None), - ("cognitive_complexity", DeprecationStatus::None), - ( - "cyclomatic_complexity", - DeprecationStatus::Replaced("cognitive_complexity"), - ), - ("dump", DeprecationStatus::None), - ("msrv", DeprecationStatus::None), + ("author", DeprecationStatus::None), + ("version", DeprecationStatus::None), + ("cognitive_complexity", DeprecationStatus::None), + ("cyclomatic_complexity", DeprecationStatus::Replaced("cognitive_complexity")), + ("dump", DeprecationStatus::None), + ("msrv", DeprecationStatus::None), ]; pub struct LimitStack { diff --git a/clippy_utils/src/eager_or_lazy.rs b/clippy_utils/src/eager_or_lazy.rs index 1ea7ccfb752..c2645ac730a 100644 --- a/clippy_utils/src/eager_or_lazy.rs +++ b/clippy_utils/src/eager_or_lazy.rs @@ -9,128 +9,227 @@ //! - or-fun-call //! - option-if-let-else -use crate::is_ctor_or_promotable_const_function; -use crate::ty::is_type_diagnostic_item; +use crate::ty::{all_predicates_of, is_copy}; +use crate::visitors::is_const_evaluatable; use rustc_hir::def::{DefKind, Res}; - -use rustc_hir::intravisit; -use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; - -use rustc_hir::{Block, Expr, ExprKind, Path, QPath}; +use rustc_hir::intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor}; +use rustc_hir::{def_id::DefId, Block, Expr, ExprKind, QPath, UnOp}; use rustc_lint::LateContext; -use rustc_middle::hir::map::Map; -use rustc_span::sym; +use rustc_middle::ty::{self, PredicateKind}; +use rustc_span::{sym, Symbol}; +use std::cmp; +use std::ops; -/// Is the expr pure (is it free from side-effects)? -/// This function is named so to stress that it isn't exhaustive and returns FNs. -fn identify_some_pure_patterns(expr: &Expr<'_>) -> bool { - match expr.kind { - ExprKind::Lit(..) | ExprKind::ConstBlock(..) | ExprKind::Path(..) | ExprKind::Field(..) => true, - ExprKind::AddrOf(_, _, addr_of_expr) => identify_some_pure_patterns(addr_of_expr), - ExprKind::Tup(tup_exprs) => tup_exprs.iter().all(identify_some_pure_patterns), - ExprKind::Struct(_, fields, expr) => { - fields.iter().all(|f| identify_some_pure_patterns(f.expr)) && expr.map_or(true, identify_some_pure_patterns) - }, - ExprKind::Call( - &Expr { - kind: - ExprKind::Path(QPath::Resolved( - _, - Path { - res: Res::Def(DefKind::Ctor(..) | DefKind::Variant, ..), - .. - }, - )), - .. - }, - args, - ) => args.iter().all(identify_some_pure_patterns), - ExprKind::Block( - &Block { - stmts, - expr: Some(expr), - .. - }, - _, - ) => stmts.is_empty() && identify_some_pure_patterns(expr), - ExprKind::Box(..) - | ExprKind::Array(..) - | ExprKind::Call(..) - | ExprKind::MethodCall(..) - | ExprKind::Binary(..) - | ExprKind::Unary(..) - | ExprKind::Let(..) - | ExprKind::Cast(..) - | ExprKind::Type(..) - | ExprKind::DropTemps(..) - | ExprKind::Loop(..) - | ExprKind::If(..) - | ExprKind::Match(..) - | ExprKind::Closure(..) - | ExprKind::Block(..) - | ExprKind::Assign(..) - | ExprKind::AssignOp(..) - | ExprKind::Index(..) - | ExprKind::Break(..) - | ExprKind::Continue(..) - | ExprKind::Ret(..) - | ExprKind::InlineAsm(..) - | ExprKind::LlvmInlineAsm(..) - | ExprKind::Repeat(..) - | ExprKind::Yield(..) - | ExprKind::Err => false, +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum EagernessSuggestion { + // The expression is cheap and should be evaluated eagerly + Eager, + // The expression may be cheap, so don't suggested lazy evaluation; or the expression may not be safe to switch to + // eager evaluation. + NoChange, + // The expression is likely expensive and should be evaluated lazily. + Lazy, + // The expression cannot be placed into a closure. + ForceNoChange, +} +impl ops::BitOr for EagernessSuggestion { + type Output = Self; + fn bitor(self, rhs: Self) -> Self { + cmp::max(self, rhs) + } +} +impl ops::BitOrAssign for EagernessSuggestion { + fn bitor_assign(&mut self, rhs: Self) { + *self = *self | rhs; } } -/// Identify some potentially computationally expensive patterns. -/// This function is named so to stress that its implementation is non-exhaustive. -/// It returns FNs and FPs. -fn identify_some_potentially_expensive_patterns<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - // Searches an expression for method calls or function calls that aren't ctors - struct FunCallFinder<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - found: bool, +/// Determine the eagerness of the given function call. +fn fn_eagerness(cx: &LateContext<'tcx>, fn_id: DefId, name: Symbol, args: &'tcx [Expr<'_>]) -> EagernessSuggestion { + use EagernessSuggestion::{Eager, Lazy, NoChange}; + let name = &*name.as_str(); + + let ty = match cx.tcx.impl_of_method(fn_id) { + Some(id) => cx.tcx.type_of(id), + None => return Lazy, + }; + + if (name.starts_with("as_") || name == "len" || name == "is_empty") && args.len() == 1 { + if matches!( + cx.tcx.crate_name(fn_id.krate), + sym::std | sym::core | sym::alloc | sym::proc_macro + ) { + Eager + } else { + NoChange + } + } else if let ty::Adt(def, subs) = ty.kind() { + // Types where the only fields are generic types (or references to) with no trait bounds other + // than marker traits. + // Due to the limited operations on these types functions should be fairly cheap. + if def + .variants + .iter() + .flat_map(|v| v.fields.iter()) + .any(|x| matches!(cx.tcx.type_of(x.did).peel_refs().kind(), ty::Param(_))) + && all_predicates_of(cx.tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() { + PredicateKind::Trait(pred) => cx.tcx.trait_def(pred.trait_ref.def_id).is_marker, + _ => true, + }) + && subs.types().all(|x| matches!(x.peel_refs().kind(), ty::Param(_))) + { + // Limit the function to either `(self) -> bool` or `(&self) -> bool` + match &**cx.tcx.fn_sig(fn_id).skip_binder().inputs_and_output { + [arg, res] if !arg.is_mutable_ptr() && arg.peel_refs() == ty && res.is_bool() => NoChange, + _ => Lazy, + } + } else { + Lazy + } + } else { + Lazy + } +} + +#[allow(clippy::too_many_lines)] +fn expr_eagerness(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessSuggestion { + struct V<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + eagerness: EagernessSuggestion, } - impl<'a, 'tcx> intravisit::Visitor<'tcx> for FunCallFinder<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - let call_found = match &expr.kind { - // ignore enum and struct constructors - ExprKind::Call(..) => !is_ctor_or_promotable_const_function(self.cx, expr), - ExprKind::Index(obj, _) => { - let ty = self.cx.typeck_results().expr_ty(obj); - is_type_diagnostic_item(self.cx, ty, sym::HashMap) - || is_type_diagnostic_item(self.cx, ty, sym::BTreeMap) - }, - ExprKind::MethodCall(..) => true, - _ => false, - }; - - if call_found { - self.found |= true; - } - - if !self.found { - intravisit::walk_expr(self, expr); - } - } - + impl<'cx, 'tcx> Visitor<'tcx> for V<'cx, 'tcx> { + type Map = ErasedMap<'tcx>; fn nested_visit_map(&mut self) -> NestedVisitorMap { NestedVisitorMap::None } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + use EagernessSuggestion::{ForceNoChange, Lazy, NoChange}; + if self.eagerness == ForceNoChange { + return; + } + match e.kind { + ExprKind::Call( + &Expr { + kind: ExprKind::Path(ref path), + hir_id, + .. + }, + args, + ) => match self.cx.qpath_res(path, hir_id) { + Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_) => (), + Res::Def(_, id) if self.cx.tcx.is_promotable_const_fn(id) => (), + // No need to walk the arguments here, `is_const_evaluatable` already did + Res::Def(..) if is_const_evaluatable(self.cx, e) => { + self.eagerness |= NoChange; + return; + }, + Res::Def(_, id) => match path { + QPath::Resolved(_, p) => { + self.eagerness |= fn_eagerness(self.cx, id, p.segments.last().unwrap().ident.name, args); + }, + QPath::TypeRelative(_, name) => { + self.eagerness |= fn_eagerness(self.cx, id, name.ident.name, args); + }, + QPath::LangItem(..) => self.eagerness = Lazy, + }, + _ => self.eagerness = Lazy, + }, + // No need to walk the arguments here, `is_const_evaluatable` already did + ExprKind::MethodCall(..) if is_const_evaluatable(self.cx, e) => { + self.eagerness |= NoChange; + return; + }, + ExprKind::MethodCall(name, _, args, _) => { + self.eagerness |= self + .cx + .typeck_results() + .type_dependent_def_id(e.hir_id) + .map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, args)); + }, + ExprKind::Index(_, e) => { + let ty = self.cx.typeck_results().expr_ty_adjusted(e); + if is_copy(self.cx, ty) && !ty.is_ref() { + self.eagerness |= NoChange; + } else { + self.eagerness = Lazy; + } + }, + + // Dereferences should be cheap, but dereferencing a raw pointer earlier may not be safe. + ExprKind::Unary(UnOp::Deref, e) if !self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => (), + ExprKind::Unary(UnOp::Deref, _) => self.eagerness |= NoChange, + + ExprKind::Unary(_, e) + if matches!( + self.cx.typeck_results().expr_ty(e).kind(), + ty::Bool | ty::Int(_) | ty::Uint(_), + ) => {}, + ExprKind::Binary(_, lhs, rhs) + if self.cx.typeck_results().expr_ty(lhs).is_primitive() + && self.cx.typeck_results().expr_ty(rhs).is_primitive() => {}, + + // Can't be moved into a closure + ExprKind::Break(..) + | ExprKind::Continue(_) + | ExprKind::Ret(_) + | ExprKind::InlineAsm(_) + | ExprKind::LlvmInlineAsm(_) + | ExprKind::Yield(..) + | ExprKind::Err => { + self.eagerness = ForceNoChange; + return; + }, + + // Memory allocation, custom operator, loop, or call to an unknown function + ExprKind::Box(_) + | ExprKind::Unary(..) + | ExprKind::Binary(..) + | ExprKind::Loop(..) + | ExprKind::Call(..) => self.eagerness = Lazy, + + ExprKind::ConstBlock(_) + | ExprKind::Array(_) + | ExprKind::Tup(_) + | ExprKind::Lit(_) + | ExprKind::Cast(..) + | ExprKind::Type(..) + | ExprKind::DropTemps(_) + | ExprKind::Let(..) + | ExprKind::If(..) + | ExprKind::Match(..) + | ExprKind::Closure(..) + | ExprKind::Field(..) + | ExprKind::Path(_) + | ExprKind::AddrOf(..) + | ExprKind::Struct(..) + | ExprKind::Repeat(..) + | ExprKind::Block(Block { stmts: [], .. }, _) => (), + + // Assignment might be to a local defined earlier, so don't eagerly evaluate. + // Blocks with multiple statements might be expensive, so don't eagerly evaluate. + // TODO: Actually check if either of these are true here. + ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Block(..) => self.eagerness |= NoChange, + } + walk_expr(self, e); + } } - let mut finder = FunCallFinder { cx, found: false }; - finder.visit_expr(expr); - finder.found + let mut v = V { + cx, + eagerness: EagernessSuggestion::Eager, + }; + v.visit_expr(e); + v.eagerness } -pub fn is_eagerness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - !identify_some_potentially_expensive_patterns(cx, expr) && identify_some_pure_patterns(expr) +/// Whether the given expression should be changed to evaluate eagerly +pub fn switch_to_eager_eval(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + expr_eagerness(cx, expr) == EagernessSuggestion::Eager } -pub fn is_lazyness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - identify_some_potentially_expensive_patterns(cx, expr) +/// Whether the given expression should be changed to evaluate lazily +pub fn switch_to_lazy_eval(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + expr_eagerness(cx, expr) == EagernessSuggestion::Lazy } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 086fbc9d3dd..3fdea55aaa1 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1,6 +1,7 @@ #![feature(box_patterns)] #![feature(in_band_lifetimes)] #![feature(iter_zip)] +#![feature(let_else)] #![feature(rustc_private)] #![feature(control_flow_enum)] #![recursion_limit = "512"] @@ -68,7 +69,7 @@ use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::hir_id::{HirIdMap, HirIdSet}; -use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::intravisit::{walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor}; use rustc_hir::itemlikevisit::ItemLikeVisitor; use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk}; use rustc_hir::{ @@ -96,6 +97,7 @@ use rustc_target::abi::Integer; use crate::consts::{constant, Constant}; use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type}; +use crate::visitors::expr_visitor_no_bodies; pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option) -> Option { if let Ok(version) = RustcVersion::parse(msrv) { @@ -250,12 +252,6 @@ pub fn is_lang_ctor(cx: &LateContext<'_>, qpath: &QPath<'_>, lang_item: LangItem false } -/// Returns `true` if this `span` was expanded by any macro. -#[must_use] -pub fn in_macro(span: Span) -> bool { - span.from_expansion() && !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..)) -} - pub fn is_unit_expr(expr: &Expr<'_>) -> bool { matches!( expr.kind, @@ -1113,63 +1109,30 @@ pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool { /// Returns `true` if `expr` contains a return expression pub fn contains_return(expr: &hir::Expr<'_>) -> bool { - struct RetCallFinder { - found: bool, - } - - impl<'tcx> hir::intravisit::Visitor<'tcx> for RetCallFinder { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { - if self.found { - return; - } + let mut found = false; + expr_visitor_no_bodies(|expr| { + if !found { if let hir::ExprKind::Ret(..) = &expr.kind { - self.found = true; - } else { - hir::intravisit::walk_expr(self, expr); + found = true; } } - - fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap { - hir::intravisit::NestedVisitorMap::None - } - } - - let mut visitor = RetCallFinder { found: false }; - visitor.visit_expr(expr); - visitor.found -} - -struct FindMacroCalls<'a, 'b> { - names: &'a [&'b str], - result: Vec, -} - -impl<'a, 'b, 'tcx> Visitor<'tcx> for FindMacroCalls<'a, 'b> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) { - self.result.push(expr.span); - } - // and check sub-expressions - intravisit::walk_expr(self, expr); - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } + !found + }) + .visit_expr(expr); + found } /// Finds calls of the specified macros in a function body. pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec { - let mut fmc = FindMacroCalls { - names, - result: Vec::new(), - }; - fmc.visit_expr(&body.value); - fmc.result + let mut result = Vec::new(); + expr_visitor_no_bodies(|expr| { + if names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) { + result.push(expr.span); + } + true + }) + .visit_expr(&body.value); + result } /// Extends the span to the beginning of the spans line, incl. whitespaces. @@ -1439,7 +1402,7 @@ pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx> /// Checks for the `#[automatically_derived]` attribute all `#[derive]`d /// implementations have. pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool { - attrs.iter().any(|attr| attr.has_name(sym::automatically_derived)) + has_attr(attrs, sym::automatically_derived) } /// Remove blocks around an expression. @@ -1561,20 +1524,29 @@ pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: rustc_ty::UintTy) -> u128 { (u << amt) >> amt } -pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool { +pub fn has_attr(attrs: &[ast::Attribute], symbol: Symbol) -> bool { + attrs.iter().any(|attr| attr.has_name(symbol)) +} + +pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool { let map = &tcx.hir(); let mut prev_enclosing_node = None; let mut enclosing_node = node; while Some(enclosing_node) != prev_enclosing_node { - if is_automatically_derived(map.attrs(enclosing_node)) { + if has_attr(map.attrs(enclosing_node), symbol) { return true; } prev_enclosing_node = Some(enclosing_node); enclosing_node = map.get_parent_item(enclosing_node); } + false } +pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool { + any_parent_has_attr(tcx, node, sym::automatically_derived) +} + /// Matches a function call with the given path and returns the arguments. /// /// Usage: @@ -1837,6 +1809,16 @@ pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..))) } +pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> { + if !is_no_std_crate(cx) { + Some("std") + } else if !is_no_core_crate(cx) { + Some("core") + } else { + None + } +} + pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool { cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| { if let ast::AttrKind::Normal(ref attr, _) = attr.kind { @@ -1847,6 +1829,16 @@ pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool { }) } +pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool { + cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| { + if let ast::AttrKind::Normal(ref attr, _) = attr.kind { + attr.path == sym::no_core + } else { + false + } + }) +} + /// Check if parent of a hir node is a trait implementation block. /// For example, `f` in /// ```rust,ignore diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index fa57dfbb57e..0cec7d6a5e4 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -19,7 +19,7 @@ msrv_aliases! { 1,46,0 { CONST_IF_MATCH } 1,45,0 { STR_STRIP_PREFIX } 1,43,0 { LOG2_10, LOG10_2 } - 1,42,0 { MATCHES_MACRO } + 1,42,0 { MATCHES_MACRO, SLICE_PATTERNS } 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 } @@ -27,7 +27,8 @@ msrv_aliases! { 1,36,0 { ITERATOR_COPIED } 1,35,0 { OPTION_COPIED, RANGE_CONTAINS } 1,34,0 { TRY_FROM } - 1,30,0 { ITERATOR_FIND_MAP } + 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } + 1,28,0 { FROM_BOOL } 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST } 1,16,0 { STR_REPEAT } } diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 501b08a47f1..b04d1736426 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -28,6 +28,7 @@ pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"]; pub(super) const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"]; /// Preferably use the diagnostic item `sym::Borrow` where possible pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"]; +pub const BORROW_MUT_TRAIT: [&str; 3] = ["core", "borrow", "BorrowMut"]; 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"]; @@ -110,6 +111,8 @@ pub(super) const PANICKING_PANIC: [&str; 3] = ["core", "panicking", "panic"]; pub(super) const PANICKING_PANIC_FMT: [&str; 3] = ["core", "panicking", "panic_fmt"]; pub(super) const PANICKING_PANIC_STR: [&str; 3] = ["core", "panicking", "panic_str"]; pub(super) const PANIC_ANY: [&str; 3] = ["std", "panic", "panic_any"]; +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; 2] = ["parking_lot", "MutexGuard"]; pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 2] = ["parking_lot", "RwLockReadGuard"]; pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 2] = ["parking_lot", "RwLockWriteGuard"]; diff --git a/clippy_utils/src/ptr.rs b/clippy_utils/src/ptr.rs index 8adb6915952..17d9a505bc9 100644 --- a/clippy_utils/src/ptr.rs +++ b/clippy_utils/src/ptr.rs @@ -1,9 +1,9 @@ use crate::source::snippet; +use crate::visitors::expr_visitor_no_bodies; use crate::{path_to_local_id, strip_pat_refs}; -use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; -use rustc_hir::{Body, BodyId, Expr, ExprKind, HirId, PatKind}; +use rustc_hir::intravisit::Visitor; +use rustc_hir::{Body, BodyId, ExprKind, HirId, PatKind}; use rustc_lint::LateContext; -use rustc_middle::hir::map::Map; use rustc_span::Span; use std::borrow::Cow; @@ -30,50 +30,28 @@ fn extract_clone_suggestions<'tcx>( replace: &[(&'static str, &'static str)], body: &'tcx Body<'_>, ) -> Option)>> { - let mut visitor = PtrCloneVisitor { - cx, - id, - replace, - spans: vec![], - abort: false, - }; - visitor.visit_body(body); - if visitor.abort { None } else { Some(visitor.spans) } -} - -struct PtrCloneVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - id: HirId, - replace: &'a [(&'static str, &'static str)], - spans: Vec<(Span, Cow<'static, str>)>, - abort: bool, -} - -impl<'a, 'tcx> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.abort { - return; + let mut abort = false; + let mut spans = Vec::new(); + expr_visitor_no_bodies(|expr| { + if abort { + return false; } if let ExprKind::MethodCall(seg, _, [recv], _) = expr.kind { - if path_to_local_id(recv, self.id) { + if path_to_local_id(recv, id) { if seg.ident.name.as_str() == "capacity" { - self.abort = true; - return; + abort = true; + return false; } - for &(fn_name, suffix) in self.replace { + for &(fn_name, suffix) in replace { if seg.ident.name.as_str() == fn_name { - self.spans.push((expr.span, snippet(self.cx, recv.span, "_") + suffix)); - return; + spans.push((expr.span, snippet(cx, recv.span, "_") + suffix)); + return false; } } } } - walk_expr(self, expr); - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } + !abort + }) + .visit_body(body); + if abort { None } else { Some(spans) } } diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index 789079510c5..d928317259d 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -128,7 +128,7 @@ pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option, ch: char) -> String { let x = s .lines() - .skip(ignore_first as usize) + .skip(usize::from(ignore_first)) .filter_map(|l| { if l.is_empty() { None @@ -155,14 +155,22 @@ fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option, .join("\n") } -/// Converts a span to a code snippet if available, otherwise use default. +/// Converts a span to a code snippet if available, otherwise returns the default. /// /// This is useful if you want to provide suggestions for your lint or more generally, if you want -/// to convert a given `Span` to a `str`. +/// to convert a given `Span` to a `str`. To create suggestions consider using +/// [`snippet_with_applicability`] to ensure that the applicability stays correct. /// /// # Example /// ```rust,ignore -/// snippet(cx, expr.span, "..") +/// // Given two spans one for `value` and one for the `init` expression. +/// let value = Vec::new(); +/// // ^^^^^ ^^^^^^^^^^ +/// // span1 span2 +/// +/// // The snipped call would return the corresponding code snippet +/// snippet(cx, span1, "..") // -> "value" +/// snippet(cx, span2, "..") // -> "Vec::new()" /// ``` pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> { snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from) diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index ca64ac7de3e..438c39bea0a 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -10,15 +10,16 @@ use rustc_hir::{TyKind, Unsafety}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; -use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, TypeFoldable, UintTy}; -use rustc_span::sym; -use rustc_span::symbol::{Ident, Symbol}; -use rustc_span::DUMMY_SP; +use rustc_middle::ty::{self, AdtDef, IntTy, Predicate, Ty, TyCtxt, TypeFoldable, UintTy}; +use rustc_span::symbol::Ident; +use rustc_span::{sym, Span, Symbol, DUMMY_SP}; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::query::normalize::AtExt; +use std::iter; use crate::{match_def_path, must_use_attr}; +// Checks if the given type implements copy. pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { ty.is_copy_modulo_regions(cx.tcx.at(DUMMY_SP), cx.param_env) } @@ -114,7 +115,12 @@ pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option< /// Checks whether a type implements a trait. /// The function returns false in case the type contains an inference variable. -/// See also [`get_trait_def_id`](super::get_trait_def_id). +/// +/// See: +/// * [`get_trait_def_id`](super::get_trait_def_id) to get a trait [`DefId`]. +/// * [Common tools for writing lints] for an example how to use this function and other options. +/// +/// [Common tools for writing lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/common_tools_writing_lints.md#checking-if-a-type-implements-a-specific-trait pub fn implements_trait<'tcx>( cx: &LateContext<'tcx>, ty: Ty<'tcx>, @@ -254,9 +260,17 @@ pub fn is_type_ref_to_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_ite } } -/// Checks if the type is equal to a diagnostic item +/// Checks if the type is equal to a diagnostic item. To check if a type implements a +/// trait marked with a diagnostic item use [`implements_trait`]. +/// +/// For a further exploitation what diagnostic items are see [diagnostic items] in +/// rustc-dev-guide. +/// +/// --- /// /// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem` +/// +/// [Diagnostic Items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool { match ty.kind() { ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did), @@ -377,3 +391,16 @@ pub fn is_uninit_value_valid_for_ty(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { _ => false, } } + +/// Gets an iterator over all predicates which apply to the given item. +pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator, Span)> { + let mut next_id = Some(id); + iter::from_fn(move || { + next_id.take().map(|id| { + let preds = tcx.predicates_of(id); + next_id = preds.parent; + preds.predicates.iter() + }) + }) + .flatten() +} diff --git a/clippy_utils/src/usage.rs b/clippy_utils/src/usage.rs index 34206b5ae2b..dfe8a66c2a1 100644 --- a/clippy_utils/src/usage.rs +++ b/clippy_utils/src/usage.rs @@ -1,7 +1,7 @@ use crate as utils; +use crate::visitors::{expr_visitor, expr_visitor_no_bodies}; use rustc_hir as hir; -use rustc_hir::intravisit; -use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; +use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::HirIdSet; use rustc_hir::{Expr, ExprKind, HirId}; use rustc_infer::infer::TyCtxtInferExt; @@ -148,96 +148,47 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> { } } -struct ReturnBreakContinueMacroVisitor { - seen_return_break_continue: bool, -} - -impl ReturnBreakContinueMacroVisitor { - fn new() -> ReturnBreakContinueMacroVisitor { - ReturnBreakContinueMacroVisitor { - seen_return_break_continue: false, - } - } -} - -impl<'tcx> Visitor<'tcx> for ReturnBreakContinueMacroVisitor { - type Map = Map<'tcx>; - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } - - fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { - if self.seen_return_break_continue { - // No need to look farther if we've already seen one of them - return; +pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool { + let mut seen_return_break_continue = false; + expr_visitor_no_bodies(|ex| { + if seen_return_break_continue { + return false; } match &ex.kind { ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => { - self.seen_return_break_continue = true; + seen_return_break_continue = true; }, // Something special could be done here to handle while or for loop // desugaring, as this will detect a break if there's a while loop // or a for loop inside the expression. _ => { - if utils::in_macro(ex.span) { - self.seen_return_break_continue = true; - } else { - rustc_hir::intravisit::walk_expr(self, ex); + if ex.span.from_expansion() { + seen_return_break_continue = true; } }, } - } + !seen_return_break_continue + }) + .visit_expr(expression); + seen_return_break_continue } -pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool { - let mut recursive_visitor = ReturnBreakContinueMacroVisitor::new(); - recursive_visitor.visit_expr(expression); - recursive_visitor.seen_return_break_continue -} - -pub struct UsedAfterExprVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - expr: &'tcx Expr<'tcx>, - definition: HirId, - past_expr: bool, - used_after_expr: bool, -} -impl<'a, 'tcx> UsedAfterExprVisitor<'a, 'tcx> { - pub fn is_found(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { - utils::path_to_local(expr).map_or(false, |definition| { - let mut visitor = UsedAfterExprVisitor { - cx, - expr, - definition, - past_expr: false, - used_after_expr: false, - }; - utils::get_enclosing_block(cx, definition).map_or(false, |block| { - visitor.visit_block(block); - visitor.used_after_expr - }) - }) - } -} - -impl<'a, 'tcx> intravisit::Visitor<'tcx> for UsedAfterExprVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - if self.used_after_expr { - return; +pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool { + let Some(block) = utils::get_enclosing_block(cx, local_id) else { return false }; + let mut used_after_expr = false; + let mut past_expr = false; + expr_visitor(cx, |expr| { + if used_after_expr { + return false; } - if expr.hir_id == self.expr.hir_id { - self.past_expr = true; - } else if self.past_expr && utils::path_to_local_id(expr, self.definition) { - self.used_after_expr = true; - } else { - intravisit::walk_expr(self, expr); + if expr.hir_id == after.hir_id { + past_expr = true; + } else if past_expr && utils::path_to_local_id(expr, local_id) { + used_after_expr = true; } - } + !used_after_expr + }) + .visit_block(block); + used_after_expr } diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index 503effbdad5..823df5cb751 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -1,38 +1,68 @@ use crate::path_to_local_id; use rustc_hir as hir; -use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor}; -use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt}; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{self, walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{Arm, Block, Body, BodyId, Expr, ExprKind, HirId, Stmt, UnOp}; use rustc_lint::LateContext; use rustc_middle::hir::map::Map; -use std::ops::ControlFlow; +use rustc_middle::ty; + +/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested +/// bodies (i.e. closures) are visited. +/// If the callback returns `true`, the expr just provided to the callback is walked. +#[must_use] +pub fn expr_visitor<'tcx>(cx: &LateContext<'tcx>, f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> { + struct V<'tcx, F> { + hir: Map<'tcx>, + f: F, + } + impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<'tcx, F> { + type Map = Map<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.hir) + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + if (self.f)(expr) { + walk_expr(self, expr); + } + } + } + V { hir: cx.tcx.hir(), f } +} + +/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested +/// bodies (i.e. closures) are not visited. +/// If the callback returns `true`, the expr just provided to the callback is walked. +#[must_use] +pub fn expr_visitor_no_bodies<'tcx>(f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> { + struct V(F); + impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V { + type Map = intravisit::ErasedMap<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if (self.0)(e) { + walk_expr(self, e); + } + } + } + V(f) +} /// returns `true` if expr contains match expr desugared from try fn contains_try(expr: &hir::Expr<'_>) -> bool { - struct TryFinder { - found: bool, - } - - impl<'hir> intravisit::Visitor<'hir> for TryFinder { - type Map = Map<'hir>; - - fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { - intravisit::NestedVisitorMap::None + let mut found = false; + expr_visitor_no_bodies(|e| { + if !found { + found = matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar)); } - - fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) { - if self.found { - return; - } - match expr.kind { - hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar) => self.found = true, - _ => intravisit::walk_expr(self, expr), - } - } - } - - let mut visitor = TryFinder { found: false }; - visitor.visit_expr(expr); - visitor.found + !found + }) + .visit_expr(expr); + found } pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool @@ -165,103 +195,125 @@ visitable_ref!(Stmt, visit_stmt); // } // } -/// Calls the given function for each break expression. -pub fn visit_break_exprs<'tcx>( - node: impl Visitable<'tcx>, - f: impl FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>), -) { - struct V(F); - impl<'tcx, F: FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>)> Visitor<'tcx> for V { - type Map = ErasedMap<'tcx>; - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } - - fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if let ExprKind::Break(dest, sub_expr) = e.kind { - self.0(e, dest, sub_expr); - } - walk_expr(self, e); - } - } - - node.visit(&mut V(f)); -} - /// Checks if the given resolved path is used in the given body. pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool { + let mut found = false; + expr_visitor(cx, |e| { + if found { + return false; + } + + if let ExprKind::Path(p) = &e.kind { + if cx.qpath_res(p, e.hir_id) == res { + found = true; + } + } + !found + }) + .visit_expr(&cx.tcx.hir().body(body).value); + found +} + +/// Checks if the given local is used. +pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool { + let mut is_used = false; + let mut visitor = expr_visitor(cx, |expr| { + if !is_used { + is_used = path_to_local_id(expr, id); + } + !is_used + }); + visitable.visit(&mut visitor); + drop(visitor); + is_used +} + +/// Checks if the given expression is a constant. +pub fn is_const_evaluatable(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { struct V<'a, 'tcx> { cx: &'a LateContext<'tcx>, - res: Res, - found: bool, + is_const: bool, } - impl Visitor<'tcx> for V<'_, 'tcx> { + impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { type Map = Map<'tcx>; fn nested_visit_map(&mut self) -> NestedVisitorMap { NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) } fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if self.found { + if !self.is_const { return; } + match e.kind { + ExprKind::ConstBlock(_) => return, + ExprKind::Call( + &Expr { + kind: ExprKind::Path(ref p), + hir_id, + .. + }, + _, + ) if self + .cx + .qpath_res(p, hir_id) + .opt_def_id() + .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {}, + ExprKind::MethodCall(..) + if self + .cx + .typeck_results() + .type_dependent_def_id(e.hir_id) + .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {}, + ExprKind::Binary(_, lhs, rhs) + if self.cx.typeck_results().expr_ty(lhs).peel_refs().is_primitive_ty() + && self.cx.typeck_results().expr_ty(rhs).peel_refs().is_primitive_ty() => {}, + ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_ref() => (), + ExprKind::Unary(_, e) if self.cx.typeck_results().expr_ty(e).peel_refs().is_primitive_ty() => (), + ExprKind::Index(base, _) + if matches!( + self.cx.typeck_results().expr_ty(base).peel_refs().kind(), + ty::Slice(_) | ty::Array(..) + ) => {}, + ExprKind::Path(ref p) + if matches!( + self.cx.qpath_res(p, e.hir_id), + Res::Def( + DefKind::Const + | DefKind::AssocConst + | DefKind::AnonConst + | DefKind::ConstParam + | DefKind::Ctor(..) + | DefKind::Fn + | DefKind::AssocFn, + _ + ) | Res::SelfCtor(_) + ) => {}, - if let ExprKind::Path(p) = &e.kind { - if self.cx.qpath_res(p, e.hir_id) == self.res { - self.found = true; - } - } else { - walk_expr(self, e); + ExprKind::AddrOf(..) + | ExprKind::Array(_) + | ExprKind::Block(..) + | ExprKind::Cast(..) + | ExprKind::DropTemps(_) + | ExprKind::Field(..) + | ExprKind::If(..) + | ExprKind::Let(..) + | ExprKind::Lit(_) + | ExprKind::Match(..) + | ExprKind::Repeat(..) + | ExprKind::Struct(..) + | ExprKind::Tup(_) + | ExprKind::Type(..) => (), + + _ => { + self.is_const = false; + return; + }, } + walk_expr(self, e); } } - let mut v = V { cx, res, found: false }; - v.visit_expr(&cx.tcx.hir().body(body).value); - v.found -} - -/// Calls the given function for each usage of the given local. -pub fn for_each_local_usage<'tcx, B>( - cx: &LateContext<'tcx>, - visitable: impl Visitable<'tcx>, - id: HirId, - f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow, -) -> ControlFlow { - struct V<'tcx, B, F> { - map: Map<'tcx>, - id: HirId, - f: F, - res: ControlFlow, - } - impl<'tcx, B, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow> Visitor<'tcx> for V<'tcx, B, F> { - type Map = Map<'tcx>; - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::OnlyBodies(self.map) - } - - fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if self.res.is_continue() { - if path_to_local_id(e, self.id) { - self.res = (self.f)(e); - } else { - walk_expr(self, e); - } - } - } - } - - let mut v = V { - map: cx.tcx.hir(), - id, - f, - res: ControlFlow::CONTINUE, - }; - visitable.visit(&mut v); - v.res -} - -/// Checks if the given local is used. -pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool { - for_each_local_usage(cx, visitable, id, |_| ControlFlow::BREAK).is_break() + let mut v = V { cx, is_const: true }; + v.visit_expr(e); + v.is_const } diff --git a/doc/adding_lints.md b/doc/adding_lints.md index bd32696d6db..cf16a1d5d3d 100644 --- a/doc/adding_lints.md +++ b/doc/adding_lints.md @@ -157,7 +157,7 @@ Manually testing against an example file can be useful if you have added some your local modifications, run ``` -env __CLIPPY_INTERNAL_TESTS=true cargo run --bin clippy-driver -- -L ./target/debug input.rs +cargo dev lint input.rs ``` from the working copy root. With tests in place, let's have a look at @@ -189,6 +189,7 @@ declare_clippy_lint! { /// ```rust /// // example code /// ``` + #[clippy::version = "1.29.0"] pub FOO_FUNCTIONS, pedantic, "function named `foo`, which is not a descriptive name" @@ -199,6 +200,10 @@ declare_clippy_lint! { section. This is the default documentation style and will be displayed [like this][example_lint_page]. To render and open this documentation locally in a browser, run `cargo dev serve`. +* The `#[clippy::version]` attribute will be rendered as part of the lint documentation. + The value should be set to the current Rust version that the lint is developed in, + it can be retrieved by running `rustc -vV` in the rust-clippy directory. The version + is listed under *release*. (Use the version without the `-nightly`) suffix. * `FOO_FUNCTIONS` is the name of our lint. Be sure to follow the [lint naming guidelines][lint_naming] here when naming your lint. In short, the name should state the thing that is being checked for and @@ -503,6 +508,7 @@ declare_clippy_lint! { /// // Good /// Insert a short example of improved code that doesn't trigger the lint /// ``` + #[clippy::version = "1.29.0"] pub FOO_FUNCTIONS, pedantic, "function named `foo`, which is not a descriptive name" @@ -634,7 +640,7 @@ in the following steps: Here are some pointers to things you are likely going to need for every lint: * [Clippy utils][utils] - Various helper functions. Maybe the function you need - is already in here (`implements_trait`, `match_def_path`, `snippet`, etc) + is already in here ([`is_type_diagnostic_item`], [`implements_trait`], [`snippet`], etc) * [Clippy diagnostics][diagnostics] * [The `if_chain` macro][if_chain] * [`from_expansion`][from_expansion] and [`in_external_macro`][in_external_macro] @@ -660,7 +666,10 @@ documentation currently. This is unfortunate, but in most cases you can probably get away with copying things from existing similar lints. If you are stuck, don't hesitate to ask on [Zulip] or in the issue/PR. -[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/lib.rs +[utils]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/index.html +[`is_type_diagnostic_item`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.is_type_diagnostic_item.html +[`implements_trait`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.implements_trait.html +[`snippet`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/source/fn.snippet.html [if_chain]: https://docs.rs/if_chain/*/if_chain/ [from_expansion]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion [in_external_macro]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/lint/fn.in_external_macro.html diff --git a/doc/changelog_update.md b/doc/changelog_update.md index 115848c4804..0cbad2c0924 100644 --- a/doc/changelog_update.md +++ b/doc/changelog_update.md @@ -32,7 +32,7 @@ bullet points might be helpful: need to check out the Rust release tag of the stable release. [Link][rust_stable_tools] -Usually you want to wirte the changelog of the **upcoming stable release**. Make +Usually you want to write the changelog of the **upcoming stable release**. Make sure though, that `beta` was already branched in the Rust repository. To find the commit hash, issue the following command when in a `rust-lang/rust` checkout: diff --git a/doc/common_tools_writing_lints.md b/doc/common_tools_writing_lints.md index 1a6b7c8cb47..c7e51d53f51 100644 --- a/doc/common_tools_writing_lints.md +++ b/doc/common_tools_writing_lints.md @@ -4,10 +4,11 @@ You may need following tooltips to catch up with common operations. - [Common tools for writing lints](#common-tools-for-writing-lints) - [Retrieving the type of an expression](#retrieving-the-type-of-an-expression) - - [Checking if an expression is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method) + - [Checking if an expr is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method) + - [Checking for a specific type](#checking-for-a-specific-type) - [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait) - [Checking if a type defines a specific method](#checking-if-a-type-defines-a-specific-method) - - [Dealing with macros](#dealing-with-macros) + - [Dealing with macros](#dealing-with-macros-and-expansions) Useful Rustc dev guide links: - [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation) @@ -15,7 +16,7 @@ Useful Rustc dev guide links: - [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html) - [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html) -# Retrieving the type of an expression +## Retrieving the type of an expression Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for example to answer following questions: @@ -54,7 +55,7 @@ Two noticeable items here: created by type checking step, it includes useful information such as types of expressions, ways to resolve methods and so on. -# Checking if an expr is calling a specific method +## Checking if an expr is calling a specific method Starting with an `expr`, you can check whether it is calling a specific method `some_method`: @@ -63,9 +64,11 @@ impl LateLintPass<'_> for MyStructLint { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { if_chain! { // Check our expr is calling a method - if let hir::ExprKind::MethodCall(path, _, _args, _) = &expr.kind; + if let hir::ExprKind::MethodCall(path, _, [_self_arg, ..], _) = &expr.kind; // Check the name of this method is `some_method` if path.ident.name == sym!(some_method); + // Optionally, check the type of the self argument. + // - See "Checking for a specific type" then { // ... } @@ -74,7 +77,45 @@ impl LateLintPass<'_> for MyStructLint { } ``` -# Checking if a type implements a specific trait +## Checking for a specific type + +There are three ways to check if an expression type is a specific type we want to check for. +All of these methods only check for the base type, generic arguments have to be checked separately. + +```rust +use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; +use clippy_utils::{paths, match_def_path}; +use rustc_span::symbol::sym; +use rustc_hir::LangItem; + +impl LateLintPass<'_> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + // Getting the expression type + let ty = cx.typeck_results().expr_ty(expr); + + // 1. Using diagnostic items + // The last argument is the diagnostic item to check for + if is_type_diagnostic_item(cx, ty, sym::Option) { + // The type is an `Option` + } + + // 2. Using lang items + if is_type_lang_item(cx, ty, LangItem::RangeFull) { + // The type is a full range like `.drain(..)` + } + + // 3. Using the type path + // This method should be avoided if possible + if match_def_path(cx, def_id, &paths::RESULT) { + // The type is a `core::result::Result` + } + } +} +``` + +Prefer using diagnostic items and lang items where possible. + +## Checking if a type implements a specific trait There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither. @@ -102,6 +143,7 @@ impl LateLintPass<'_> for MyStructLint { // 3. Using the type path with the expression // we use `match_trait_method` function from Clippy's utils + // (This method should be avoided if possible) if match_trait_method(cx, expr, &paths::INTO) { // `expr` implements `Into` trait } @@ -114,7 +156,7 @@ impl LateLintPass<'_> for MyStructLint { We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate. A list of defined paths for Clippy can be found in [paths.rs][paths] -# Checking if a type defines a specific method +## Checking if a type defines a specific method To check if our type defines a method called `some_method`: @@ -140,64 +182,78 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { } ``` -# Dealing with macros +## Dealing with macros and expansions -There are several helpers in [`clippy_utils`][utils] to deal with macros: +Keep in mind that macros are already expanded and desugaring is already applied +to the code representation that you are working with in Clippy. This unfortunately causes a lot of +false positives because macro expansions are "invisible" unless you actively check for them. +Generally speaking, code with macro expansions should just be ignored by Clippy because that code can be +dynamic in ways that are difficult or impossible to see. +Use the following functions to deal with macros: -- `in_macro()`: detect if the given span is expanded by a macro +- `span.from_expansion()`: detects if a span is from macro expansion or desugaring. + Checking this is a common first step in a lint. -You may want to use this for example to not start linting in any macro. + ```rust + if expr.span.from_expansion() { + // just forget it + return; + } + ``` -```rust -macro_rules! foo { - ($param:expr) => { - match $param { - "bar" => println!("whatever"), - _ => () - } - }; -} +- `span.ctxt()`: the span's context represents whether it is from expansion, and if so, which macro call expanded it. + It is sometimes useful to check if the context of two spans are equal. -foo!("bar"); + ```rust + // expands to `1 + 0`, but don't lint + 1 + mac!() + ``` + ```rust + if left.span.ctxt() != right.span.ctxt() { + // the coder most likely cannot modify this expression + return; + } + ``` + Note: Code that is not from expansion is in the "root" context. So any spans where `from_expansion` returns `true` can + be assumed to have the same context. And so just using `span.from_expansion()` is often good enough. -// if we lint the `match` of `foo` call and test its span -assert_eq!(in_macro(match_span), true); -``` -- `in_external_macro()`: detect if the given span is from an external macro, defined in a foreign crate +- `in_external_macro(span)`: detect if the given span is from a macro defined in a foreign crate. + If you want the lint to work with macro-generated code, this is the next line of defense to avoid macros + not defined in the current crate. It doesn't make sense to lint code that the coder can't change. -You may want to use it for example to not start linting in macros from other crates + You may want to use it for example to not start linting in macros from other crates -```rust -#[macro_use] -extern crate a_crate_with_macros; + ```rust + #[macro_use] + extern crate a_crate_with_macros; -// `foo` is defined in `a_crate_with_macros` -foo!("bar"); + // `foo` is defined in `a_crate_with_macros` + foo!("bar"); -// if we lint the `match` of `foo` call and test its span -assert_eq!(in_external_macro(cx.sess(), match_span), true); -``` + // if we lint the `match` of `foo` call and test its span + assert_eq!(in_external_macro(cx.sess(), match_span), true); + ``` - `differing_macro_contexts()`: returns true if the two given spans are not from the same context -```rust -macro_rules! m { - ($a:expr, $b:expr) => { - if $a.is_some() { - $b; - } - } -} + ```rust + macro_rules! m { + ($a:expr, $b:expr) => { + if $a.is_some() { + $b; + } + } + } -let x: Option = Some(42); -m!(x, x.unwrap()); + let x: Option = Some(42); + m!(x, x.unwrap()); -// These spans are not from the same context -// x.is_some() is from inside the macro -// x.unwrap() is from outside the macro -assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true); -``` + // These spans are not from the same context + // x.is_some() is from inside the macro + // x.unwrap() is from outside the macro + assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true); + ``` [TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.html [TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html @@ -207,4 +263,3 @@ assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true); [TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html [pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty [paths]: ../clippy_utils/src/paths.rs -[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/lib.rs diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index 97d3794fb84..41de2576e28 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -563,7 +563,7 @@ fn parse_json_message(json_message: &str, krate: &Crate) -> ClippyWarning { } } -/// Generate a short list of occuring lints-types and their count +/// Generate a short list of occurring lints-types and their count fn gather_stats(clippy_warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) { // count lint type occurrences let mut counter: HashMap<&String, usize> = HashMap::new(); @@ -718,7 +718,7 @@ pub fn main() { // quarter of the time which might result in a longer wall clock runtime // This helps when we check many small crates with dep-trees that don't have a lot of branches in - // order to achive some kind of parallelism + // order to achieve some kind of parallelism // by default, use a single thread let num_cpus = config.max_jobs; diff --git a/rust-toolchain b/rust-toolchain index 09554c08987..31e2aecad2c 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] channel = "nightly-2021-11-04" -components = ["llvm-tools-preview", "rustc-dev", "rust-src"] +components = ["llvm-tools-preview", "rustc-dev", "rust-src", "rustfmt"] diff --git a/src/driver.rs b/src/driver.rs index 0c82f37d6a2..a8aa3a76abc 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -108,7 +108,7 @@ impl rustc_driver::Callbacks for ClippyCallbacks { let conf = clippy_lints::read_conf(sess); clippy_lints::register_plugins(lint_store, sess, &conf); - clippy_lints::register_pre_expansion_lints(lint_store); + clippy_lints::register_pre_expansion_lints(lint_store, sess, &conf); clippy_lints::register_renamed(lint_store); })); diff --git a/tests/compile-test.rs b/tests/compile-test.rs index f25cf1d3ef5..a2d58491872 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -28,6 +28,7 @@ static TEST_DEPENDENCIES: &[&str] = &[ "serde", "serde_derive", "syn", + "parking_lot", ]; // Test dependencies may need an `extern crate` here to ensure that they show up @@ -41,6 +42,8 @@ extern crate if_chain; #[allow(unused_extern_crates)] extern crate itertools; #[allow(unused_extern_crates)] +extern crate parking_lot; +#[allow(unused_extern_crates)] extern crate quote; #[allow(unused_extern_crates)] extern crate syn; diff --git a/tests/ui-internal/check_clippy_version_attribute.rs b/tests/ui-internal/check_clippy_version_attribute.rs new file mode 100644 index 00000000000..31acac89cc6 --- /dev/null +++ b/tests/ui-internal/check_clippy_version_attribute.rs @@ -0,0 +1,87 @@ +#![deny(clippy::internal)] +#![feature(rustc_private)] + +#[macro_use] +extern crate rustc_middle; +#[macro_use] +extern crate rustc_session; +extern crate rustc_lint; + +/////////////////////// +// Valid descriptions +/////////////////////// +declare_tool_lint! { + #[clippy::version = "pre 1.29.0"] + pub clippy::VALID_ONE, + Warn, + "One", + report_in_external_macro: true +} + +declare_tool_lint! { + #[clippy::version = "1.29.0"] + pub clippy::VALID_TWO, + Warn, + "Two", + report_in_external_macro: true +} + +declare_tool_lint! { + #[clippy::version = "1.59.0"] + pub clippy::VALID_THREE, + Warn, + "Three", + report_in_external_macro: true +} + +/////////////////////// +// Invalid attributes +/////////////////////// +declare_tool_lint! { + #[clippy::version = "1.2.3.4.5.6"] + pub clippy::INVALID_ONE, + Warn, + "One", + report_in_external_macro: true +} + +declare_tool_lint! { + #[clippy::version = "I'm a string"] + pub clippy::INVALID_TWO, + Warn, + "Two", + report_in_external_macro: true +} + +/////////////////////// +// Missing attribute test +/////////////////////// +declare_tool_lint! { + #[clippy::version] + pub clippy::MISSING_ATTRIBUTE_ONE, + Warn, + "Two", + report_in_external_macro: true +} + +declare_tool_lint! { + pub clippy::MISSING_ATTRIBUTE_TWO, + Warn, + "Two", + report_in_external_macro: true +} + +#[allow(clippy::missing_clippy_version_attribute)] +mod internal_clippy_lints { + declare_tool_lint! { + pub clippy::ALLOW_MISSING_ATTRIBUTE_ONE, + Warn, + "Two", + report_in_external_macro: true + } +} + +use crate::internal_clippy_lints::ALLOW_MISSING_ATTRIBUTE_ONE; +declare_lint_pass!(Pass2 => [VALID_ONE, VALID_TWO, VALID_THREE, INVALID_ONE, INVALID_TWO, MISSING_ATTRIBUTE_ONE, MISSING_ATTRIBUTE_TWO, ALLOW_MISSING_ATTRIBUTE_ONE]); + +fn main() {} diff --git a/tests/ui-internal/check_clippy_version_attribute.stderr b/tests/ui-internal/check_clippy_version_attribute.stderr new file mode 100644 index 00000000000..9302e02ccb9 --- /dev/null +++ b/tests/ui-internal/check_clippy_version_attribute.stderr @@ -0,0 +1,73 @@ +error: this item has an invalid `clippy::version` attribute + --> $DIR/check_clippy_version_attribute.rs:40:1 + | +LL | / declare_tool_lint! { +LL | | #[clippy::version = "1.2.3.4.5.6"] +LL | | pub clippy::INVALID_ONE, +LL | | Warn, +LL | | "One", +LL | | report_in_external_macro: true +LL | | } + | |_^ + | +note: the lint level is defined here + --> $DIR/check_clippy_version_attribute.rs:1:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::invalid_clippy_version_attribute)]` implied by `#[deny(clippy::internal)]` + = help: please use a valid sematic version, see `doc/adding_lints.md` + = note: this error originates in the macro `$crate::declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this item has an invalid `clippy::version` attribute + --> $DIR/check_clippy_version_attribute.rs:48:1 + | +LL | / declare_tool_lint! { +LL | | #[clippy::version = "I'm a string"] +LL | | pub clippy::INVALID_TWO, +LL | | Warn, +LL | | "Two", +LL | | report_in_external_macro: true +LL | | } + | |_^ + | + = help: please use a valid sematic version, see `doc/adding_lints.md` + = note: this error originates in the macro `$crate::declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this lint is missing the `clippy::version` attribute or version value + --> $DIR/check_clippy_version_attribute.rs:59:1 + | +LL | / declare_tool_lint! { +LL | | #[clippy::version] +LL | | pub clippy::MISSING_ATTRIBUTE_ONE, +LL | | Warn, +LL | | "Two", +LL | | report_in_external_macro: true +LL | | } + | |_^ + | +note: the lint level is defined here + --> $DIR/check_clippy_version_attribute.rs:1:9 + | +LL | #![deny(clippy::internal)] + | ^^^^^^^^^^^^^^^^ + = note: `#[deny(clippy::missing_clippy_version_attribute)]` implied by `#[deny(clippy::internal)]` + = help: please use a `clippy::version` attribute, see `doc/adding_lints.md` + = note: this error originates in the macro `$crate::declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this lint is missing the `clippy::version` attribute or version value + --> $DIR/check_clippy_version_attribute.rs:67:1 + | +LL | / declare_tool_lint! { +LL | | pub clippy::MISSING_ATTRIBUTE_TWO, +LL | | Warn, +LL | | "Two", +LL | | report_in_external_macro: true +LL | | } + | |_^ + | + = help: please use a `clippy::version` attribute, see `doc/adding_lints.md` + = note: this error originates in the macro `$crate::declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 4 previous errors + diff --git a/tests/ui-internal/collapsible_span_lint_calls.fixed b/tests/ui-internal/collapsible_span_lint_calls.fixed index 7764cc8da78..a5a6f20ddd5 100644 --- a/tests/ui-internal/collapsible_span_lint_calls.fixed +++ b/tests/ui-internal/collapsible_span_lint_calls.fixed @@ -1,5 +1,6 @@ // run-rustfix #![deny(clippy::internal)] +#![allow(clippy::missing_clippy_version_attribute)] #![feature(rustc_private)] extern crate clippy_utils; diff --git a/tests/ui-internal/collapsible_span_lint_calls.rs b/tests/ui-internal/collapsible_span_lint_calls.rs index bdd296db832..6d783aa0786 100644 --- a/tests/ui-internal/collapsible_span_lint_calls.rs +++ b/tests/ui-internal/collapsible_span_lint_calls.rs @@ -1,5 +1,6 @@ // run-rustfix #![deny(clippy::internal)] +#![allow(clippy::missing_clippy_version_attribute)] #![feature(rustc_private)] extern crate clippy_utils; diff --git a/tests/ui-internal/collapsible_span_lint_calls.stderr b/tests/ui-internal/collapsible_span_lint_calls.stderr index 0632b038577..558d1299160 100644 --- a/tests/ui-internal/collapsible_span_lint_calls.stderr +++ b/tests/ui-internal/collapsible_span_lint_calls.stderr @@ -1,5 +1,5 @@ error: this call is collapsible - --> $DIR/collapsible_span_lint_calls.rs:35:9 + --> $DIR/collapsible_span_lint_calls.rs:36:9 | LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { LL | | db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable); @@ -14,7 +14,7 @@ LL | #![deny(clippy::internal)] = note: `#[deny(clippy::collapsible_span_lint_calls)]` implied by `#[deny(clippy::internal)]` error: this call is collapsible - --> $DIR/collapsible_span_lint_calls.rs:38:9 + --> $DIR/collapsible_span_lint_calls.rs:39:9 | LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { LL | | db.span_help(expr.span, help_msg); @@ -22,7 +22,7 @@ LL | | }); | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg)` error: this call is collapsible - --> $DIR/collapsible_span_lint_calls.rs:41:9 + --> $DIR/collapsible_span_lint_calls.rs:42:9 | LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { LL | | db.help(help_msg); @@ -30,7 +30,7 @@ LL | | }); | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg)` error: this call is collspible - --> $DIR/collapsible_span_lint_calls.rs:44:9 + --> $DIR/collapsible_span_lint_calls.rs:45:9 | LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { LL | | db.span_note(expr.span, note_msg); @@ -38,7 +38,7 @@ LL | | }); | |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg)` error: this call is collspible - --> $DIR/collapsible_span_lint_calls.rs:47:9 + --> $DIR/collapsible_span_lint_calls.rs:48:9 | LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { LL | | db.note(note_msg); diff --git a/tests/ui-internal/custom_ice_message.rs b/tests/ui-internal/custom_ice_message.rs index 5b30c9d5721..5057a018300 100644 --- a/tests/ui-internal/custom_ice_message.rs +++ b/tests/ui-internal/custom_ice_message.rs @@ -4,6 +4,7 @@ // normalize-stderr-test: "', .*clippy_lints" -> "', clippy_lints" #![deny(clippy::internal)] +#![allow(clippy::missing_clippy_version_attribute)] fn it_looks_like_you_are_trying_to_kill_clippy() {} diff --git a/tests/ui-internal/default_lint.rs b/tests/ui-internal/default_lint.rs index 053faae02ce..da29aedb2a3 100644 --- a/tests/ui-internal/default_lint.rs +++ b/tests/ui-internal/default_lint.rs @@ -1,4 +1,5 @@ #![deny(clippy::internal)] +#![allow(clippy::missing_clippy_version_attribute)] #![feature(rustc_private)] #[macro_use] diff --git a/tests/ui-internal/default_lint.stderr b/tests/ui-internal/default_lint.stderr index 4735573a47d..af6735f4e4d 100644 --- a/tests/ui-internal/default_lint.stderr +++ b/tests/ui-internal/default_lint.stderr @@ -1,5 +1,5 @@ error: the lint `TEST_LINT_DEFAULT` has the default lint description - --> $DIR/default_lint.rs:17:1 + --> $DIR/default_lint.rs:18:1 | LL | / declare_tool_lint! { LL | | pub clippy::TEST_LINT_DEFAULT, diff --git a/tests/ui-internal/if_chain_style.rs b/tests/ui-internal/if_chain_style.rs index 8e871707aa8..b0d89e038aa 100644 --- a/tests/ui-internal/if_chain_style.rs +++ b/tests/ui-internal/if_chain_style.rs @@ -1,5 +1,5 @@ #![warn(clippy::if_chain_style)] -#![allow(clippy::no_effect)] +#![allow(clippy::no_effect, clippy::nonminimal_bool, clippy::missing_clippy_version_attribute)] extern crate if_chain; diff --git a/tests/ui-internal/interning_defined_symbol.fixed b/tests/ui-internal/interning_defined_symbol.fixed index 9ab845a573a..6b7fd6efe39 100644 --- a/tests/ui-internal/interning_defined_symbol.fixed +++ b/tests/ui-internal/interning_defined_symbol.fixed @@ -1,5 +1,6 @@ // run-rustfix #![deny(clippy::internal)] +#![allow(clippy::missing_clippy_version_attribute)] #![feature(rustc_private)] extern crate rustc_span; diff --git a/tests/ui-internal/interning_defined_symbol.rs b/tests/ui-internal/interning_defined_symbol.rs index a58e182971d..98d7d7adad1 100644 --- a/tests/ui-internal/interning_defined_symbol.rs +++ b/tests/ui-internal/interning_defined_symbol.rs @@ -1,5 +1,6 @@ // run-rustfix #![deny(clippy::internal)] +#![allow(clippy::missing_clippy_version_attribute)] #![feature(rustc_private)] extern crate rustc_span; diff --git a/tests/ui-internal/interning_defined_symbol.stderr b/tests/ui-internal/interning_defined_symbol.stderr index 50c1c268eb1..4e99636e683 100644 --- a/tests/ui-internal/interning_defined_symbol.stderr +++ b/tests/ui-internal/interning_defined_symbol.stderr @@ -1,5 +1,5 @@ error: interning a defined symbol - --> $DIR/interning_defined_symbol.rs:17:13 + --> $DIR/interning_defined_symbol.rs:18:13 | LL | let _ = Symbol::intern("f32"); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::f32` @@ -12,19 +12,19 @@ LL | #![deny(clippy::internal)] = note: `#[deny(clippy::interning_defined_symbol)]` implied by `#[deny(clippy::internal)]` error: interning a defined symbol - --> $DIR/interning_defined_symbol.rs:20:13 + --> $DIR/interning_defined_symbol.rs:21:13 | LL | let _ = sym!(f32); | ^^^^^^^^^ help: try: `rustc_span::sym::f32` error: interning a defined symbol - --> $DIR/interning_defined_symbol.rs:23:13 + --> $DIR/interning_defined_symbol.rs:24:13 | LL | let _ = Symbol::intern("proc-macro"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::proc_dash_macro` error: interning a defined symbol - --> $DIR/interning_defined_symbol.rs:26:13 + --> $DIR/interning_defined_symbol.rs:27:13 | LL | let _ = Symbol::intern("self"); | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::symbol::kw::SelfLower` diff --git a/tests/ui-internal/invalid_paths.rs b/tests/ui-internal/invalid_paths.rs index a3b19c2e394..b823ff7fe37 100644 --- a/tests/ui-internal/invalid_paths.rs +++ b/tests/ui-internal/invalid_paths.rs @@ -1,4 +1,5 @@ #![warn(clippy::internal)] +#![allow(clippy::missing_clippy_version_attribute)] mod paths { // Good path diff --git a/tests/ui-internal/invalid_paths.stderr b/tests/ui-internal/invalid_paths.stderr index 20aa81b98a0..0a8e5427978 100644 --- a/tests/ui-internal/invalid_paths.stderr +++ b/tests/ui-internal/invalid_paths.stderr @@ -1,5 +1,5 @@ error: invalid path - --> $DIR/invalid_paths.rs:17:5 + --> $DIR/invalid_paths.rs:18:5 | LL | pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -7,7 +7,7 @@ LL | pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"]; = note: `-D clippy::invalid-paths` implied by `-D warnings` error: invalid path - --> $DIR/invalid_paths.rs:20:5 + --> $DIR/invalid_paths.rs:21:5 | LL | pub const BAD_MOD_PATH: [&str; 2] = ["std", "xxx"]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui-internal/lint_without_lint_pass.rs b/tests/ui-internal/lint_without_lint_pass.rs index beaef79a340..1fd03cfe36d 100644 --- a/tests/ui-internal/lint_without_lint_pass.rs +++ b/tests/ui-internal/lint_without_lint_pass.rs @@ -1,4 +1,5 @@ #![deny(clippy::internal)] +#![allow(clippy::missing_clippy_version_attribute)] #![feature(rustc_private)] #[macro_use] diff --git a/tests/ui-internal/lint_without_lint_pass.stderr b/tests/ui-internal/lint_without_lint_pass.stderr index e308e13da13..de04920b8e6 100644 --- a/tests/ui-internal/lint_without_lint_pass.stderr +++ b/tests/ui-internal/lint_without_lint_pass.stderr @@ -1,5 +1,5 @@ error: the lint `TEST_LINT` is not added to any `LintPass` - --> $DIR/lint_without_lint_pass.rs:11:1 + --> $DIR/lint_without_lint_pass.rs:12:1 | LL | / declare_tool_lint! { LL | | pub clippy::TEST_LINT, diff --git a/tests/ui-internal/match_type_on_diag_item.rs b/tests/ui-internal/match_type_on_diag_item.rs index be7b7a9af19..4b41ff15e80 100644 --- a/tests/ui-internal/match_type_on_diag_item.rs +++ b/tests/ui-internal/match_type_on_diag_item.rs @@ -1,4 +1,5 @@ #![deny(clippy::internal)] +#![allow(clippy::missing_clippy_version_attribute)] #![feature(rustc_private)] extern crate clippy_utils; diff --git a/tests/ui-internal/match_type_on_diag_item.stderr b/tests/ui-internal/match_type_on_diag_item.stderr index bf1d67e6054..e3cb6b6c22e 100644 --- a/tests/ui-internal/match_type_on_diag_item.stderr +++ b/tests/ui-internal/match_type_on_diag_item.stderr @@ -1,5 +1,5 @@ error: usage of `clippy_utils::ty::match_type()` on a type diagnostic item - --> $DIR/match_type_on_diag_item.rs:30:17 + --> $DIR/match_type_on_diag_item.rs:31:17 | LL | let _ = match_type(cx, ty, &OPTION); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clippy_utils::ty::is_type_diagnostic_item(cx, ty, sym::Option)` @@ -12,13 +12,13 @@ LL | #![deny(clippy::internal)] = note: `#[deny(clippy::match_type_on_diagnostic_item)]` implied by `#[deny(clippy::internal)]` error: usage of `clippy_utils::ty::match_type()` on a type diagnostic item - --> $DIR/match_type_on_diag_item.rs:31:17 + --> $DIR/match_type_on_diag_item.rs:32:17 | LL | let _ = match_type(cx, ty, &["core", "result", "Result"]); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clippy_utils::ty::is_type_diagnostic_item(cx, ty, sym::Result)` error: usage of `clippy_utils::ty::match_type()` on a type diagnostic item - --> $DIR/match_type_on_diag_item.rs:34:17 + --> $DIR/match_type_on_diag_item.rs:35:17 | LL | let _ = clippy_utils::ty::match_type(cx, ty, rc_path); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clippy_utils::ty::is_type_diagnostic_item(cx, ty, sym::Rc)` diff --git a/tests/ui-internal/outer_expn_data.fixed b/tests/ui-internal/outer_expn_data.fixed index b0b3498f057..bb82faf0c90 100644 --- a/tests/ui-internal/outer_expn_data.fixed +++ b/tests/ui-internal/outer_expn_data.fixed @@ -1,6 +1,7 @@ // run-rustfix #![deny(clippy::internal)] +#![allow(clippy::missing_clippy_version_attribute)] #![feature(rustc_private)] extern crate rustc_hir; diff --git a/tests/ui-internal/outer_expn_data.rs b/tests/ui-internal/outer_expn_data.rs index 55a3fed00d0..187d468b392 100644 --- a/tests/ui-internal/outer_expn_data.rs +++ b/tests/ui-internal/outer_expn_data.rs @@ -1,6 +1,7 @@ // run-rustfix #![deny(clippy::internal)] +#![allow(clippy::missing_clippy_version_attribute)] #![feature(rustc_private)] extern crate rustc_hir; diff --git a/tests/ui-internal/outer_expn_data.stderr b/tests/ui-internal/outer_expn_data.stderr index 56b6ce1f78e..afef696785e 100644 --- a/tests/ui-internal/outer_expn_data.stderr +++ b/tests/ui-internal/outer_expn_data.stderr @@ -1,5 +1,5 @@ error: usage of `outer_expn().expn_data()` - --> $DIR/outer_expn_data.rs:24:34 + --> $DIR/outer_expn_data.rs:25:34 | LL | let _ = expr.span.ctxt().outer_expn().expn_data(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `outer_expn_data()` diff --git a/tests/ui-internal/unnecessary_symbol_str.fixed b/tests/ui-internal/unnecessary_symbol_str.fixed index 95b8c6dfe89..4f5336663a8 100644 --- a/tests/ui-internal/unnecessary_symbol_str.fixed +++ b/tests/ui-internal/unnecessary_symbol_str.fixed @@ -1,7 +1,11 @@ // run-rustfix #![feature(rustc_private)] #![deny(clippy::internal)] -#![allow(clippy::unnecessary_operation, unused_must_use)] +#![allow( + clippy::unnecessary_operation, + unused_must_use, + clippy::missing_clippy_version_attribute +)] extern crate rustc_span; diff --git a/tests/ui-internal/unnecessary_symbol_str.rs b/tests/ui-internal/unnecessary_symbol_str.rs index ad6937cf60a..894aa1d3bc6 100644 --- a/tests/ui-internal/unnecessary_symbol_str.rs +++ b/tests/ui-internal/unnecessary_symbol_str.rs @@ -1,7 +1,11 @@ // run-rustfix #![feature(rustc_private)] #![deny(clippy::internal)] -#![allow(clippy::unnecessary_operation, unused_must_use)] +#![allow( + clippy::unnecessary_operation, + unused_must_use, + clippy::missing_clippy_version_attribute +)] extern crate rustc_span; diff --git a/tests/ui-internal/unnecessary_symbol_str.stderr b/tests/ui-internal/unnecessary_symbol_str.stderr index 12e05eaa7a0..75367bf4bc5 100644 --- a/tests/ui-internal/unnecessary_symbol_str.stderr +++ b/tests/ui-internal/unnecessary_symbol_str.stderr @@ -1,5 +1,5 @@ error: unnecessary `Symbol` to string conversion - --> $DIR/unnecessary_symbol_str.rs:11:5 + --> $DIR/unnecessary_symbol_str.rs:15:5 | LL | Symbol::intern("foo").as_str() == "clippy"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") == rustc_span::sym::clippy` @@ -12,25 +12,25 @@ LL | #![deny(clippy::internal)] = note: `#[deny(clippy::unnecessary_symbol_str)]` implied by `#[deny(clippy::internal)]` error: unnecessary `Symbol` to string conversion - --> $DIR/unnecessary_symbol_str.rs:12:5 + --> $DIR/unnecessary_symbol_str.rs:16:5 | LL | Symbol::intern("foo").to_string() == "self"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") == rustc_span::symbol::kw::SelfLower` error: unnecessary `Symbol` to string conversion - --> $DIR/unnecessary_symbol_str.rs:13:5 + --> $DIR/unnecessary_symbol_str.rs:17:5 | LL | Symbol::intern("foo").to_ident_string() != "Self"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") != rustc_span::symbol::kw::SelfUpper` error: unnecessary `Symbol` to string conversion - --> $DIR/unnecessary_symbol_str.rs:14:5 + --> $DIR/unnecessary_symbol_str.rs:18:5 | LL | &*Ident::empty().as_str() == "clippy"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Ident::empty().name == rustc_span::sym::clippy` error: unnecessary `Symbol` to string conversion - --> $DIR/unnecessary_symbol_str.rs:15:5 + --> $DIR/unnecessary_symbol_str.rs:19:5 | LL | "clippy" == Ident::empty().to_string(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::clippy == Ident::empty().name` diff --git a/tests/ui-toml/max_suggested_slice_pattern_length/clippy.toml b/tests/ui-toml/max_suggested_slice_pattern_length/clippy.toml new file mode 100644 index 00000000000..78c7e63b410 --- /dev/null +++ b/tests/ui-toml/max_suggested_slice_pattern_length/clippy.toml @@ -0,0 +1 @@ +max-suggested-slice-pattern-length = 8 diff --git a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs new file mode 100644 index 00000000000..21849a14fa9 --- /dev/null +++ b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.rs @@ -0,0 +1,23 @@ +#![deny(clippy::index_refutable_slice)] + +fn below_limit() { + let slice: Option<&[u32]> = Some(&[1, 2, 3]); + if let Some(slice) = slice { + // This would usually not be linted but is included now due to the + // index limit in the config file + println!("{}", slice[7]); + } +} + +fn above_limit() { + let slice: Option<&[u32]> = Some(&[1, 2, 3]); + if let Some(slice) = slice { + // This will not be linted as 8 is above the limit + println!("{}", slice[8]); + } +} + +fn main() { + below_limit(); + above_limit(); +} diff --git a/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.stderr b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.stderr new file mode 100644 index 00000000000..d319e65d06c --- /dev/null +++ b/tests/ui-toml/max_suggested_slice_pattern_length/index_refutable_slice.stderr @@ -0,0 +1,22 @@ +error: this binding can be a slice pattern to avoid indexing + --> $DIR/index_refutable_slice.rs:5:17 + | +LL | if let Some(slice) = slice { + | ^^^^^ + | +note: the lint level is defined here + --> $DIR/index_refutable_slice.rs:1:9 + | +LL | #![deny(clippy::index_refutable_slice)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try using a slice pattern here + | +LL | if let Some([_, _, _, _, _, _, _, slice_7, ..]) = slice { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: and replace the index expressions here + | +LL | println!("{}", slice_7); + | ~~~~~~~ + +error: aborting due to previous error + diff --git a/tests/ui-toml/min_rust_version/min_rust_version.rs b/tests/ui-toml/min_rust_version/min_rust_version.rs index bc41efa42a1..8e104926524 100644 --- a/tests/ui-toml/min_rust_version/min_rust_version.rs +++ b/tests/ui-toml/min_rust_version/min_rust_version.rs @@ -59,10 +59,20 @@ fn manual_strip_msrv() { } } +fn check_index_refutable_slice() { + // This shouldn't trigger `clippy::index_refutable_slice` as the suggestion + // would only be valid from 1.42.0 onward + let slice: Option<&[u32]> = Some(&[1]); + if let Some(slice) = slice { + println!("{}", slice[0]); + } +} + fn main() { option_as_ref_deref(); match_like_matches(); match_same_arms(); match_same_arms2(); manual_strip_msrv(); + check_index_refutable_slice(); } diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 97bab1308aa..00ddbd608a7 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -1,4 +1,4 @@ -error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `avoid-breaking-exported-api`, `msrv`, `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `pass-by-value-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `disallowed-methods`, `disallowed-types`, `unreadable-literal-lint-fractions`, `upper-case-acronyms-aggressive`, `cargo-ignore-publish`, `standard-macro-braces`, `enforced-import-renames`, `allowed-scripts`, `enable-raw-pointer-heuristic-for-send`, `third-party` at line 5 column 1 +error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `avoid-breaking-exported-api`, `msrv`, `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `pass-by-value-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `disallowed-methods`, `disallowed-types`, `unreadable-literal-lint-fractions`, `upper-case-acronyms-aggressive`, `cargo-ignore-publish`, `standard-macro-braces`, `enforced-import-renames`, `allowed-scripts`, `enable-raw-pointer-heuristic-for-send`, `max-suggested-slice-pattern-length`, `third-party` at line 5 column 1 error: aborting due to previous error diff --git a/tests/ui/author.stdout b/tests/ui/author.stdout index 211d11c7c1a..3125863036b 100644 --- a/tests/ui/author.stdout +++ b/tests/ui/author.stdout @@ -1,11 +1,11 @@ if_chain! { - if let StmtKind::Local(ref local) = stmt.kind; - if let Some(ref init) = local.init; - if let ExprKind::Cast(ref expr, ref cast_ty) = init.kind; - if let TyKind::Path(ref qp) = cast_ty.kind; - if match_qpath(qp, &["char"]); + if let StmtKind::Local(local) = stmt.kind; + if let Some(init) = local.init; + if let ExprKind::Cast(expr, cast_ty) = init.kind; + if let TyKind::Path(ref qpath) = cast_ty.kind; + if match_qpath(qpath, &["char"]); if let ExprKind::Lit(ref lit) = expr.kind; - if let LitKind::Int(69, _) = lit.node; + if let LitKind::Int(69, LitIntType::Unsuffixed) = lit.node; if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = local.pat.kind; if name.as_str() == "x"; then { diff --git a/tests/ui/author/blocks.rs b/tests/ui/author/blocks.rs index c8465cd59aa..a7335c01baa 100644 --- a/tests/ui/author/blocks.rs +++ b/tests/ui/author/blocks.rs @@ -1,10 +1,16 @@ +// edition:2018 + #![allow(redundant_semicolons, clippy::no_effect)] +#![feature(stmt_expr_attributes)] +#![feature(async_closure)] #[rustfmt::skip] fn main() { #[clippy::author] { let x = 42i32; + let _t = 1f32; + -x; }; #[clippy::author] @@ -12,4 +18,7 @@ fn main() { let expr = String::new(); drop(expr) }; + + #[clippy::author] + async move || {}; } diff --git a/tests/ui/author/blocks.stdout b/tests/ui/author/blocks.stdout index 854bc28083a..2fc4a7d1f7f 100644 --- a/tests/ui/author/blocks.stdout +++ b/tests/ui/author/blocks.stdout @@ -1,39 +1,63 @@ if_chain! { - if let ExprKind::Block(ref block) = expr.kind; - if block.stmts.len() == 2; - if let StmtKind::Local(ref local) = block.stmts[0].kind; - if let Some(ref init) = local.init; + if let ExprKind::Block(block, None) = expr.kind; + if block.stmts.len() == 3; + if let StmtKind::Local(local) = block.stmts[0].kind; + if let Some(init) = local.init; if let ExprKind::Lit(ref lit) = init.kind; - if let LitKind::Int(42, _) = lit.node; + if let LitKind::Int(42, LitIntType::Signed(IntTy::I32)) = lit.node; if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = local.pat.kind; if name.as_str() == "x"; - if let StmtKind::Semi(ref e, _) = block.stmts[1].kind - if let ExprKind::Unary(UnOp::Neg, ref inner) = e.kind; - if let ExprKind::Path(ref path) = inner.kind; - if match_qpath(path, &["x"]); + if let StmtKind::Local(local1) = block.stmts[1].kind; + if let Some(init1) = local1.init; + if let ExprKind::Lit(ref lit1) = init1.kind; + if let LitKind::Float(_, LitFloatType::Suffixed(FloatTy::F32)) = lit1.node; + if let PatKind::Binding(BindingAnnotation::Unannotated, _, name1, None) = local1.pat.kind; + if name1.as_str() == "_t"; + if let StmtKind::Semi(e) = block.stmts[2].kind; + if let ExprKind::Unary(UnOp::Neg, inner) = e.kind; + if let ExprKind::Path(ref qpath) = inner.kind; + if match_qpath(qpath, &["x"]); if block.expr.is_none(); then { // report your lint here } } if_chain! { - if let ExprKind::Block(ref block) = expr.kind; + if let ExprKind::Block(block, None) = expr.kind; if block.stmts.len() == 1; - if let StmtKind::Local(ref local) = block.stmts[0].kind; - if let Some(ref init) = local.init; - if let ExprKind::Call(ref func, ref args) = init.kind; - if let ExprKind::Path(ref path) = func.kind; - if match_qpath(path, &["String", "new"]); - if args.len() == 0; + if let StmtKind::Local(local) = block.stmts[0].kind; + if let Some(init) = local.init; + if let ExprKind::Call(func, args) = init.kind; + if let ExprKind::Path(ref qpath) = func.kind; + if match_qpath(qpath, &["String", "new"]); + if args.is_empty(); if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = local.pat.kind; if name.as_str() == "expr"; - if let Some(trailing_expr) = &block.expr; - if let ExprKind::Call(ref func1, ref args1) = trailing_expr.kind; - if let ExprKind::Path(ref path1) = func1.kind; - if match_qpath(path1, &["drop"]); + if let Some(trailing_expr) = block.expr; + if let ExprKind::Call(func1, args1) = trailing_expr.kind; + if let ExprKind::Path(ref qpath1) = func1.kind; + if match_qpath(qpath1, &["drop"]); if args1.len() == 1; - if let ExprKind::Path(ref path2) = args1[0].kind; - if match_qpath(path2, &["expr"]); + if let ExprKind::Path(ref qpath2) = args1[0].kind; + if match_qpath(qpath2, &["expr"]); + then { + // report your lint here + } +} +if_chain! { + if let ExprKind::Closure(CaptureBy::Value, fn_decl, body_id, _, None) = expr.kind; + if let FnRetTy::DefaultReturn(_) = fn_decl.output; + let expr1 = &cx.tcx.hir().body(body_id).value; + if let ExprKind::Call(func, args) = expr1.kind; + if let ExprKind::Path(ref qpath) = func.kind; + if matches!(qpath, QPath::LangItem(LangItem::FromGenerator, _)); + if args.len() == 1; + if let ExprKind::Closure(CaptureBy::Value, fn_decl1, body_id1, _, Some(Movability::Static)) = args[0].kind; + if let FnRetTy::DefaultReturn(_) = fn_decl1.output; + let expr2 = &cx.tcx.hir().body(body_id1).value; + if let ExprKind::Block(block, None) = expr2.kind; + if block.stmts.is_empty(); + if block.expr.is_none(); then { // report your lint here } diff --git a/tests/ui/author/call.stdout b/tests/ui/author/call.stdout index 4dccf666631..266312d63e5 100644 --- a/tests/ui/author/call.stdout +++ b/tests/ui/author/call.stdout @@ -1,14 +1,14 @@ if_chain! { - if let StmtKind::Local(ref local) = stmt.kind; - if let Some(ref init) = local.init; - if let ExprKind::Call(ref func, ref args) = init.kind; - if let ExprKind::Path(ref path) = func.kind; - if match_qpath(path, &["{{root}}", "std", "cmp", "min"]); + if let StmtKind::Local(local) = stmt.kind; + if let Some(init) = local.init; + if let ExprKind::Call(func, args) = init.kind; + if let ExprKind::Path(ref qpath) = func.kind; + if match_qpath(qpath, &["{{root}}", "std", "cmp", "min"]); if args.len() == 2; if let ExprKind::Lit(ref lit) = args[0].kind; - if let LitKind::Int(3, _) = lit.node; + if let LitKind::Int(3, LitIntType::Unsuffixed) = lit.node; if let ExprKind::Lit(ref lit1) = args[1].kind; - if let LitKind::Int(4, _) = lit1.node; + if let LitKind::Int(4, LitIntType::Unsuffixed) = lit1.node; if let PatKind::Wild = local.pat.kind; then { // report your lint here diff --git a/tests/ui/author/for_loop.rs b/tests/ui/author/for_loop.rs deleted file mode 100644 index b3dec876535..00000000000 --- a/tests/ui/author/for_loop.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![feature(stmt_expr_attributes)] - -fn main() { - #[clippy::author] - for y in 0..10 { - let z = y; - } -} diff --git a/tests/ui/author/for_loop.stdout b/tests/ui/author/for_loop.stdout deleted file mode 100644 index f1b4d4e096e..00000000000 --- a/tests/ui/author/for_loop.stdout +++ /dev/null @@ -1,64 +0,0 @@ -if_chain! { - if let ExprKind::DropTemps(ref expr) = expr.kind; - if let ExprKind::Match(ref expr1, ref arms, MatchSource::ForLoopDesugar) = expr.kind; - if let ExprKind::Call(ref func, ref args) = expr1.kind; - if let ExprKind::Path(ref path) = func.kind; - if matches!(path, QPath::LangItem(LangItem::IntoIterIntoIter, _)); - if args.len() == 1; - if let ExprKind::Struct(ref path1, ref fields, None) = args[0].kind; - if matches!(path1, QPath::LangItem(LangItem::Range, _)); - if fields.len() == 2; - // unimplemented: field checks - if arms.len() == 1; - if let ExprKind::Loop(ref body, ref label, LoopSource::ForLoop) = arms[0].body.kind; - if body.stmts.len() == 4; - if let StmtKind::Local(ref local) = body.stmts[0].kind; - if let PatKind::Binding(BindingAnnotation::Mutable, _, name, None) = local.pat.kind; - if name.as_str() == "__next"; - if let StmtKind::Expr(ref e, _) = body.stmts[1].kind - if let ExprKind::Match(ref expr2, ref arms1, MatchSource::ForLoopDesugar) = e.kind; - if let ExprKind::Call(ref func1, ref args1) = expr2.kind; - if let ExprKind::Path(ref path2) = func1.kind; - if matches!(path2, QPath::LangItem(LangItem::IteratorNext, _)); - if args1.len() == 1; - if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, ref inner) = args1[0].kind; - if let ExprKind::Path(ref path3) = inner.kind; - if match_qpath(path3, &["iter"]); - if arms1.len() == 2; - if let ExprKind::Assign(ref target, ref value, ref _span) = arms1[0].body.kind; - if let ExprKind::Path(ref path4) = target.kind; - if match_qpath(path4, &["__next"]); - if let ExprKind::Path(ref path5) = value.kind; - if match_qpath(path5, &["val"]); - if let PatKind::Struct(ref path6, ref fields1, false) = arms1[0].pat.kind; - if matches!(path6, QPath::LangItem(LangItem::OptionSome, _)); - if fields1.len() == 1; - // unimplemented: field checks - if let ExprKind::Break(ref destination, None) = arms1[1].body.kind; - if let PatKind::Struct(ref path7, ref fields2, false) = arms1[1].pat.kind; - if matches!(path7, QPath::LangItem(LangItem::OptionNone, _)); - if fields2.len() == 0; - // unimplemented: field checks - if let StmtKind::Local(ref local1) = body.stmts[2].kind; - if let Some(ref init) = local1.init; - if let ExprKind::Path(ref path8) = init.kind; - if match_qpath(path8, &["__next"]); - if let PatKind::Binding(BindingAnnotation::Unannotated, _, name1, None) = local1.pat.kind; - if name1.as_str() == "y"; - if let StmtKind::Expr(ref e1, _) = body.stmts[3].kind - if let ExprKind::Block(ref block) = e1.kind; - if block.stmts.len() == 1; - if let StmtKind::Local(ref local2) = block.stmts[0].kind; - if let Some(ref init1) = local2.init; - if let ExprKind::Path(ref path9) = init1.kind; - if match_qpath(path9, &["y"]); - if let PatKind::Binding(BindingAnnotation::Unannotated, _, name2, None) = local2.pat.kind; - if name2.as_str() == "z"; - if block.expr.is_none(); - if body.expr.is_none(); - if let PatKind::Binding(BindingAnnotation::Mutable, _, name3, None) = arms[0].pat.kind; - if name3.as_str() == "iter"; - then { - // report your lint here - } -} diff --git a/tests/ui/author/if.rs b/tests/ui/author/if.rs index 2e9cb1466d0..946088ab346 100644 --- a/tests/ui/author/if.rs +++ b/tests/ui/author/if.rs @@ -7,4 +7,11 @@ fn main() { } else { 2 == 2; }; + + let a = true; + + #[clippy::author] + if let true = a { + } else { + }; } diff --git a/tests/ui/author/if.stdout b/tests/ui/author/if.stdout index 1653de9a6f2..75ff3faf29a 100644 --- a/tests/ui/author/if.stdout +++ b/tests/ui/author/if.stdout @@ -1,32 +1,50 @@ if_chain! { - if let StmtKind::Local(ref local) = stmt.kind; - if let Some(ref init) = local.init; - if let ExprKind::If(ref cond, ref then, Some(ref else_)) = init.kind; - if let ExprKind::Block(ref block) = else_.kind; + if let StmtKind::Local(local) = stmt.kind; + if let Some(init) = local.init; + if let ExprKind::If(cond, then, Some(else_expr)) = init.kind; + if let ExprKind::DropTemps(expr) = cond.kind; + if let ExprKind::Lit(ref lit) = expr.kind; + if let LitKind::Bool(true) = lit.node; + if let ExprKind::Block(block, None) = then.kind; if block.stmts.len() == 1; - if let StmtKind::Semi(ref e, _) = block.stmts[0].kind - if let ExprKind::Binary(ref op, ref left, ref right) = e.kind; + if let StmtKind::Semi(e) = block.stmts[0].kind; + if let ExprKind::Binary(op, left, right) = e.kind; if BinOpKind::Eq == op.node; - if let ExprKind::Lit(ref lit) = left.kind; - if let LitKind::Int(2, _) = lit.node; - if let ExprKind::Lit(ref lit1) = right.kind; - if let LitKind::Int(2, _) = lit1.node; + if let ExprKind::Lit(ref lit1) = left.kind; + if let LitKind::Int(1, LitIntType::Unsuffixed) = lit1.node; + if let ExprKind::Lit(ref lit2) = right.kind; + if let LitKind::Int(1, LitIntType::Unsuffixed) = lit2.node; if block.expr.is_none(); - if let ExprKind::DropTemps(ref expr) = cond.kind; - if let ExprKind::Lit(ref lit2) = expr.kind; - if let LitKind::Bool(true) = lit2.node; - if let ExprKind::Block(ref block1) = then.kind; + if let ExprKind::Block(block1, None) = else_expr.kind; if block1.stmts.len() == 1; - if let StmtKind::Semi(ref e1, _) = block1.stmts[0].kind - if let ExprKind::Binary(ref op1, ref left1, ref right1) = e1.kind; + if let StmtKind::Semi(e1) = block1.stmts[0].kind; + if let ExprKind::Binary(op1, left1, right1) = e1.kind; if BinOpKind::Eq == op1.node; if let ExprKind::Lit(ref lit3) = left1.kind; - if let LitKind::Int(1, _) = lit3.node; + if let LitKind::Int(2, LitIntType::Unsuffixed) = lit3.node; if let ExprKind::Lit(ref lit4) = right1.kind; - if let LitKind::Int(1, _) = lit4.node; + if let LitKind::Int(2, LitIntType::Unsuffixed) = lit4.node; if block1.expr.is_none(); if let PatKind::Wild = local.pat.kind; then { // report your lint here } } +if_chain! { + if let ExprKind::If(cond, then, Some(else_expr)) = expr.kind; + if let ExprKind::Let(pat, expr1, _) = cond.kind; + if let PatKind::Lit(lit_expr) = pat.kind; + if let ExprKind::Lit(ref lit) = lit_expr.kind; + if let LitKind::Bool(true) = lit.node; + if let ExprKind::Path(ref qpath) = expr1.kind; + if match_qpath(qpath, &["a"]); + if let ExprKind::Block(block, None) = then.kind; + if block.stmts.is_empty(); + if block.expr.is_none(); + if let ExprKind::Block(block1, None) = else_expr.kind; + if block1.stmts.is_empty(); + if block1.expr.is_none(); + then { + // report your lint here + } +} diff --git a/tests/ui/author/issue_3849.stdout b/tests/ui/author/issue_3849.stdout index 65f93f3cdc0..bce4bc70273 100644 --- a/tests/ui/author/issue_3849.stdout +++ b/tests/ui/author/issue_3849.stdout @@ -1,12 +1,12 @@ if_chain! { - if let StmtKind::Local(ref local) = stmt.kind; - if let Some(ref init) = local.init; - if let ExprKind::Call(ref func, ref args) = init.kind; - if let ExprKind::Path(ref path) = func.kind; - if match_qpath(path, &["std", "mem", "transmute"]); + if let StmtKind::Local(local) = stmt.kind; + if let Some(init) = local.init; + if let ExprKind::Call(func, args) = init.kind; + if let ExprKind::Path(ref qpath) = func.kind; + if match_qpath(qpath, &["std", "mem", "transmute"]); if args.len() == 1; - if let ExprKind::Path(ref path1) = args[0].kind; - if match_qpath(path1, &["ZPTR"]); + if let ExprKind::Path(ref qpath1) = args[0].kind; + if match_qpath(qpath1, &["ZPTR"]); if let PatKind::Wild = local.pat.kind; then { // report your lint here diff --git a/tests/ui/author/loop.rs b/tests/ui/author/loop.rs new file mode 100644 index 00000000000..d6de21631e2 --- /dev/null +++ b/tests/ui/author/loop.rs @@ -0,0 +1,36 @@ +#![feature(stmt_expr_attributes)] +#![allow(clippy::never_loop, clippy::while_immutable_condition)] + +fn main() { + #[clippy::author] + for y in 0..10 { + let z = y; + } + + #[clippy::author] + for _ in 0..10 { + break; + } + + #[clippy::author] + 'label: for _ in 0..10 { + break 'label; + } + + let a = true; + + #[clippy::author] + while a { + break; + } + + #[clippy::author] + while let true = a { + break; + } + + #[clippy::author] + loop { + break; + } +} diff --git a/tests/ui/author/loop.stdout b/tests/ui/author/loop.stdout new file mode 100644 index 00000000000..5a1f731ac4a --- /dev/null +++ b/tests/ui/author/loop.stdout @@ -0,0 +1,116 @@ +if_chain! { + if let ExprKind::DropTemps(expr1) = expr.kind; + if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::ForLoop::hir(expr1); + if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = pat.kind; + if name.as_str() == "y"; + if let ExprKind::Struct(qpath, fields, None) = arg.kind; + if matches!(qpath, QPath::LangItem(LangItem::Range, _)); + if fields.len() == 2; + if fields[0].ident.as_str() == "start"; + if let ExprKind::Lit(ref lit) = fields[0].expr.kind; + if let LitKind::Int(0, LitIntType::Unsuffixed) = lit.node; + if fields[1].ident.as_str() == "end"; + if let ExprKind::Lit(ref lit1) = fields[1].expr.kind; + if let LitKind::Int(10, LitIntType::Unsuffixed) = lit1.node; + if let ExprKind::Block(block, None) = body.kind; + if block.stmts.len() == 1; + if let StmtKind::Local(local) = block.stmts[0].kind; + if let Some(init) = local.init; + if let ExprKind::Path(ref qpath1) = init.kind; + if match_qpath(qpath1, &["y"]); + if let PatKind::Binding(BindingAnnotation::Unannotated, _, name1, None) = local.pat.kind; + if name1.as_str() == "z"; + if block.expr.is_none(); + then { + // report your lint here + } +} +if_chain! { + if let ExprKind::DropTemps(expr1) = expr.kind; + if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::ForLoop::hir(expr1); + if let PatKind::Wild = pat.kind; + if let ExprKind::Struct(qpath, fields, None) = arg.kind; + if matches!(qpath, QPath::LangItem(LangItem::Range, _)); + if fields.len() == 2; + if fields[0].ident.as_str() == "start"; + if let ExprKind::Lit(ref lit) = fields[0].expr.kind; + if let LitKind::Int(0, LitIntType::Unsuffixed) = lit.node; + if fields[1].ident.as_str() == "end"; + if let ExprKind::Lit(ref lit1) = fields[1].expr.kind; + if let LitKind::Int(10, LitIntType::Unsuffixed) = lit1.node; + if let ExprKind::Block(block, None) = body.kind; + if block.stmts.len() == 1; + if let StmtKind::Semi(e) = block.stmts[0].kind; + if let ExprKind::Break(destination, None) = e.kind; + if destination.label.is_none(); + if block.expr.is_none(); + then { + // report your lint here + } +} +if_chain! { + if let ExprKind::DropTemps(expr1) = expr.kind; + if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::ForLoop::hir(expr1); + if let PatKind::Wild = pat.kind; + if let ExprKind::Struct(qpath, fields, None) = arg.kind; + if matches!(qpath, QPath::LangItem(LangItem::Range, _)); + if fields.len() == 2; + if fields[0].ident.as_str() == "start"; + if let ExprKind::Lit(ref lit) = fields[0].expr.kind; + if let LitKind::Int(0, LitIntType::Unsuffixed) = lit.node; + if fields[1].ident.as_str() == "end"; + if let ExprKind::Lit(ref lit1) = fields[1].expr.kind; + if let LitKind::Int(10, LitIntType::Unsuffixed) = lit1.node; + if let ExprKind::Block(block, None) = body.kind; + if block.stmts.len() == 1; + if let StmtKind::Semi(e) = block.stmts[0].kind; + if let ExprKind::Break(destination, None) = e.kind; + if let Some(label) = destination.label; + if label.ident.as_str() == "'label"; + if block.expr.is_none(); + then { + // report your lint here + } +} +if_chain! { + if let Some(higher::While { condition: condition, body: body }) = higher::While::hir(expr); + if let ExprKind::Path(ref qpath) = condition.kind; + if match_qpath(qpath, &["a"]); + if let ExprKind::Block(block, None) = body.kind; + if block.stmts.len() == 1; + if let StmtKind::Semi(e) = block.stmts[0].kind; + if let ExprKind::Break(destination, None) = e.kind; + if destination.label.is_none(); + if block.expr.is_none(); + then { + // report your lint here + } +} +if_chain! { + if let Some(higher::WhileLet { let_pat: let_pat, let_expr: let_expr, if_then: if_then }) = higher::WhileLet::hir(expr); + if let PatKind::Lit(lit_expr) = let_pat.kind; + if let ExprKind::Lit(ref lit) = lit_expr.kind; + if let LitKind::Bool(true) = lit.node; + if let ExprKind::Path(ref qpath) = let_expr.kind; + if match_qpath(qpath, &["a"]); + if let ExprKind::Block(block, None) = if_then.kind; + if block.stmts.len() == 1; + if let StmtKind::Semi(e) = block.stmts[0].kind; + if let ExprKind::Break(destination, None) = e.kind; + if destination.label.is_none(); + if block.expr.is_none(); + then { + // report your lint here + } +} +if_chain! { + if let ExprKind::Loop(body, None, LoopSource::Loop, _) = expr.kind; + if body.stmts.len() == 1; + if let StmtKind::Semi(e) = body.stmts[0].kind; + if let ExprKind::Break(destination, None) = e.kind; + if destination.label.is_none(); + if body.expr.is_none(); + then { + // report your lint here + } +} diff --git a/tests/ui/author/matches.stdout b/tests/ui/author/matches.stdout index 68cc2b214eb..38444a0094c 100644 --- a/tests/ui/author/matches.stdout +++ b/tests/ui/author/matches.stdout @@ -1,32 +1,35 @@ if_chain! { - if let StmtKind::Local(ref local) = stmt.kind; - if let Some(ref init) = local.init; - if let ExprKind::Match(ref expr, ref arms, MatchSource::Normal) = init.kind; - if let ExprKind::Lit(ref lit) = expr.kind; - if let LitKind::Int(42, _) = lit.node; + if let StmtKind::Local(local) = stmt.kind; + if let Some(init) = local.init; + if let ExprKind::Match(scrutinee, arms, MatchSource::Normal) = init.kind; + if let ExprKind::Lit(ref lit) = scrutinee.kind; + if let LitKind::Int(42, LitIntType::Unsuffixed) = lit.node; if arms.len() == 3; - if let ExprKind::Lit(ref lit1) = arms[0].body.kind; - if let LitKind::Int(5, _) = lit1.node; - if let PatKind::Lit(ref lit_expr) = arms[0].pat.kind - if let ExprKind::Lit(ref lit2) = lit_expr.kind; - if let LitKind::Int(16, _) = lit2.node; - if let ExprKind::Block(ref block) = arms[1].body.kind; + if let PatKind::Lit(lit_expr) = arms[0].pat.kind; + if let ExprKind::Lit(ref lit1) = lit_expr.kind; + if let LitKind::Int(16, LitIntType::Unsuffixed) = lit1.node; + if arms[0].guard.is_none(); + if let ExprKind::Lit(ref lit2) = arms[0].body.kind; + if let LitKind::Int(5, LitIntType::Unsuffixed) = lit2.node; + if let PatKind::Lit(lit_expr1) = arms[1].pat.kind; + if let ExprKind::Lit(ref lit3) = lit_expr1.kind; + if let LitKind::Int(17, LitIntType::Unsuffixed) = lit3.node; + if arms[1].guard.is_none(); + if let ExprKind::Block(block, None) = arms[1].body.kind; if block.stmts.len() == 1; - if let StmtKind::Local(ref local1) = block.stmts[0].kind; - if let Some(ref init1) = local1.init; - if let ExprKind::Lit(ref lit3) = init1.kind; - if let LitKind::Int(3, _) = lit3.node; + if let StmtKind::Local(local1) = block.stmts[0].kind; + if let Some(init1) = local1.init; + if let ExprKind::Lit(ref lit4) = init1.kind; + if let LitKind::Int(3, LitIntType::Unsuffixed) = lit4.node; if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = local1.pat.kind; if name.as_str() == "x"; - if let Some(trailing_expr) = &block.expr; - if let ExprKind::Path(ref path) = trailing_expr.kind; - if match_qpath(path, &["x"]); - if let PatKind::Lit(ref lit_expr1) = arms[1].pat.kind - if let ExprKind::Lit(ref lit4) = lit_expr1.kind; - if let LitKind::Int(17, _) = lit4.node; - if let ExprKind::Lit(ref lit5) = arms[2].body.kind; - if let LitKind::Int(1, _) = lit5.node; + if let Some(trailing_expr) = block.expr; + if let ExprKind::Path(ref qpath) = trailing_expr.kind; + if match_qpath(qpath, &["x"]); if let PatKind::Wild = arms[2].pat.kind; + if arms[2].guard.is_none(); + if let ExprKind::Lit(ref lit5) = arms[2].body.kind; + if let LitKind::Int(1, LitIntType::Unsuffixed) = lit5.node; if let PatKind::Binding(BindingAnnotation::Unannotated, _, name1, None) = local.pat.kind; if name1.as_str() == "a"; then { diff --git a/tests/ui/author/repeat.rs b/tests/ui/author/repeat.rs new file mode 100644 index 00000000000..d8e9d589e68 --- /dev/null +++ b/tests/ui/author/repeat.rs @@ -0,0 +1,5 @@ +#[allow(clippy::no_effect)] +fn main() { + #[clippy::author] + [1_u8; 5]; +} diff --git a/tests/ui/author/repeat.stdout b/tests/ui/author/repeat.stdout new file mode 100644 index 00000000000..f16350e4b5e --- /dev/null +++ b/tests/ui/author/repeat.stdout @@ -0,0 +1,11 @@ +if_chain! { + if let ExprKind::Repeat(value, length) = expr.kind; + if let ExprKind::Lit(ref lit) = value.kind; + if let LitKind::Int(1, LitIntType::Unsigned(UintTy::U8)) = lit.node; + let expr1 = &cx.tcx.hir().body(length.body).value; + if let ExprKind::Lit(ref lit1) = expr1.kind; + if let LitKind::Int(5, LitIntType::Unsuffixed) = lit1.node; + then { + // report your lint here + } +} diff --git a/tests/ui/author/struct.rs b/tests/ui/author/struct.rs new file mode 100644 index 00000000000..5fdf3433a37 --- /dev/null +++ b/tests/ui/author/struct.rs @@ -0,0 +1,40 @@ +#[allow(clippy::unnecessary_operation, clippy::single_match)] +fn main() { + struct Test { + field: u32, + } + + #[clippy::author] + Test { + field: if true { 1 } else { 0 }, + }; + + let test = Test { field: 1 }; + + match test { + #[clippy::author] + Test { field: 1 } => {}, + _ => {}, + } + + struct TestTuple(u32); + + let test_tuple = TestTuple(1); + + match test_tuple { + #[clippy::author] + TestTuple(1) => {}, + _ => {}, + } + + struct TestMethodCall(u32); + + impl TestMethodCall { + fn test(&self) {} + } + + let test_method_call = TestMethodCall(1); + + #[clippy::author] + test_method_call.test(); +} diff --git a/tests/ui/author/struct.stdout b/tests/ui/author/struct.stdout new file mode 100644 index 00000000000..ded5abd8d33 --- /dev/null +++ b/tests/ui/author/struct.stdout @@ -0,0 +1,64 @@ +if_chain! { + if let ExprKind::Struct(qpath, fields, None) = expr.kind; + if match_qpath(qpath, &["Test"]); + if fields.len() == 1; + if fields[0].ident.as_str() == "field"; + if let ExprKind::If(cond, then, Some(else_expr)) = fields[0].expr.kind; + if let ExprKind::DropTemps(expr1) = cond.kind; + if let ExprKind::Lit(ref lit) = expr1.kind; + if let LitKind::Bool(true) = lit.node; + if let ExprKind::Block(block, None) = then.kind; + if block.stmts.is_empty(); + if let Some(trailing_expr) = block.expr; + if let ExprKind::Lit(ref lit1) = trailing_expr.kind; + if let LitKind::Int(1, LitIntType::Unsuffixed) = lit1.node; + if let ExprKind::Block(block1, None) = else_expr.kind; + if block1.stmts.is_empty(); + if let Some(trailing_expr1) = block1.expr; + if let ExprKind::Lit(ref lit2) = trailing_expr1.kind; + if let LitKind::Int(0, LitIntType::Unsuffixed) = lit2.node; + then { + // report your lint here + } +} +if_chain! { + if let PatKind::Struct(ref qpath, fields, false) = arm.pat.kind; + if match_qpath(qpath, &["Test"]); + if fields.len() == 1; + if fields[0].ident.as_str() == "field"; + if let PatKind::Lit(lit_expr) = fields[0].pat.kind; + if let ExprKind::Lit(ref lit) = lit_expr.kind; + if let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node; + if arm.guard.is_none(); + if let ExprKind::Block(block, None) = arm.body.kind; + if block.stmts.is_empty(); + if block.expr.is_none(); + then { + // report your lint here + } +} +if_chain! { + if let PatKind::TupleStruct(ref qpath, fields, None) = arm.pat.kind; + if match_qpath(qpath, &["TestTuple"]); + if fields.len() == 1; + if let PatKind::Lit(lit_expr) = fields[0].kind; + if let ExprKind::Lit(ref lit) = lit_expr.kind; + if let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node; + if arm.guard.is_none(); + if let ExprKind::Block(block, None) = arm.body.kind; + if block.stmts.is_empty(); + if block.expr.is_none(); + then { + // report your lint here + } +} +if_chain! { + if let ExprKind::MethodCall(method_name, _, args, _) = expr.kind; + if method_name.ident.as_str() == "test"; + if args.len() == 1; + if let ExprKind::Path(ref qpath) = args[0].kind; + if match_qpath(qpath, &["test_method_call"]); + then { + // report your lint here + } +} diff --git a/tests/ui/cast_lossless_bool.fixed b/tests/ui/cast_lossless_bool.fixed new file mode 100644 index 00000000000..9e2da45c378 --- /dev/null +++ b/tests/ui/cast_lossless_bool.fixed @@ -0,0 +1,42 @@ +// run-rustfix + +#![allow(dead_code)] +#![warn(clippy::cast_lossless)] + +fn main() { + // Test clippy::cast_lossless with casts to integer types + let _ = u8::from(true); + let _ = u16::from(true); + let _ = u32::from(true); + let _ = u64::from(true); + let _ = u128::from(true); + let _ = usize::from(true); + + let _ = i8::from(true); + let _ = i16::from(true); + let _ = i32::from(true); + let _ = i64::from(true); + let _ = i128::from(true); + let _ = isize::from(true); + + // Test with an expression wrapped in parens + let _ = u16::from(true | false); +} + +// The lint would suggest using `u32::from(input)` here but the `XX::from` function is not const, +// so we skip the lint if the expression is in a const fn. +// See #3656 +const fn abc(input: bool) -> u32 { + input as u32 +} + +// Same as the above issue. We can't suggest `::from` in const fns in impls +mod cast_lossless_in_impl { + struct A; + + impl A { + pub const fn convert(x: bool) -> u64 { + x as u64 + } + } +} diff --git a/tests/ui/cast_lossless_bool.rs b/tests/ui/cast_lossless_bool.rs new file mode 100644 index 00000000000..b6f6c59a01f --- /dev/null +++ b/tests/ui/cast_lossless_bool.rs @@ -0,0 +1,42 @@ +// run-rustfix + +#![allow(dead_code)] +#![warn(clippy::cast_lossless)] + +fn main() { + // Test clippy::cast_lossless with casts to integer types + let _ = true as u8; + let _ = true as u16; + let _ = true as u32; + let _ = true as u64; + let _ = true as u128; + let _ = true as usize; + + let _ = true as i8; + let _ = true as i16; + let _ = true as i32; + let _ = true as i64; + let _ = true as i128; + let _ = true as isize; + + // Test with an expression wrapped in parens + let _ = (true | false) as u16; +} + +// The lint would suggest using `u32::from(input)` here but the `XX::from` function is not const, +// so we skip the lint if the expression is in a const fn. +// See #3656 +const fn abc(input: bool) -> u32 { + input as u32 +} + +// Same as the above issue. We can't suggest `::from` in const fns in impls +mod cast_lossless_in_impl { + struct A; + + impl A { + pub const fn convert(x: bool) -> u64 { + x as u64 + } + } +} diff --git a/tests/ui/cast_lossless_bool.stderr b/tests/ui/cast_lossless_bool.stderr new file mode 100644 index 00000000000..6b148336011 --- /dev/null +++ b/tests/ui/cast_lossless_bool.stderr @@ -0,0 +1,82 @@ +error: casting `bool` to `u8` is more cleanly stated with `u8::from(_)` + --> $DIR/cast_lossless_bool.rs:8:13 + | +LL | let _ = true as u8; + | ^^^^^^^^^^ help: try: `u8::from(true)` + | + = note: `-D clippy::cast-lossless` implied by `-D warnings` + +error: casting `bool` to `u16` is more cleanly stated with `u16::from(_)` + --> $DIR/cast_lossless_bool.rs:9:13 + | +LL | let _ = true as u16; + | ^^^^^^^^^^^ help: try: `u16::from(true)` + +error: casting `bool` to `u32` is more cleanly stated with `u32::from(_)` + --> $DIR/cast_lossless_bool.rs:10:13 + | +LL | let _ = true as u32; + | ^^^^^^^^^^^ help: try: `u32::from(true)` + +error: casting `bool` to `u64` is more cleanly stated with `u64::from(_)` + --> $DIR/cast_lossless_bool.rs:11:13 + | +LL | let _ = true as u64; + | ^^^^^^^^^^^ help: try: `u64::from(true)` + +error: casting `bool` to `u128` is more cleanly stated with `u128::from(_)` + --> $DIR/cast_lossless_bool.rs:12:13 + | +LL | let _ = true as u128; + | ^^^^^^^^^^^^ help: try: `u128::from(true)` + +error: casting `bool` to `usize` is more cleanly stated with `usize::from(_)` + --> $DIR/cast_lossless_bool.rs:13:13 + | +LL | let _ = true as usize; + | ^^^^^^^^^^^^^ help: try: `usize::from(true)` + +error: casting `bool` to `i8` is more cleanly stated with `i8::from(_)` + --> $DIR/cast_lossless_bool.rs:15:13 + | +LL | let _ = true as i8; + | ^^^^^^^^^^ help: try: `i8::from(true)` + +error: casting `bool` to `i16` is more cleanly stated with `i16::from(_)` + --> $DIR/cast_lossless_bool.rs:16:13 + | +LL | let _ = true as i16; + | ^^^^^^^^^^^ help: try: `i16::from(true)` + +error: casting `bool` to `i32` is more cleanly stated with `i32::from(_)` + --> $DIR/cast_lossless_bool.rs:17:13 + | +LL | let _ = true as i32; + | ^^^^^^^^^^^ help: try: `i32::from(true)` + +error: casting `bool` to `i64` is more cleanly stated with `i64::from(_)` + --> $DIR/cast_lossless_bool.rs:18:13 + | +LL | let _ = true as i64; + | ^^^^^^^^^^^ help: try: `i64::from(true)` + +error: casting `bool` to `i128` is more cleanly stated with `i128::from(_)` + --> $DIR/cast_lossless_bool.rs:19:13 + | +LL | let _ = true as i128; + | ^^^^^^^^^^^^ help: try: `i128::from(true)` + +error: casting `bool` to `isize` is more cleanly stated with `isize::from(_)` + --> $DIR/cast_lossless_bool.rs:20:13 + | +LL | let _ = true as isize; + | ^^^^^^^^^^^^^ help: try: `isize::from(true)` + +error: casting `bool` to `u16` is more cleanly stated with `u16::from(_)` + --> $DIR/cast_lossless_bool.rs:23:13 + | +LL | let _ = (true | false) as u16; + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::from(true | false)` + +error: aborting due to 13 previous errors + diff --git a/tests/ui/crashes/auxiliary/ice-7934-aux.rs b/tests/ui/crashes/auxiliary/ice-7934-aux.rs new file mode 100644 index 00000000000..4afbf027b61 --- /dev/null +++ b/tests/ui/crashes/auxiliary/ice-7934-aux.rs @@ -0,0 +1,4 @@ +fn zero() { + // SAFETY: + unsafe { 0 }; +} diff --git a/tests/ui/crashes/ice-7934.rs b/tests/ui/crashes/ice-7934.rs new file mode 100644 index 00000000000..a4691c4131b --- /dev/null +++ b/tests/ui/crashes/ice-7934.rs @@ -0,0 +1,7 @@ +#![warn(clippy::undocumented_unsafe_blocks)] +#![allow(clippy::no_effect)] + +#[path = "auxiliary/ice-7934-aux.rs"] +mod zero; + +fn main() {} diff --git a/tests/ui/crate_level_checks/no_std_swap.rs b/tests/ui/crate_level_checks/no_std_swap.rs new file mode 100644 index 00000000000..f5ff9eed7a1 --- /dev/null +++ b/tests/ui/crate_level_checks/no_std_swap.rs @@ -0,0 +1,15 @@ +#![no_std] +#![feature(lang_items, start, libc)] +#![crate_type = "lib"] + +use core::panic::PanicInfo; + +#[warn(clippy::all)] +fn main() { + // TODO: do somethjing with swap + let mut a = 42; + let mut b = 1337; + + a = b; + b = a; +} diff --git a/tests/ui/crate_level_checks/no_std_swap.stderr b/tests/ui/crate_level_checks/no_std_swap.stderr new file mode 100644 index 00000000000..079f1828cf9 --- /dev/null +++ b/tests/ui/crate_level_checks/no_std_swap.stderr @@ -0,0 +1,12 @@ +error: this looks like you are trying to swap `a` and `b` + --> $DIR/no_std_swap.rs:13:5 + | +LL | / a = b; +LL | | b = a; + | |_________^ help: try: `core::mem::swap(&mut a, &mut b)` + | + = note: `-D clippy::almost-swapped` implied by `-D warnings` + = note: or maybe you should use `core::mem::replace`? + +error: aborting due to previous error + diff --git a/tests/ui/explicit_counter_loop.rs b/tests/ui/explicit_counter_loop.rs index 81d8221bd13..aa966761feb 100644 --- a/tests/ui/explicit_counter_loop.rs +++ b/tests/ui/explicit_counter_loop.rs @@ -158,3 +158,33 @@ mod issue_4677 { } } } + +mod issue_7920 { + pub fn test() { + let slice = &[1, 2, 3]; + + let index_usize: usize = 0; + let mut idx_usize: usize = 0; + + // should suggest `enumerate` + for _item in slice { + if idx_usize == index_usize { + break; + } + + idx_usize += 1; + } + + let index_u32: u32 = 0; + let mut idx_u32: u32 = 0; + + // should suggest `zip` + for _item in slice { + if idx_u32 == index_u32 { + break; + } + + idx_u32 += 1; + } + } +} diff --git a/tests/ui/explicit_counter_loop.stderr b/tests/ui/explicit_counter_loop.stderr index 4cbacffe87b..9edddea651c 100644 --- a/tests/ui/explicit_counter_loop.stderr +++ b/tests/ui/explicit_counter_loop.stderr @@ -42,5 +42,19 @@ error: the variable `count` is used as a loop counter LL | for _i in 3..10 { | ^^^^^^^^^^^^^^^ help: consider using: `for (count, _i) in (3..10).enumerate()` -error: aborting due to 7 previous errors +error: the variable `idx_usize` is used as a loop counter + --> $DIR/explicit_counter_loop.rs:170:9 + | +LL | for _item in slice { + | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_usize, _item) in slice.into_iter().enumerate()` + +error: the variable `idx_u32` is used as a loop counter + --> $DIR/explicit_counter_loop.rs:182:9 + | +LL | for _item in slice { + | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_u32, _item) in (0_u32..).zip(slice.into_iter())` + | + = note: `idx_u32` is of type `u32`, making it ineligible for `Iterator::enumerate` + +error: aborting due to 9 previous errors diff --git a/tests/ui/index_refutable_slice/if_let_slice_binding.rs b/tests/ui/index_refutable_slice/if_let_slice_binding.rs new file mode 100644 index 00000000000..c2c0c520dc6 --- /dev/null +++ b/tests/ui/index_refutable_slice/if_let_slice_binding.rs @@ -0,0 +1,166 @@ +#![deny(clippy::index_refutable_slice)] + +enum SomeEnum { + One(T), + Two(T), + Three(T), + Four(T), +} + +fn lintable_examples() { + // Try with reference + let slice: Option<&[u32]> = Some(&[1, 2, 3]); + if let Some(slice) = slice { + println!("{}", slice[0]); + } + + // Try with copy + let slice: Option<[u32; 3]> = Some([1, 2, 3]); + if let Some(slice) = slice { + println!("{}", slice[0]); + } + + // Try with long slice and small indices + let slice: Option<[u32; 9]> = Some([1, 2, 3, 4, 5, 6, 7, 8, 9]); + if let Some(slice) = slice { + println!("{}", slice[2]); + println!("{}", slice[0]); + } + + // Multiple bindings + let slice_wrapped: SomeEnum<[u32; 3]> = SomeEnum::One([5, 6, 7]); + if let SomeEnum::One(slice) | SomeEnum::Three(slice) = slice_wrapped { + println!("{}", slice[0]); + } + + // Two lintable slices in one if let + let a_wrapped: SomeEnum<[u32; 3]> = SomeEnum::One([9, 5, 1]); + let b_wrapped: Option<[u32; 2]> = Some([4, 6]); + if let (SomeEnum::Three(a), Some(b)) = (a_wrapped, b_wrapped) { + println!("{} -> {}", a[2], b[1]); + } + + // This requires the slice values to be borrowed as the slice values can only be + // borrowed and `String` doesn't implement copy + let slice: Option<[String; 2]> = Some([String::from("1"), String::from("2")]); + if let Some(ref slice) = slice { + println!("{:?}", slice[1]); + } + println!("{:?}", slice); + + // This should not suggest using the `ref` keyword as the scrutinee is already + // a reference + let slice: Option<[String; 2]> = Some([String::from("1"), String::from("2")]); + if let Some(slice) = &slice { + println!("{:?}", slice[0]); + } + println!("{:?}", slice); +} + +fn slice_index_above_limit() { + let slice: Option<&[u32]> = Some(&[1, 2, 3]); + + if let Some(slice) = slice { + // Would cause a panic, IDK + println!("{}", slice[7]); + } +} + +fn slice_is_used() { + let slice: Option<&[u32]> = Some(&[1, 2, 3]); + if let Some(slice) = slice { + println!("{:?}", slice.len()); + } + + let slice: Option<&[u32]> = Some(&[1, 2, 3]); + if let Some(slice) = slice { + println!("{:?}", slice.to_vec()); + } + + let opt: Option<[String; 2]> = Some([String::from("Hello"), String::from("world")]); + if let Some(slice) = opt { + if !slice.is_empty() { + println!("first: {}", slice[0]); + } + } +} + +/// The slice is used by an external function and should therefore not be linted +fn check_slice_as_arg() { + fn is_interesting(slice: &[T; 2]) -> bool { + !slice.is_empty() + } + + let slice_wrapped: Option<[String; 2]> = Some([String::from("Hello"), String::from("world")]); + if let Some(slice) = &slice_wrapped { + if is_interesting(slice) { + println!("This is interesting {}", slice[0]); + } + } + println!("{:?}", slice_wrapped); +} + +fn check_slice_in_struct() { + #[derive(Debug)] + struct Wrapper<'a> { + inner: Option<&'a [String]>, + is_awesome: bool, + } + + impl<'a> Wrapper<'a> { + fn is_super_awesome(&self) -> bool { + self.is_awesome + } + } + + let inner = &[String::from("New"), String::from("World")]; + let wrap = Wrapper { + inner: Some(inner), + is_awesome: true, + }; + + // Test 1: Field access + if let Some(slice) = wrap.inner { + if wrap.is_awesome { + println!("This is awesome! {}", slice[0]); + } + } + + // Test 2: function access + if let Some(slice) = wrap.inner { + if wrap.is_super_awesome() { + println!("This is super awesome! {}", slice[0]); + } + } + println!("Complete wrap: {:?}", wrap); +} + +/// This would be a nice additional feature to have in the future, but adding it +/// now would make the PR too large. This is therefore only a test that we don't +/// lint cases we can't make a reasonable suggestion for +fn mutable_slice_index() { + // Mut access + let mut slice: Option<[String; 1]> = Some([String::from("Penguin")]); + if let Some(ref mut slice) = slice { + slice[0] = String::from("Mr. Penguin"); + } + println!("Use after modification: {:?}", slice); + + // Mut access on reference + let mut slice: Option<[String; 1]> = Some([String::from("Cat")]); + if let Some(slice) = &mut slice { + slice[0] = String::from("Lord Meow Meow"); + } + println!("Use after modification: {:?}", slice); +} + +/// The lint will ignore bindings with sub patterns as it would be hard +/// to build correct suggestions for these instances :) +fn binding_with_sub_pattern() { + let slice: Option<&[u32]> = Some(&[1, 2, 3]); + if let Some(slice @ [_, _, _]) = slice { + println!("{:?}", slice[2]); + } +} + +fn main() {} diff --git a/tests/ui/index_refutable_slice/if_let_slice_binding.stderr b/tests/ui/index_refutable_slice/if_let_slice_binding.stderr new file mode 100644 index 00000000000..a607df9b876 --- /dev/null +++ b/tests/ui/index_refutable_slice/if_let_slice_binding.stderr @@ -0,0 +1,158 @@ +error: this binding can be a slice pattern to avoid indexing + --> $DIR/if_let_slice_binding.rs:13:17 + | +LL | if let Some(slice) = slice { + | ^^^^^ + | +note: the lint level is defined here + --> $DIR/if_let_slice_binding.rs:1:9 + | +LL | #![deny(clippy::index_refutable_slice)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try using a slice pattern here + | +LL | if let Some([slice_0, ..]) = slice { + | ~~~~~~~~~~~~~ +help: and replace the index expressions here + | +LL | println!("{}", slice_0); + | ~~~~~~~ + +error: this binding can be a slice pattern to avoid indexing + --> $DIR/if_let_slice_binding.rs:19:17 + | +LL | if let Some(slice) = slice { + | ^^^^^ + | +help: try using a slice pattern here + | +LL | if let Some([slice_0, ..]) = slice { + | ~~~~~~~~~~~~~ +help: and replace the index expressions here + | +LL | println!("{}", slice_0); + | ~~~~~~~ + +error: this binding can be a slice pattern to avoid indexing + --> $DIR/if_let_slice_binding.rs:25:17 + | +LL | if let Some(slice) = slice { + | ^^^^^ + | +help: try using a slice pattern here + | +LL | if let Some([slice_0, _, slice_2, ..]) = slice { + | ~~~~~~~~~~~~~~~~~~~~~~~~~ +help: and replace the index expressions here + | +LL ~ println!("{}", slice_2); +LL ~ println!("{}", slice_0); + | + +error: this binding can be a slice pattern to avoid indexing + --> $DIR/if_let_slice_binding.rs:32:26 + | +LL | if let SomeEnum::One(slice) | SomeEnum::Three(slice) = slice_wrapped { + | ^^^^^ + | +help: try using a slice pattern here + | +LL | if let SomeEnum::One([slice_0, ..]) | SomeEnum::Three([slice_0, ..]) = slice_wrapped { + | ~~~~~~~~~~~~~ ~~~~~~~~~~~~~ +help: and replace the index expressions here + | +LL | println!("{}", slice_0); + | ~~~~~~~ + +error: this binding can be a slice pattern to avoid indexing + --> $DIR/if_let_slice_binding.rs:39:29 + | +LL | if let (SomeEnum::Three(a), Some(b)) = (a_wrapped, b_wrapped) { + | ^ + | +help: try using a slice pattern here + | +LL | if let (SomeEnum::Three([_, _, a_2, ..]), Some(b)) = (a_wrapped, b_wrapped) { + | ~~~~~~~~~~~~~~~ +help: and replace the index expressions here + | +LL | println!("{} -> {}", a_2, b[1]); + | ~~~ + +error: this binding can be a slice pattern to avoid indexing + --> $DIR/if_let_slice_binding.rs:39:38 + | +LL | if let (SomeEnum::Three(a), Some(b)) = (a_wrapped, b_wrapped) { + | ^ + | +help: try using a slice pattern here + | +LL | if let (SomeEnum::Three(a), Some([_, b_1, ..])) = (a_wrapped, b_wrapped) { + | ~~~~~~~~~~~~ +help: and replace the index expressions here + | +LL | println!("{} -> {}", a[2], b_1); + | ~~~ + +error: this binding can be a slice pattern to avoid indexing + --> $DIR/if_let_slice_binding.rs:46:21 + | +LL | if let Some(ref slice) = slice { + | ^^^^^ + | +help: try using a slice pattern here + | +LL | if let Some([_, ref slice_1, ..]) = slice { + | ~~~~~~~~~~~~~~~~~~~~ +help: and replace the index expressions here + | +LL | println!("{:?}", slice_1); + | ~~~~~~~ + +error: this binding can be a slice pattern to avoid indexing + --> $DIR/if_let_slice_binding.rs:54:17 + | +LL | if let Some(slice) = &slice { + | ^^^^^ + | +help: try using a slice pattern here + | +LL | if let Some([slice_0, ..]) = &slice { + | ~~~~~~~~~~~~~ +help: and replace the index expressions here + | +LL | println!("{:?}", slice_0); + | ~~~~~~~ + +error: this binding can be a slice pattern to avoid indexing + --> $DIR/if_let_slice_binding.rs:123:17 + | +LL | if let Some(slice) = wrap.inner { + | ^^^^^ + | +help: try using a slice pattern here + | +LL | if let Some([slice_0, ..]) = wrap.inner { + | ~~~~~~~~~~~~~ +help: and replace the index expressions here + | +LL | println!("This is awesome! {}", slice_0); + | ~~~~~~~ + +error: this binding can be a slice pattern to avoid indexing + --> $DIR/if_let_slice_binding.rs:130:17 + | +LL | if let Some(slice) = wrap.inner { + | ^^^^^ + | +help: try using a slice pattern here + | +LL | if let Some([slice_0, ..]) = wrap.inner { + | ~~~~~~~~~~~~~ +help: and replace the index expressions here + | +LL | println!("This is super awesome! {}", slice_0); + | ~~~~~~~ + +error: aborting due to 10 previous errors + diff --git a/tests/ui/index_refutable_slice/slice_indexing_in_macro.rs b/tests/ui/index_refutable_slice/slice_indexing_in_macro.rs new file mode 100644 index 00000000000..406e82083f8 --- /dev/null +++ b/tests/ui/index_refutable_slice/slice_indexing_in_macro.rs @@ -0,0 +1,28 @@ +#![deny(clippy::index_refutable_slice)] + +extern crate if_chain; +use if_chain::if_chain; + +macro_rules! if_let_slice_macro { + () => { + // This would normally be linted + let slice: Option<&[u32]> = Some(&[1, 2, 3]); + if let Some(slice) = slice { + println!("{}", slice[0]); + } + }; +} + +fn main() { + // Don't lint this + if_let_slice_macro!(); + + // Do lint this + if_chain! { + let slice: Option<&[u32]> = Some(&[1, 2, 3]); + if let Some(slice) = slice; + then { + println!("{}", slice[0]); + } + } +} diff --git a/tests/ui/index_refutable_slice/slice_indexing_in_macro.stderr b/tests/ui/index_refutable_slice/slice_indexing_in_macro.stderr new file mode 100644 index 00000000000..11b19428b4f --- /dev/null +++ b/tests/ui/index_refutable_slice/slice_indexing_in_macro.stderr @@ -0,0 +1,22 @@ +error: this binding can be a slice pattern to avoid indexing + --> $DIR/slice_indexing_in_macro.rs:23:21 + | +LL | if let Some(slice) = slice; + | ^^^^^ + | +note: the lint level is defined here + --> $DIR/slice_indexing_in_macro.rs:1:9 + | +LL | #![deny(clippy::index_refutable_slice)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try using a slice pattern here + | +LL | if let Some([slice_0, ..]) = slice; + | ~~~~~~~~~~~~~ +help: and replace the index expressions here + | +LL | println!("{}", slice_0); + | ~~~~~~~ + +error: aborting due to previous error + diff --git a/tests/ui/let_underscore_lock.rs b/tests/ui/let_underscore_lock.rs index 88fb216a743..539d74d1d4c 100644 --- a/tests/ui/let_underscore_lock.rs +++ b/tests/ui/let_underscore_lock.rs @@ -1,5 +1,7 @@ #![warn(clippy::let_underscore_lock)] +extern crate parking_lot; + fn main() { let m = std::sync::Mutex::new(()); let rw = std::sync::RwLock::new(()); @@ -10,4 +12,16 @@ fn main() { let _ = m.try_lock(); let _ = rw.try_read(); let _ = rw.try_write(); + + use parking_lot::{lock_api::RawMutex, Mutex, RwLock}; + + let p_m: Mutex<()> = Mutex::const_new(RawMutex::INIT, ()); + let _ = p_m.lock(); + + let p_m1 = Mutex::new(0); + let _ = p_m1.lock(); + + let p_rw = RwLock::new(0); + let _ = p_rw.read(); + let _ = p_rw.write(); } diff --git a/tests/ui/let_underscore_lock.stderr b/tests/ui/let_underscore_lock.stderr index 5d5f6059ef1..3a2bc17bf7b 100644 --- a/tests/ui/let_underscore_lock.stderr +++ b/tests/ui/let_underscore_lock.stderr @@ -1,5 +1,5 @@ error: non-binding let on a synchronization lock - --> $DIR/let_underscore_lock.rs:7:5 + --> $DIR/let_underscore_lock.rs:9:5 | LL | let _ = m.lock(); | ^^^^^^^^^^^^^^^^^ @@ -8,7 +8,7 @@ LL | let _ = 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:8:5 + --> $DIR/let_underscore_lock.rs:10:5 | LL | let _ = rw.read(); | ^^^^^^^^^^^^^^^^^^ @@ -16,7 +16,7 @@ LL | let _ = 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:9:5 + --> $DIR/let_underscore_lock.rs:11:5 | LL | let _ = rw.write(); | ^^^^^^^^^^^^^^^^^^^ @@ -24,7 +24,7 @@ LL | let _ = rw.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:10:5 + --> $DIR/let_underscore_lock.rs:12:5 | LL | let _ = m.try_lock(); | ^^^^^^^^^^^^^^^^^^^^^ @@ -32,7 +32,7 @@ LL | let _ = m.try_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:11:5 + --> $DIR/let_underscore_lock.rs:13:5 | LL | let _ = rw.try_read(); | ^^^^^^^^^^^^^^^^^^^^^^ @@ -40,12 +40,44 @@ LL | let _ = rw.try_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:12:5 + --> $DIR/let_underscore_lock.rs:14:5 | LL | let _ = rw.try_write(); | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` -error: aborting due to 6 previous errors +error: non-binding let on a synchronization lock + --> $DIR/let_underscore_lock.rs:19:5 + | +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 + | +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 + | +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 + | +LL | let _ = p_rw.write(); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop` + +error: aborting due to 10 previous errors diff --git a/tests/ui/manual_assert.edition2018.fixed b/tests/ui/manual_assert.edition2018.fixed index 11fe06c5724..6c2a25c37d8 100644 --- a/tests/ui/manual_assert.edition2018.fixed +++ b/tests/ui/manual_assert.edition2018.fixed @@ -2,7 +2,9 @@ // [edition2018] edition:2018 // [edition2021] edition:2021 // run-rustfix + #![warn(clippy::manual_assert)] +#![allow(clippy::nonminimal_bool)] fn main() { let a = vec![1, 2, 3]; diff --git a/tests/ui/manual_assert.edition2018.stderr b/tests/ui/manual_assert.edition2018.stderr index 03c03472f90..77511631e44 100644 --- a/tests/ui/manual_assert.edition2018.stderr +++ b/tests/ui/manual_assert.edition2018.stderr @@ -1,5 +1,5 @@ error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:22:5 + --> $DIR/manual_assert.rs:24:5 | LL | / if !a.is_empty() { LL | | panic!("qaqaq{:?}", a); @@ -9,7 +9,7 @@ LL | | } = note: `-D clippy::manual-assert` implied by `-D warnings` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:25:5 + --> $DIR/manual_assert.rs:27:5 | LL | / if !a.is_empty() { LL | | panic!("qwqwq"); @@ -17,7 +17,7 @@ LL | | } | |_____^ help: try: `assert!(a.is_empty(), "qwqwq");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:42:5 + --> $DIR/manual_assert.rs:44:5 | LL | / if b.is_empty() { LL | | panic!("panic1"); @@ -25,7 +25,7 @@ LL | | } | |_____^ help: try: `assert!(!b.is_empty(), "panic1");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:45:5 + --> $DIR/manual_assert.rs:47:5 | LL | / if b.is_empty() && a.is_empty() { LL | | panic!("panic2"); @@ -33,7 +33,7 @@ LL | | } | |_____^ help: try: `assert!(!(b.is_empty() && a.is_empty()), "panic2");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:48:5 + --> $DIR/manual_assert.rs:50:5 | LL | / if a.is_empty() && !b.is_empty() { LL | | panic!("panic3"); @@ -41,7 +41,7 @@ LL | | } | |_____^ help: try: `assert!(!(a.is_empty() && !b.is_empty()), "panic3");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:51:5 + --> $DIR/manual_assert.rs:53:5 | LL | / if b.is_empty() || a.is_empty() { LL | | panic!("panic4"); @@ -49,7 +49,7 @@ LL | | } | |_____^ help: try: `assert!(!(b.is_empty() || a.is_empty()), "panic4");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:54:5 + --> $DIR/manual_assert.rs:56:5 | LL | / if a.is_empty() || !b.is_empty() { LL | | panic!("panic5"); diff --git a/tests/ui/manual_assert.edition2021.fixed b/tests/ui/manual_assert.edition2021.fixed index 11fe06c5724..6c2a25c37d8 100644 --- a/tests/ui/manual_assert.edition2021.fixed +++ b/tests/ui/manual_assert.edition2021.fixed @@ -2,7 +2,9 @@ // [edition2018] edition:2018 // [edition2021] edition:2021 // run-rustfix + #![warn(clippy::manual_assert)] +#![allow(clippy::nonminimal_bool)] fn main() { let a = vec![1, 2, 3]; diff --git a/tests/ui/manual_assert.edition2021.stderr b/tests/ui/manual_assert.edition2021.stderr index 03c03472f90..77511631e44 100644 --- a/tests/ui/manual_assert.edition2021.stderr +++ b/tests/ui/manual_assert.edition2021.stderr @@ -1,5 +1,5 @@ error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:22:5 + --> $DIR/manual_assert.rs:24:5 | LL | / if !a.is_empty() { LL | | panic!("qaqaq{:?}", a); @@ -9,7 +9,7 @@ LL | | } = note: `-D clippy::manual-assert` implied by `-D warnings` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:25:5 + --> $DIR/manual_assert.rs:27:5 | LL | / if !a.is_empty() { LL | | panic!("qwqwq"); @@ -17,7 +17,7 @@ LL | | } | |_____^ help: try: `assert!(a.is_empty(), "qwqwq");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:42:5 + --> $DIR/manual_assert.rs:44:5 | LL | / if b.is_empty() { LL | | panic!("panic1"); @@ -25,7 +25,7 @@ LL | | } | |_____^ help: try: `assert!(!b.is_empty(), "panic1");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:45:5 + --> $DIR/manual_assert.rs:47:5 | LL | / if b.is_empty() && a.is_empty() { LL | | panic!("panic2"); @@ -33,7 +33,7 @@ LL | | } | |_____^ help: try: `assert!(!(b.is_empty() && a.is_empty()), "panic2");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:48:5 + --> $DIR/manual_assert.rs:50:5 | LL | / if a.is_empty() && !b.is_empty() { LL | | panic!("panic3"); @@ -41,7 +41,7 @@ LL | | } | |_____^ help: try: `assert!(!(a.is_empty() && !b.is_empty()), "panic3");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:51:5 + --> $DIR/manual_assert.rs:53:5 | LL | / if b.is_empty() || a.is_empty() { LL | | panic!("panic4"); @@ -49,7 +49,7 @@ LL | | } | |_____^ help: try: `assert!(!(b.is_empty() || a.is_empty()), "panic4");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:54:5 + --> $DIR/manual_assert.rs:56:5 | LL | / if a.is_empty() || !b.is_empty() { LL | | panic!("panic5"); diff --git a/tests/ui/manual_assert.fixed b/tests/ui/manual_assert.fixed index 11fe06c5724..6c2a25c37d8 100644 --- a/tests/ui/manual_assert.fixed +++ b/tests/ui/manual_assert.fixed @@ -2,7 +2,9 @@ // [edition2018] edition:2018 // [edition2021] edition:2021 // run-rustfix + #![warn(clippy::manual_assert)] +#![allow(clippy::nonminimal_bool)] fn main() { let a = vec![1, 2, 3]; diff --git a/tests/ui/manual_assert.rs b/tests/ui/manual_assert.rs index 8713426fc88..d3e0897488f 100644 --- a/tests/ui/manual_assert.rs +++ b/tests/ui/manual_assert.rs @@ -2,7 +2,9 @@ // [edition2018] edition:2018 // [edition2021] edition:2021 // run-rustfix + #![warn(clippy::manual_assert)] +#![allow(clippy::nonminimal_bool)] fn main() { let a = vec![1, 2, 3]; diff --git a/tests/ui/manual_map_option_2.fixed b/tests/ui/manual_map_option_2.fixed index 8cc12149403..ebf3f8cabd4 100644 --- a/tests/ui/manual_map_option_2.fixed +++ b/tests/ui/manual_map_option_2.fixed @@ -47,4 +47,14 @@ fn main() { let _ = s.as_ref().map(|x| { if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }); + + // Issue #7820 + unsafe fn f(x: u32) -> u32 { + x + } + unsafe { + let _ = Some(0).map(|x| f(x)); + } + let _ = Some(0).map(|x| unsafe { f(x) }); + let _ = Some(0).map(|x| unsafe { f(x) }); } diff --git a/tests/ui/manual_map_option_2.rs b/tests/ui/manual_map_option_2.rs index 0862b201ead..1382d9af0aa 100644 --- a/tests/ui/manual_map_option_2.rs +++ b/tests/ui/manual_map_option_2.rs @@ -53,4 +53,23 @@ fn main() { }), None => None, }; + + // Issue #7820 + unsafe fn f(x: u32) -> u32 { + x + } + unsafe { + let _ = match Some(0) { + Some(x) => Some(f(x)), + None => None, + }; + } + let _ = match Some(0) { + Some(x) => unsafe { Some(f(x)) }, + None => None, + }; + let _ = match Some(0) { + Some(x) => Some(unsafe { f(x) }), + None => None, + }; } diff --git a/tests/ui/manual_map_option_2.stderr b/tests/ui/manual_map_option_2.stderr index 711ff6c4a4b..d35b6252fb8 100644 --- a/tests/ui/manual_map_option_2.stderr +++ b/tests/ui/manual_map_option_2.stderr @@ -39,5 +39,35 @@ LL + if let Some(ref s) = s { (x.clone(), s) } else { panic!() } LL ~ }); | -error: aborting due to 2 previous errors +error: manual implementation of `Option::map` + --> $DIR/manual_map_option_2.rs:62:17 + | +LL | let _ = match Some(0) { + | _________________^ +LL | | Some(x) => Some(f(x)), +LL | | None => None, +LL | | }; + | |_________^ help: try this: `Some(0).map(|x| f(x))` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option_2.rs:67:13 + | +LL | let _ = match Some(0) { + | _____________^ +LL | | Some(x) => unsafe { Some(f(x)) }, +LL | | None => None, +LL | | }; + | |_____^ help: try this: `Some(0).map(|x| unsafe { f(x) })` + +error: manual implementation of `Option::map` + --> $DIR/manual_map_option_2.rs:71:13 + | +LL | let _ = match Some(0) { + | _____________^ +LL | | Some(x) => Some(unsafe { f(x) }), +LL | | None => None, +LL | | }; + | |_____^ help: try this: `Some(0).map(|x| unsafe { f(x) })` + +error: aborting due to 5 previous errors diff --git a/tests/ui/manual_split_once.fixed b/tests/ui/manual_split_once.fixed index 992baf1f185..d5113df569a 100644 --- a/tests/ui/manual_split_once.fixed +++ b/tests/ui/manual_split_once.fixed @@ -2,7 +2,7 @@ #![feature(custom_inner_attributes)] #![warn(clippy::manual_split_once)] -#![allow(clippy::iter_skip_next, clippy::iter_nth_zero)] +#![allow(clippy::iter_skip_next, clippy::iter_nth_zero, clippy::needless_splitn)] extern crate itertools; @@ -38,8 +38,8 @@ fn main() { let _ = [0, 1, 2].splitn(2, |&x| x == 1).nth(1).unwrap(); // `rsplitn` gives the results in the reverse order of `rsplit_once` - let _ = "key=value".rsplit_once('=').unwrap().1; - let _ = "key=value".rsplit_once('=').map_or("key=value", |x| x.0); + let _ = "key=value".rsplitn(2, '=').next().unwrap(); + let _ = "key=value".rsplit_once('=').unwrap().0; let _ = "key=value".rsplit_once('=').map(|x| x.1); let (_, _) = "key=value".rsplit_once('=').map(|(x, y)| (y, x)).unwrap(); } diff --git a/tests/ui/manual_split_once.rs b/tests/ui/manual_split_once.rs index 4f92ab6b812..80e02952dbd 100644 --- a/tests/ui/manual_split_once.rs +++ b/tests/ui/manual_split_once.rs @@ -2,7 +2,7 @@ #![feature(custom_inner_attributes)] #![warn(clippy::manual_split_once)] -#![allow(clippy::iter_skip_next, clippy::iter_nth_zero)] +#![allow(clippy::iter_skip_next, clippy::iter_nth_zero, clippy::needless_splitn)] extern crate itertools; diff --git a/tests/ui/manual_split_once.stderr b/tests/ui/manual_split_once.stderr index 7bea2303d92..af9c7a2d41b 100644 --- a/tests/ui/manual_split_once.stderr +++ b/tests/ui/manual_split_once.stderr @@ -72,17 +72,11 @@ error: manual implementation of `split_once` LL | let _ = s.splitn(2, "key=value").skip(1).next()?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once("key=value")?.1` -error: manual implementation of `rsplit_once` - --> $DIR/manual_split_once.rs:41:13 - | -LL | let _ = "key=value".rsplitn(2, '=').next().unwrap(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".rsplit_once('=').unwrap().1` - error: manual implementation of `rsplit_once` --> $DIR/manual_split_once.rs:42:13 | LL | let _ = "key=value".rsplitn(2, '=').nth(1).unwrap(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".rsplit_once('=').map_or("key=value", |x| x.0)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".rsplit_once('=').unwrap().0` error: manual implementation of `rsplit_once` --> $DIR/manual_split_once.rs:43:13 @@ -102,5 +96,5 @@ error: manual implementation of `split_once` LL | let _ = "key=value".splitn(2, '=').nth(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').unwrap().1` -error: aborting due to 17 previous errors +error: aborting due to 16 previous errors diff --git a/tests/ui/match_overlapping_arm.rs b/tests/ui/match_overlapping_arm.rs index 845986a4ead..2f85e635713 100644 --- a/tests/ui/match_overlapping_arm.rs +++ b/tests/ui/match_overlapping_arm.rs @@ -100,6 +100,15 @@ fn overlapping() { _ => (), } + // Issue #7816 - overlap after included range + match 42 { + 5..=10 => (), + 0..=20 => (), + 21..=30 => (), + 21..=40 => (), + _ => (), + } + // Issue #7829 match 0 { -1..=1 => (), @@ -107,6 +116,15 @@ fn overlapping() { _ => (), } + // Only warn about the first if there are multiple overlaps + match 42u128 { + 0..=0x0000_0000_0000_00ff => (), + 0..=0x0000_0000_0000_ffff => (), + 0..=0x0000_0000_ffff_ffff => (), + 0..=0xffff_ffff_ffff_ffff => (), + _ => (), + } + if let None = Some(42) { // nothing } else if let None = Some(42) { diff --git a/tests/ui/match_overlapping_arm.stderr b/tests/ui/match_overlapping_arm.stderr index c2b3f173c2b..b81bb1ecfae 100644 --- a/tests/ui/match_overlapping_arm.stderr +++ b/tests/ui/match_overlapping_arm.stderr @@ -71,5 +71,29 @@ note: overlaps with this LL | ..26 => println!("..26"), | ^^^^ -error: aborting due to 6 previous errors +error: some ranges overlap + --> $DIR/match_overlapping_arm.rs:107:9 + | +LL | 21..=30 => (), + | ^^^^^^^ + | +note: overlaps with this + --> $DIR/match_overlapping_arm.rs:108:9 + | +LL | 21..=40 => (), + | ^^^^^^^ + +error: some ranges overlap + --> $DIR/match_overlapping_arm.rs:121:9 + | +LL | 0..=0x0000_0000_0000_00ff => (), + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: overlaps with this + --> $DIR/match_overlapping_arm.rs:122:9 + | +LL | 0..=0x0000_0000_0000_ffff => (), + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors diff --git a/tests/ui/min_rust_version_attr.rs b/tests/ui/min_rust_version_attr.rs index 8d9fc5a864d..c5f221220ec 100644 --- a/tests/ui/min_rust_version_attr.rs +++ b/tests/ui/min_rust_version_attr.rs @@ -137,6 +137,14 @@ fn unnest_or_patterns() { if let TS(0, x) | TS(1, x) = TS(0, 0) {} } +#[cfg_attr(rustfmt, rustfmt_skip)] +fn deprecated_cfg_attr() {} + +#[warn(clippy::cast_lossless)] +fn int_from_bool() -> u8 { + true as u8 +} + fn main() { filter_map_next(); checked_conversion(); @@ -153,6 +161,19 @@ fn main() { map_unwrap_or(); missing_const_for_fn(); unnest_or_patterns(); + int_from_bool(); +} + +mod just_under_msrv { + #![feature(custom_inner_attributes)] + #![clippy::msrv = "1.44.0"] + + fn main() { + let s = "hello, world!"; + if s.starts_with("hello, ") { + assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); + } + } } mod meets_msrv { @@ -167,7 +188,7 @@ mod meets_msrv { } } -mod just_under_msrv { +mod just_above_msrv { #![feature(custom_inner_attributes)] #![clippy::msrv = "1.46.0"] @@ -178,15 +199,3 @@ mod just_under_msrv { } } } - -mod just_above_msrv { - #![feature(custom_inner_attributes)] - #![clippy::msrv = "1.44.0"] - - fn main() { - let s = "hello, world!"; - if s.starts_with("hello, ") { - assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); - } - } -} diff --git a/tests/ui/min_rust_version_attr.stderr b/tests/ui/min_rust_version_attr.stderr index 360dcfb230c..6b3fdb0844b 100644 --- a/tests/ui/min_rust_version_attr.stderr +++ b/tests/ui/min_rust_version_attr.stderr @@ -1,12 +1,12 @@ error: stripping a prefix manually - --> $DIR/min_rust_version_attr.rs:165:24 + --> $DIR/min_rust_version_attr.rs:186: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:164:9 + --> $DIR/min_rust_version_attr.rs:185: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:177:24 + --> $DIR/min_rust_version_attr.rs:198:24 | LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!"); | ^^^^^^^^^^^^^^^^^^^^ | note: the prefix was tested here - --> $DIR/min_rust_version_attr.rs:176:9 + --> $DIR/min_rust_version_attr.rs:197:9 | LL | if s.starts_with("hello, ") { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/needless_collect_indirect.rs b/tests/ui/needless_collect_indirect.rs index 2c94235b8f5..1f11d1f8d56 100644 --- a/tests/ui/needless_collect_indirect.rs +++ b/tests/ui/needless_collect_indirect.rs @@ -76,6 +76,37 @@ mod issue7110 { } } +mod issue7975 { + use super::*; + + fn direct_mapping_with_used_mutable_reference() -> Vec<()> { + let test_vec: Vec<()> = vec![]; + let mut vec_2: Vec<()> = vec![]; + let mut_ref = &mut vec_2; + let collected_vec: Vec<_> = test_vec.into_iter().map(|_| mut_ref.push(())).collect(); + collected_vec.into_iter().map(|_| mut_ref.push(())).collect() + } + + fn indirectly_mapping_with_used_mutable_reference() -> Vec<()> { + let test_vec: Vec<()> = vec![]; + let mut vec_2: Vec<()> = vec![]; + let mut_ref = &mut vec_2; + let collected_vec: Vec<_> = test_vec.into_iter().map(|_| mut_ref.push(())).collect(); + let iter = collected_vec.into_iter(); + iter.map(|_| mut_ref.push(())).collect() + } + + fn indirect_collect_after_indirect_mapping_with_used_mutable_reference() -> Vec<()> { + let test_vec: Vec<()> = vec![]; + let mut vec_2: Vec<()> = vec![]; + let mut_ref = &mut vec_2; + let collected_vec: Vec<_> = test_vec.into_iter().map(|_| mut_ref.push(())).collect(); + let iter = collected_vec.into_iter(); + let mapped_iter = iter.map(|_| mut_ref.push(())); + mapped_iter.collect() + } +} + fn allow_test() { #[allow(clippy::needless_collect)] let v = [1].iter().collect::>(); diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index 812ce7163cd..83f467c8400 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -108,6 +108,7 @@ fn test_return_in_macro() { } mod issue6501 { + #[allow(clippy::unnecessary_lazy_evaluations)] fn foo(bar: Result<(), ()>) { bar.unwrap_or_else(|_| {}) } diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index c42567b517c..341caf18bd6 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -108,6 +108,7 @@ fn test_return_in_macro() { } mod issue6501 { + #[allow(clippy::unnecessary_lazy_evaluations)] fn foo(bar: Result<(), ()>) { bar.unwrap_or_else(|_| return) } diff --git a/tests/ui/needless_return.stderr b/tests/ui/needless_return.stderr index 74dda971fda..c0abc2c63dd 100644 --- a/tests/ui/needless_return.stderr +++ b/tests/ui/needless_return.stderr @@ -85,109 +85,109 @@ LL | return String::new(); | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::new()` error: unneeded `return` statement - --> $DIR/needless_return.rs:112:32 + --> $DIR/needless_return.rs:113:32 | LL | bar.unwrap_or_else(|_| return) | ^^^^^^ help: replace `return` with an empty block: `{}` error: unneeded `return` statement - --> $DIR/needless_return.rs:117:13 + --> $DIR/needless_return.rs:118:13 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:119:20 + --> $DIR/needless_return.rs:120:20 | LL | let _ = || return; | ^^^^^^ help: replace `return` with an empty block: `{}` error: unneeded `return` statement - --> $DIR/needless_return.rs:125:32 + --> $DIR/needless_return.rs:126:32 | LL | res.unwrap_or_else(|_| return Foo) | ^^^^^^^^^^ help: remove `return`: `Foo` error: unneeded `return` statement - --> $DIR/needless_return.rs:134:5 + --> $DIR/needless_return.rs:135:5 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:138:5 + --> $DIR/needless_return.rs:139:5 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:143:9 + --> $DIR/needless_return.rs:144:9 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:145:9 + --> $DIR/needless_return.rs:146:9 | LL | return false; | ^^^^^^^^^^^^^ help: remove `return`: `false` error: unneeded `return` statement - --> $DIR/needless_return.rs:151:17 + --> $DIR/needless_return.rs:152:17 | LL | true => return false, | ^^^^^^^^^^^^ help: remove `return`: `false` error: unneeded `return` statement - --> $DIR/needless_return.rs:153:13 + --> $DIR/needless_return.rs:154:13 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:160:9 + --> $DIR/needless_return.rs:161:9 | LL | return true; | ^^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:162:16 + --> $DIR/needless_return.rs:163:16 | LL | let _ = || return true; | ^^^^^^^^^^^ help: remove `return`: `true` error: unneeded `return` statement - --> $DIR/needless_return.rs:170:5 + --> $DIR/needless_return.rs:171:5 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:175:9 + --> $DIR/needless_return.rs:176:9 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:177:9 + --> $DIR/needless_return.rs:178:9 | LL | return; | ^^^^^^^ help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:184:14 + --> $DIR/needless_return.rs:185:14 | LL | _ => return, | ^^^^^^ help: replace `return` with an empty block: `{}` error: unneeded `return` statement - --> $DIR/needless_return.rs:199:9 + --> $DIR/needless_return.rs:200:9 | LL | return String::from("test"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::from("test")` error: unneeded `return` statement - --> $DIR/needless_return.rs:201:9 + --> $DIR/needless_return.rs:202:9 | LL | return String::new(); | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::new()` diff --git a/tests/ui/needless_splitn.fixed b/tests/ui/needless_splitn.fixed new file mode 100644 index 00000000000..f6a4b2f17d3 --- /dev/null +++ b/tests/ui/needless_splitn.fixed @@ -0,0 +1,27 @@ +// run-rustfix +// edition:2018 + +#![feature(custom_inner_attributes)] +#![warn(clippy::needless_splitn)] +#![allow(clippy::iter_skip_next, clippy::iter_nth_zero, clippy::manual_split_once)] + +extern crate itertools; + +#[allow(unused_imports)] +use itertools::Itertools; + +fn main() { + let str = "key=value=end"; + let _ = str.split('=').next(); + let _ = str.split('=').nth(0); + let _ = str.splitn(2, '=').nth(1); + let (_, _) = str.splitn(2, '=').next_tuple().unwrap(); + let (_, _) = str.split('=').next_tuple().unwrap(); + let _: Vec<&str> = str.splitn(3, '=').collect(); + + let _ = str.rsplit('=').next(); + let _ = str.rsplit('=').nth(0); + let _ = str.rsplitn(2, '=').nth(1); + let (_, _) = str.rsplitn(2, '=').next_tuple().unwrap(); + let (_, _) = str.rsplit('=').next_tuple().unwrap(); +} diff --git a/tests/ui/needless_splitn.rs b/tests/ui/needless_splitn.rs new file mode 100644 index 00000000000..6ba32255bb2 --- /dev/null +++ b/tests/ui/needless_splitn.rs @@ -0,0 +1,27 @@ +// run-rustfix +// edition:2018 + +#![feature(custom_inner_attributes)] +#![warn(clippy::needless_splitn)] +#![allow(clippy::iter_skip_next, clippy::iter_nth_zero, clippy::manual_split_once)] + +extern crate itertools; + +#[allow(unused_imports)] +use itertools::Itertools; + +fn main() { + let str = "key=value=end"; + let _ = str.splitn(2, '=').next(); + let _ = str.splitn(2, '=').nth(0); + let _ = str.splitn(2, '=').nth(1); + let (_, _) = str.splitn(2, '=').next_tuple().unwrap(); + let (_, _) = str.splitn(3, '=').next_tuple().unwrap(); + let _: Vec<&str> = str.splitn(3, '=').collect(); + + let _ = str.rsplitn(2, '=').next(); + let _ = str.rsplitn(2, '=').nth(0); + let _ = str.rsplitn(2, '=').nth(1); + let (_, _) = str.rsplitn(2, '=').next_tuple().unwrap(); + let (_, _) = str.rsplitn(3, '=').next_tuple().unwrap(); +} diff --git a/tests/ui/needless_splitn.stderr b/tests/ui/needless_splitn.stderr new file mode 100644 index 00000000000..66de2256554 --- /dev/null +++ b/tests/ui/needless_splitn.stderr @@ -0,0 +1,40 @@ +error: unnecessary use of `splitn` + --> $DIR/needless_splitn.rs:15:13 + | +LL | let _ = str.splitn(2, '=').next(); + | ^^^^^^^^^^^^^^^^^^ help: try this: `str.split('=')` + | + = note: `-D clippy::needless-splitn` implied by `-D warnings` + +error: unnecessary use of `splitn` + --> $DIR/needless_splitn.rs:16:13 + | +LL | let _ = str.splitn(2, '=').nth(0); + | ^^^^^^^^^^^^^^^^^^ help: try this: `str.split('=')` + +error: unnecessary use of `splitn` + --> $DIR/needless_splitn.rs:19:18 + | +LL | let (_, _) = str.splitn(3, '=').next_tuple().unwrap(); + | ^^^^^^^^^^^^^^^^^^ help: try this: `str.split('=')` + +error: unnecessary use of `rsplitn` + --> $DIR/needless_splitn.rs:22:13 + | +LL | let _ = str.rsplitn(2, '=').next(); + | ^^^^^^^^^^^^^^^^^^^ help: try this: `str.rsplit('=')` + +error: unnecessary use of `rsplitn` + --> $DIR/needless_splitn.rs:23:13 + | +LL | let _ = str.rsplitn(2, '=').nth(0); + | ^^^^^^^^^^^^^^^^^^^ help: try this: `str.rsplit('=')` + +error: unnecessary use of `rsplitn` + --> $DIR/needless_splitn.rs:26:18 + | +LL | let (_, _) = str.rsplitn(3, '=').next_tuple().unwrap(); + | ^^^^^^^^^^^^^^^^^^^ help: try this: `str.rsplit('=')` + +error: aborting due to 6 previous errors + diff --git a/tests/ui/no_effect.rs b/tests/ui/no_effect.rs index 7bcc4cad0d3..5427c88faf3 100644 --- a/tests/ui/no_effect.rs +++ b/tests/ui/no_effect.rs @@ -1,4 +1,4 @@ -#![feature(box_syntax)] +#![feature(box_syntax, fn_traits, unboxed_closures)] #![warn(clippy::no_effect_underscore_binding)] #![allow(dead_code)] #![allow(path_statements)] @@ -58,6 +58,36 @@ unsafe fn unsafe_fn() -> i32 { 0 } +struct GreetStruct1; + +impl FnOnce<(&str,)> for GreetStruct1 { + type Output = (); + + extern "rust-call" fn call_once(self, (who,): (&str,)) -> Self::Output { + println!("hello {}", who); + } +} + +struct GreetStruct2(); + +impl FnOnce<(&str,)> for GreetStruct2 { + type Output = (); + + extern "rust-call" fn call_once(self, (who,): (&str,)) -> Self::Output { + println!("hello {}", who); + } +} + +struct GreetStruct3 {} + +impl FnOnce<(&str,)> for GreetStruct3 { + type Output = (); + + extern "rust-call" fn call_once(self, (who,): (&str,)) -> Self::Output { + println!("hello {}", who); + } +} + fn main() { let s = get_struct(); let s2 = get_struct(); @@ -108,4 +138,7 @@ fn main() { DropTuple(0); DropEnum::Tuple(0); DropEnum::Struct { field: 0 }; + GreetStruct1("world"); + GreetStruct2()("world"); + GreetStruct3 {}("world"); } diff --git a/tests/ui/no_effect.stderr b/tests/ui/no_effect.stderr index a5dbc9fef45..06b88bb5bee 100644 --- a/tests/ui/no_effect.stderr +++ b/tests/ui/no_effect.stderr @@ -1,5 +1,5 @@ error: statement with no effect - --> $DIR/no_effect.rs:65:5 + --> $DIR/no_effect.rs:95:5 | LL | 0; | ^^ @@ -7,157 +7,157 @@ LL | 0; = note: `-D clippy::no-effect` implied by `-D warnings` error: statement with no effect - --> $DIR/no_effect.rs:66:5 + --> $DIR/no_effect.rs:96:5 | LL | s2; | ^^^ error: statement with no effect - --> $DIR/no_effect.rs:67:5 + --> $DIR/no_effect.rs:97:5 | LL | Unit; | ^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:68:5 + --> $DIR/no_effect.rs:98:5 | LL | Tuple(0); | ^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:69:5 + --> $DIR/no_effect.rs:99:5 | LL | Struct { field: 0 }; | ^^^^^^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:70:5 + --> $DIR/no_effect.rs:100:5 | LL | Struct { ..s }; | ^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:71:5 + --> $DIR/no_effect.rs:101:5 | LL | Union { a: 0 }; | ^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:72:5 + --> $DIR/no_effect.rs:102:5 | LL | Enum::Tuple(0); | ^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:73:5 + --> $DIR/no_effect.rs:103:5 | LL | Enum::Struct { field: 0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:74:5 + --> $DIR/no_effect.rs:104:5 | LL | 5 + 6; | ^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:75:5 + --> $DIR/no_effect.rs:105:5 | LL | *&42; | ^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:76:5 + --> $DIR/no_effect.rs:106:5 | LL | &6; | ^^^ error: statement with no effect - --> $DIR/no_effect.rs:77:5 + --> $DIR/no_effect.rs:107:5 | LL | (5, 6, 7); | ^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:78:5 + --> $DIR/no_effect.rs:108:5 | LL | box 42; | ^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:79:5 + --> $DIR/no_effect.rs:109:5 | LL | ..; | ^^^ error: statement with no effect - --> $DIR/no_effect.rs:80:5 + --> $DIR/no_effect.rs:110:5 | LL | 5..; | ^^^^ error: statement with no effect - --> $DIR/no_effect.rs:81:5 + --> $DIR/no_effect.rs:111:5 | LL | ..5; | ^^^^ error: statement with no effect - --> $DIR/no_effect.rs:82:5 + --> $DIR/no_effect.rs:112:5 | LL | 5..6; | ^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:83:5 + --> $DIR/no_effect.rs:113:5 | LL | 5..=6; | ^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:84:5 + --> $DIR/no_effect.rs:114:5 | LL | [42, 55]; | ^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:85:5 + --> $DIR/no_effect.rs:115:5 | LL | [42, 55][1]; | ^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:86:5 + --> $DIR/no_effect.rs:116:5 | LL | (42, 55).1; | ^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:87:5 + --> $DIR/no_effect.rs:117:5 | LL | [42; 55]; | ^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:88:5 + --> $DIR/no_effect.rs:118:5 | LL | [42; 55][13]; | ^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:90:5 + --> $DIR/no_effect.rs:120:5 | LL | || x += 5; | ^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:92:5 + --> $DIR/no_effect.rs:122:5 | LL | FooString { s: s }; | ^^^^^^^^^^^^^^^^^^^ error: binding to `_` prefixed variable with no side-effect - --> $DIR/no_effect.rs:93:5 + --> $DIR/no_effect.rs:123:5 | LL | let _unused = 1; | ^^^^^^^^^^^^^^^^ @@ -165,19 +165,19 @@ LL | let _unused = 1; = note: `-D clippy::no-effect-underscore-binding` implied by `-D warnings` error: binding to `_` prefixed variable with no side-effect - --> $DIR/no_effect.rs:94:5 + --> $DIR/no_effect.rs:124:5 | LL | let _penguin = || println!("Some helpful closure"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: binding to `_` prefixed variable with no side-effect - --> $DIR/no_effect.rs:95:5 + --> $DIR/no_effect.rs:125:5 | LL | let _duck = Struct { field: 0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: binding to `_` prefixed variable with no side-effect - --> $DIR/no_effect.rs:96:5 + --> $DIR/no_effect.rs:126:5 | LL | let _cat = [2, 4, 6, 8][2]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/option_if_let_else.fixed b/tests/ui/option_if_let_else.fixed index 9cb6a9d1ecc..ce3093c542a 100644 --- a/tests/ui/option_if_let_else.fixed +++ b/tests/ui/option_if_let_else.fixed @@ -75,6 +75,17 @@ fn negative_tests(arg: Option) -> u32 { 7 } +// #7973 +fn pattern_to_vec(pattern: &str) -> Vec { + pattern + .trim_matches('/') + .split('/') + .flat_map(|s| { + s.find('.').map_or_else(|| vec![s.to_string()], |idx| vec![s[..idx].to_string(), s[idx..].to_string()]) + }) + .collect::>() +} + fn main() { let optional = Some(5); let _ = optional.map_or(5, |x| x + 2); @@ -110,7 +121,7 @@ fn main() { let s = String::new(); // Lint, both branches immutably borrow `s`. - let _ = Some(0).map_or_else(|| s.len(), |x| s.len() + x); + let _ = Some(0).map_or(s.len(), |x| s.len() + x); let s = String::new(); // Lint, `Some` branch consumes `s`, but else branch doesn't use `s`. @@ -146,4 +157,6 @@ fn main() { // Don't lint. `await` can't be moved into a closure. let _ = if let Some(x) = Some(0) { _f1(x).await } else { 0 }; } + + let _ = pattern_to_vec("hello world"); } diff --git a/tests/ui/option_if_let_else.rs b/tests/ui/option_if_let_else.rs index b3ba5eb870a..c228b2f43d1 100644 --- a/tests/ui/option_if_let_else.rs +++ b/tests/ui/option_if_let_else.rs @@ -94,6 +94,21 @@ fn negative_tests(arg: Option) -> u32 { 7 } +// #7973 +fn pattern_to_vec(pattern: &str) -> Vec { + pattern + .trim_matches('/') + .split('/') + .flat_map(|s| { + if let Some(idx) = s.find('.') { + vec![s[..idx].to_string(), s[idx..].to_string()] + } else { + vec![s.to_string()] + } + }) + .collect::>() +} + fn main() { let optional = Some(5); let _ = if let Some(x) = optional { x + 2 } else { 5 }; @@ -171,4 +186,6 @@ fn main() { // Don't lint. `await` can't be moved into a closure. let _ = if let Some(x) = Some(0) { _f1(x).await } else { 0 }; } + + let _ = pattern_to_vec("hello world"); } diff --git a/tests/ui/option_if_let_else.stderr b/tests/ui/option_if_let_else.stderr index 685bb48ea37..4e64cd7cdb1 100644 --- a/tests/ui/option_if_let_else.stderr +++ b/tests/ui/option_if_let_else.stderr @@ -142,14 +142,24 @@ LL + y LL ~ }, |x| x * x * x * x); | +error: use Option::map_or_else instead of an if let/else + --> $DIR/option_if_let_else.rs:103:13 + | +LL | / if let Some(idx) = s.find('.') { +LL | | vec![s[..idx].to_string(), s[idx..].to_string()] +LL | | } else { +LL | | vec![s.to_string()] +LL | | } + | |_____________^ help: try: `s.find('.').map_or_else(|| vec![s.to_string()], |idx| vec![s[..idx].to_string(), s[idx..].to_string()])` + error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:99:13 + --> $DIR/option_if_let_else.rs:114:13 | LL | let _ = if let Some(x) = optional { x + 2 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `optional.map_or(5, |x| x + 2)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:108:13 + --> $DIR/option_if_let_else.rs:123:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -170,14 +180,14 @@ LL + } LL ~ }); | -error: use Option::map_or_else instead of an if let/else - --> $DIR/option_if_let_else.rs:136:13 +error: use Option::map_or instead of an if let/else + --> $DIR/option_if_let_else.rs:151:13 | LL | let _ = if let Some(x) = Some(0) { s.len() + x } else { s.len() }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(0).map_or_else(|| s.len(), |x| s.len() + x)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(0).map_or(s.len(), |x| s.len() + x)` error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:140:13 + --> $DIR/option_if_let_else.rs:155:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -196,5 +206,5 @@ LL + s.len() + x LL ~ }); | -error: aborting due to 14 previous errors +error: aborting due to 15 previous errors diff --git a/tests/ui/option_map_or_none.fixed b/tests/ui/option_map_or_none.fixed index d80c3c7c1b7..177054f763b 100644 --- a/tests/ui/option_map_or_none.fixed +++ b/tests/ui/option_map_or_none.fixed @@ -4,13 +4,19 @@ fn main() { let opt = Some(1); + let bar = |_| Some(1); // Check `OPTION_MAP_OR_NONE`. // Single line case. - let _ = opt.and_then(|x| Some(x + 1)); + let _: Option = opt.map(|x| x + 1); // Multi-line case. #[rustfmt::skip] - let _ = opt.and_then(|x| { - Some(x + 1) - }); + let _: Option = opt.map(|x| x + 1); + // function returning `Option` + let _: Option = opt.and_then(bar); + let _: Option = opt.and_then(|x| { + let offset = 0; + let height = x; + Some(offset + height) + }); } diff --git a/tests/ui/option_map_or_none.rs b/tests/ui/option_map_or_none.rs index 629842419e5..6908546d325 100644 --- a/tests/ui/option_map_or_none.rs +++ b/tests/ui/option_map_or_none.rs @@ -4,13 +4,21 @@ fn main() { let opt = Some(1); + let bar = |_| Some(1); // Check `OPTION_MAP_OR_NONE`. // Single line case. - let _ = opt.map_or(None, |x| Some(x + 1)); + let _: Option = opt.map_or(None, |x| Some(x + 1)); // Multi-line case. #[rustfmt::skip] - let _ = opt.map_or(None, |x| { + let _: Option = opt.map_or(None, |x| { Some(x + 1) }); + // function returning `Option` + let _: Option = opt.map_or(None, bar); + let _: Option = opt.map_or(None, |x| { + let offset = 0; + let height = x; + Some(offset + height) + }); } diff --git a/tests/ui/option_map_or_none.stderr b/tests/ui/option_map_or_none.stderr index 27d68b85e6f..11bdd887b6d 100644 --- a/tests/ui/option_map_or_none.stderr +++ b/tests/ui/option_map_or_none.stderr @@ -1,26 +1,45 @@ -error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `and_then(..)` instead - --> $DIR/option_map_or_none.rs:10:13 +error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `map(..)` instead + --> $DIR/option_map_or_none.rs:11:26 | -LL | let _ = opt.map_or(None, |x| Some(x + 1)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `opt.and_then(|x| Some(x + 1))` +LL | let _: Option = opt.map_or(None, |x| Some(x + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `map` instead: `opt.map(|x| x + 1)` | = note: `-D clippy::option-map-or-none` implied by `-D warnings` -error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `and_then(..)` instead - --> $DIR/option_map_or_none.rs:13:13 +error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `map(..)` instead + --> $DIR/option_map_or_none.rs:14:26 | -LL | let _ = opt.map_or(None, |x| { - | _____________^ +LL | let _: Option = opt.map_or(None, |x| { + | __________________________^ LL | | Some(x + 1) LL | | }); - | |_________________________^ + | |_________________________^ help: try using `map` instead: `opt.map(|x| x + 1)` + +error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `and_then(..)` instead + --> $DIR/option_map_or_none.rs:18:26 + | +LL | let _: Option = opt.map_or(None, bar); + | ^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `opt.and_then(bar)` + +error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `and_then(..)` instead + --> $DIR/option_map_or_none.rs:19:26 + | +LL | let _: Option = opt.map_or(None, |x| { + | __________________________^ +LL | | let offset = 0; +LL | | let height = x; +LL | | Some(offset + height) +LL | | }); + | |______^ | help: try using `and_then` instead | -LL ~ let _ = opt.and_then(|x| { -LL + Some(x + 1) -LL ~ }); +LL ~ let _: Option = opt.and_then(|x| { +LL + let offset = 0; +LL + let height = x; +LL + Some(offset + height) +LL ~ }); | -error: aborting due to 2 previous errors +error: aborting due to 4 previous errors diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index c2f94d0e857..d6d6ab49734 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -43,7 +43,7 @@ fn or_fun_call() { with_enum.unwrap_or(Enum::A(5)); let with_const_fn = Some(Duration::from_secs(1)); - with_const_fn.unwrap_or_else(|| Duration::from_secs(5)); + with_const_fn.unwrap_or(Duration::from_secs(5)); let with_constructor = Some(vec![1]); with_constructor.unwrap_or_else(make); @@ -79,16 +79,16 @@ fn or_fun_call() { without_default.unwrap_or_else(Foo::new); let mut map = HashMap::::new(); - map.entry(42).or_insert_with(String::new); + map.entry(42).or_insert(String::new()); let mut map_vec = HashMap::>::new(); - map_vec.entry(42).or_insert_with(Vec::new); + map_vec.entry(42).or_insert(vec![]); let mut btree = BTreeMap::::new(); - btree.entry(42).or_insert_with(String::new); + btree.entry(42).or_insert(String::new()); let mut btree_vec = BTreeMap::>::new(); - btree_vec.entry(42).or_insert_with(Vec::new); + btree_vec.entry(42).or_insert(vec![]); let stringy = Some(String::from("")); let _ = stringy.unwrap_or_else(|| "".to_owned()); @@ -129,7 +129,7 @@ fn test_or_with_ctors() { let b = "b".to_string(); let _ = Some(Bar("a".to_string(), Duration::from_secs(1))) - .or_else(|| Some(Bar(b, Duration::from_secs(2)))); + .or(Some(Bar(b, Duration::from_secs(2)))); let vec = vec!["foo"]; let _ = opt.ok_or(vec.len()); @@ -155,16 +155,24 @@ fn f() -> Option<()> { } mod issue6675 { + unsafe fn ptr_to_ref<'a, T>(p: *const T) -> &'a T { + #[allow(unused)] + let x = vec![0; 1000]; // future-proofing, make this function expensive. + &*p + } + unsafe fn foo() { - let mut s = "test".to_owned(); - None.unwrap_or_else(|| s.as_mut_vec()); + let s = "test".to_owned(); + let s = &s as *const _; + None.unwrap_or_else(|| ptr_to_ref(s)); } fn bar() { - let mut s = "test".to_owned(); - None.unwrap_or_else(|| unsafe { s.as_mut_vec() }); + let s = "test".to_owned(); + let s = &s as *const _; + None.unwrap_or_else(|| unsafe { ptr_to_ref(s) }); #[rustfmt::skip] - None.unwrap_or_else(|| unsafe { s.as_mut_vec() }); + None.unwrap_or_else(|| unsafe { ptr_to_ref(s) }); } } diff --git a/tests/ui/or_fun_call.rs b/tests/ui/or_fun_call.rs index afaf92961b0..8eadc6ce3b4 100644 --- a/tests/ui/or_fun_call.rs +++ b/tests/ui/or_fun_call.rs @@ -155,16 +155,24 @@ fn f() -> Option<()> { } mod issue6675 { + unsafe fn ptr_to_ref<'a, T>(p: *const T) -> &'a T { + #[allow(unused)] + let x = vec![0; 1000]; // future-proofing, make this function expensive. + &*p + } + unsafe fn foo() { - let mut s = "test".to_owned(); - None.unwrap_or(s.as_mut_vec()); + let s = "test".to_owned(); + let s = &s as *const _; + None.unwrap_or(ptr_to_ref(s)); } fn bar() { - let mut s = "test".to_owned(); - None.unwrap_or(unsafe { s.as_mut_vec() }); + let s = "test".to_owned(); + let s = &s as *const _; + None.unwrap_or(unsafe { ptr_to_ref(s) }); #[rustfmt::skip] - None.unwrap_or( unsafe { s.as_mut_vec() } ); + None.unwrap_or( unsafe { ptr_to_ref(s) } ); } } diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index b2bcbd38c2d..9d0c42b10c2 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -1,16 +1,10 @@ -error: use of `unwrap_or` followed by a function call - --> $DIR/or_fun_call.rs:46:19 - | -LL | with_const_fn.unwrap_or(Duration::from_secs(5)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| Duration::from_secs(5))` - | - = note: `-D clippy::or-fun-call` implied by `-D warnings` - error: use of `unwrap_or` followed by a function call --> $DIR/or_fun_call.rs:49:22 | LL | with_constructor.unwrap_or(make()); | ^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(make)` + | + = note: `-D clippy::or-fun-call` implied by `-D warnings` error: use of `unwrap_or` followed by a call to `new` --> $DIR/or_fun_call.rs:52:5 @@ -72,30 +66,6 @@ error: use of `unwrap_or` followed by a function call LL | without_default.unwrap_or(Foo::new()); | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(Foo::new)` -error: use of `or_insert` followed by a function call - --> $DIR/or_fun_call.rs:82:19 - | -LL | map.entry(42).or_insert(String::new()); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(String::new)` - -error: use of `or_insert` followed by a function call - --> $DIR/or_fun_call.rs:85:23 - | -LL | map_vec.entry(42).or_insert(vec![]); - | ^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(Vec::new)` - -error: use of `or_insert` followed by a function call - --> $DIR/or_fun_call.rs:88:21 - | -LL | btree.entry(42).or_insert(String::new()); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(String::new)` - -error: use of `or_insert` followed by a function call - --> $DIR/or_fun_call.rs:91:25 - | -LL | btree_vec.entry(42).or_insert(vec![]); - | ^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(Vec::new)` - error: use of `unwrap_or` followed by a function call --> $DIR/or_fun_call.rs:94:21 | @@ -120,29 +90,23 @@ error: use of `or` followed by a function call LL | let _ = Some("a".to_string()).or(Some("b".to_string())); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_else(|| Some("b".to_string()))` -error: use of `or` followed by a function call - --> $DIR/or_fun_call.rs:132:10 - | -LL | .or(Some(Bar(b, Duration::from_secs(2)))); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_else(|| Some(Bar(b, Duration::from_secs(2))))` - -error: use of `unwrap_or` followed by a function call - --> $DIR/or_fun_call.rs:160:14 - | -LL | None.unwrap_or(s.as_mut_vec()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| s.as_mut_vec())` - -error: use of `unwrap_or` followed by a function call - --> $DIR/or_fun_call.rs:165:14 - | -LL | None.unwrap_or(unsafe { s.as_mut_vec() }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| unsafe { s.as_mut_vec() })` - error: use of `unwrap_or` followed by a function call --> $DIR/or_fun_call.rs:167:14 | -LL | None.unwrap_or( unsafe { s.as_mut_vec() } ); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| unsafe { s.as_mut_vec() })` +LL | None.unwrap_or(ptr_to_ref(s)); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| ptr_to_ref(s))` -error: aborting due to 24 previous errors +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:173:14 + | +LL | None.unwrap_or(unsafe { ptr_to_ref(s) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })` + +error: use of `unwrap_or` followed by a function call + --> $DIR/or_fun_call.rs:175:14 + | +LL | None.unwrap_or( unsafe { ptr_to_ref(s) } ); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })` + +error: aborting due to 18 previous errors diff --git a/tests/ui/redundant_pattern_matching_option.fixed b/tests/ui/redundant_pattern_matching_option.fixed index 813e268a60c..cc93859269c 100644 --- a/tests/ui/redundant_pattern_matching_option.fixed +++ b/tests/ui/redundant_pattern_matching_option.fixed @@ -80,3 +80,9 @@ const fn issue6067() { None::<()>.is_none(); } + +#[allow(clippy::deref_addrof, dead_code)] +fn issue7921() { + if (&None::<()>).is_none() {} + if (&None::<()>).is_none() {} +} diff --git a/tests/ui/redundant_pattern_matching_option.rs b/tests/ui/redundant_pattern_matching_option.rs index 82a98468943..280dca40c01 100644 --- a/tests/ui/redundant_pattern_matching_option.rs +++ b/tests/ui/redundant_pattern_matching_option.rs @@ -95,3 +95,9 @@ const fn issue6067() { None => true, }; } + +#[allow(clippy::deref_addrof, dead_code)] +fn issue7921() { + if let None = *(&None::<()>) {} + if let None = *&None::<()> {} +} diff --git a/tests/ui/redundant_pattern_matching_option.stderr b/tests/ui/redundant_pattern_matching_option.stderr index 3a58e5ad7be..27ff812ba45 100644 --- a/tests/ui/redundant_pattern_matching_option.stderr +++ b/tests/ui/redundant_pattern_matching_option.stderr @@ -130,5 +130,17 @@ LL | | None => true, LL | | }; | |_____^ help: try this: `None::<()>.is_none()` -error: aborting due to 19 previous errors +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:101:12 + | +LL | if let None = *(&None::<()>) {} + | -------^^^^----------------- help: try this: `if (&None::<()>).is_none()` + +error: redundant pattern matching, consider using `is_none()` + --> $DIR/redundant_pattern_matching_option.rs:102:12 + | +LL | if let None = *&None::<()> {} + | -------^^^^--------------- help: try this: `if (&None::<()>).is_none()` + +error: aborting due to 21 previous errors diff --git a/tests/ui/redundant_pattern_matching_result.fixed b/tests/ui/redundant_pattern_matching_result.fixed index d7af5d762ae..83c783385ef 100644 --- a/tests/ui/redundant_pattern_matching_result.fixed +++ b/tests/ui/redundant_pattern_matching_result.fixed @@ -70,8 +70,8 @@ fn issue5504() { } fn try_result_opt() -> Result { - while r#try!(result_opt()).is_some() {} - if r#try!(result_opt()).is_some() {} + while (r#try!(result_opt())).is_some() {} + if (r#try!(result_opt())).is_some() {} Ok(42) } diff --git a/tests/ui/redundant_pattern_matching_result.stderr b/tests/ui/redundant_pattern_matching_result.stderr index e06f095da20..d674d061e4d 100644 --- a/tests/ui/redundant_pattern_matching_result.stderr +++ b/tests/ui/redundant_pattern_matching_result.stderr @@ -88,13 +88,13 @@ error: redundant pattern matching, consider using `is_some()` --> $DIR/redundant_pattern_matching_result.rs:85:19 | LL | while let Some(_) = r#try!(result_opt()) {} - | ----------^^^^^^^----------------------- help: try this: `while r#try!(result_opt()).is_some()` + | ----------^^^^^^^----------------------- help: try this: `while (r#try!(result_opt())).is_some()` error: redundant pattern matching, consider using `is_some()` --> $DIR/redundant_pattern_matching_result.rs:86:16 | LL | if let Some(_) = r#try!(result_opt()) {} - | -------^^^^^^^----------------------- help: try this: `if r#try!(result_opt()).is_some()` + | -------^^^^^^^----------------------- help: try this: `if (r#try!(result_opt())).is_some()` error: redundant pattern matching, consider using `is_some()` --> $DIR/redundant_pattern_matching_result.rs:92:12 diff --git a/tests/ui/semicolon_if_nothing_returned.rs b/tests/ui/semicolon_if_nothing_returned.rs index 7a45f1b18d4..91916e7480f 100644 --- a/tests/ui/semicolon_if_nothing_returned.rs +++ b/tests/ui/semicolon_if_nothing_returned.rs @@ -1,6 +1,7 @@ #![warn(clippy::semicolon_if_nothing_returned)] #![allow(clippy::redundant_closure)] #![feature(label_break_value)] +#![feature(let_else)] fn get_unit() {} @@ -110,3 +111,12 @@ fn macro_with_semicolon() { } repro!(); } + +fn function_returning_option() -> Option { + Some(1) +} + +// No warning +fn let_else_stmts() { + let Some(x) = function_returning_option() else { return; }; +} diff --git a/tests/ui/semicolon_if_nothing_returned.stderr b/tests/ui/semicolon_if_nothing_returned.stderr index 78813e7cc1c..41d2c1cfb87 100644 --- a/tests/ui/semicolon_if_nothing_returned.stderr +++ b/tests/ui/semicolon_if_nothing_returned.stderr @@ -1,5 +1,5 @@ error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:9:5 + --> $DIR/semicolon_if_nothing_returned.rs:10:5 | LL | println!("Hello") | ^^^^^^^^^^^^^^^^^ help: add a `;` here: `println!("Hello");` @@ -7,25 +7,25 @@ LL | println!("Hello") = note: `-D clippy::semicolon-if-nothing-returned` implied by `-D warnings` error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:13:5 + --> $DIR/semicolon_if_nothing_returned.rs:14:5 | LL | get_unit() | ^^^^^^^^^^ help: add a `;` here: `get_unit();` error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:18:5 + --> $DIR/semicolon_if_nothing_returned.rs:19:5 | LL | y = x + 1 | ^^^^^^^^^ help: add a `;` here: `y = x + 1;` error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:24:9 + --> $DIR/semicolon_if_nothing_returned.rs:25:9 | LL | hello() | ^^^^^^^ help: add a `;` here: `hello();` error: consider adding a `;` to the last statement for consistent formatting - --> $DIR/semicolon_if_nothing_returned.rs:35:9 + --> $DIR/semicolon_if_nothing_returned.rs:36:9 | LL | ptr::drop_in_place(s.as_mut_ptr()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add a `;` here: `ptr::drop_in_place(s.as_mut_ptr());` diff --git a/tests/ui/suspicious_splitn.rs b/tests/ui/suspicious_splitn.rs index a21d94cf20b..528f2ddcc86 100644 --- a/tests/ui/suspicious_splitn.rs +++ b/tests/ui/suspicious_splitn.rs @@ -1,4 +1,5 @@ #![warn(clippy::suspicious_splitn)] +#![allow(clippy::needless_splitn)] fn main() { let _ = "a,b,c".splitn(3, ','); diff --git a/tests/ui/suspicious_splitn.stderr b/tests/ui/suspicious_splitn.stderr index b6220ae2393..3bcd681fa49 100644 --- a/tests/ui/suspicious_splitn.stderr +++ b/tests/ui/suspicious_splitn.stderr @@ -1,5 +1,5 @@ error: `splitn` called with `0` splits - --> $DIR/suspicious_splitn.rs:9:13 + --> $DIR/suspicious_splitn.rs:10:13 | LL | let _ = "a,b".splitn(0, ','); | ^^^^^^^^^^^^^^^^^^^^ @@ -8,7 +8,7 @@ LL | let _ = "a,b".splitn(0, ','); = note: the resulting iterator will always return `None` error: `rsplitn` called with `0` splits - --> $DIR/suspicious_splitn.rs:10:13 + --> $DIR/suspicious_splitn.rs:11:13 | LL | let _ = "a,b".rsplitn(0, ','); | ^^^^^^^^^^^^^^^^^^^^^ @@ -16,7 +16,7 @@ LL | let _ = "a,b".rsplitn(0, ','); = note: the resulting iterator will always return `None` error: `splitn` called with `1` split - --> $DIR/suspicious_splitn.rs:11:13 + --> $DIR/suspicious_splitn.rs:12:13 | LL | let _ = "a,b".splitn(1, ','); | ^^^^^^^^^^^^^^^^^^^^ @@ -24,7 +24,7 @@ LL | let _ = "a,b".splitn(1, ','); = note: the resulting iterator will always return the entire string followed by `None` error: `splitn` called with `0` splits - --> $DIR/suspicious_splitn.rs:12:13 + --> $DIR/suspicious_splitn.rs:13:13 | LL | let _ = [0, 1, 2].splitn(0, |&x| x == 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -32,7 +32,7 @@ LL | let _ = [0, 1, 2].splitn(0, |&x| x == 1); = note: the resulting iterator will always return `None` error: `splitn_mut` called with `0` splits - --> $DIR/suspicious_splitn.rs:13:13 + --> $DIR/suspicious_splitn.rs:14:13 | LL | let _ = [0, 1, 2].splitn_mut(0, |&x| x == 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,7 +40,7 @@ LL | let _ = [0, 1, 2].splitn_mut(0, |&x| x == 1); = note: the resulting iterator will always return `None` error: `splitn` called with `1` split - --> $DIR/suspicious_splitn.rs:14:13 + --> $DIR/suspicious_splitn.rs:15:13 | LL | let _ = [0, 1, 2].splitn(1, |&x| x == 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -48,7 +48,7 @@ LL | let _ = [0, 1, 2].splitn(1, |&x| x == 1); = note: the resulting iterator will always return the entire slice followed by `None` error: `rsplitn_mut` called with `1` split - --> $DIR/suspicious_splitn.rs:15:13 + --> $DIR/suspicious_splitn.rs:16:13 | LL | let _ = [0, 1, 2].rsplitn_mut(1, |&x| x == 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -56,7 +56,7 @@ LL | let _ = [0, 1, 2].rsplitn_mut(1, |&x| x == 1); = note: the resulting iterator will always return the entire slice followed by `None` error: `splitn` called with `1` split - --> $DIR/suspicious_splitn.rs:18:13 + --> $DIR/suspicious_splitn.rs:19:13 | LL | let _ = "a,b".splitn(X + 1, ','); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -64,7 +64,7 @@ LL | let _ = "a,b".splitn(X + 1, ','); = note: the resulting iterator will always return the entire string followed by `None` error: `splitn` called with `0` splits - --> $DIR/suspicious_splitn.rs:19:13 + --> $DIR/suspicious_splitn.rs:20:13 | LL | let _ = "a,b".splitn(X, ','); | ^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/temporary_assignment.rs b/tests/ui/temporary_assignment.rs index b4a931043b0..ac4c1bc6597 100644 --- a/tests/ui/temporary_assignment.rs +++ b/tests/ui/temporary_assignment.rs @@ -1,5 +1,4 @@ #![warn(clippy::temporary_assignment)] -#![allow(const_item_mutation)] use std::ops::{Deref, DerefMut}; diff --git a/tests/ui/temporary_assignment.stderr b/tests/ui/temporary_assignment.stderr index 4cc32c79f05..7d79901a28d 100644 --- a/tests/ui/temporary_assignment.stderr +++ b/tests/ui/temporary_assignment.stderr @@ -1,5 +1,5 @@ error: assignment to temporary - --> $DIR/temporary_assignment.rs:48:5 + --> $DIR/temporary_assignment.rs:47:5 | LL | Struct { field: 0 }.field = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -7,7 +7,7 @@ LL | Struct { field: 0 }.field = 1; = note: `-D clippy::temporary-assignment` implied by `-D warnings` error: assignment to temporary - --> $DIR/temporary_assignment.rs:49:5 + --> $DIR/temporary_assignment.rs:48:5 | LL | / MultiStruct { LL | | structure: Struct { field: 0 }, @@ -17,13 +17,13 @@ LL | | .field = 1; | |______________^ error: assignment to temporary - --> $DIR/temporary_assignment.rs:54:5 + --> $DIR/temporary_assignment.rs:53:5 | LL | ArrayStruct { array: [0] }.array[0] = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: assignment to temporary - --> $DIR/temporary_assignment.rs:55:5 + --> $DIR/temporary_assignment.rs:54:5 | LL | (0, 0).0 = 1; | ^^^^^^^^^^^^ diff --git a/tests/ui/undocumented_unsafe_blocks.rs b/tests/ui/undocumented_unsafe_blocks.rs index 52577323a58..7e510d89475 100644 --- a/tests/ui/undocumented_unsafe_blocks.rs +++ b/tests/ui/undocumented_unsafe_blocks.rs @@ -284,4 +284,8 @@ fn interference() { unsafe {}; } +pub fn print_binary_tree() { + println!("{}", unsafe { String::from_utf8_unchecked(vec![]) }); +} + fn main() {} diff --git a/tests/ui/undocumented_unsafe_blocks.stderr b/tests/ui/undocumented_unsafe_blocks.stderr index 613e9ffca45..ebe589001a1 100644 --- a/tests/ui/undocumented_unsafe_blocks.stderr +++ b/tests/ui/undocumented_unsafe_blocks.stderr @@ -155,5 +155,17 @@ LL ~ // Safety: ... LL ~ unsafe {}; | -error: aborting due to 13 previous errors +error: unsafe block missing a safety comment + --> $DIR/undocumented_unsafe_blocks.rs:288:20 + | +LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider adding a safety comment + | +LL ~ println!("{}", // Safety: ... +LL ~ unsafe { String::from_utf8_unchecked(vec![]) }); + | + +error: aborting due to 14 previous errors diff --git a/util/gh-pages/index.html b/util/gh-pages/index.html index 48421150a54..58e8b4f4829 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index.html @@ -118,6 +118,12 @@ Otherwise, have a great day =^.^= background-color: #777777; margin: auto 5px; } + + .label-version { + background-color: #777777; + margin: auto 5px; + font-family: monospace; + }