diff --git a/CHANGELOG.md b/CHANGELOG.md index a92fbdc767bd..8d770ad2a338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6304,6 +6304,7 @@ Released 2018-09-13 [`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names [`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names [`redundant_guards`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_guards +[`redundant_iter_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_iter_cloned [`redundant_locals`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_locals [`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern [`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching diff --git a/Cargo.toml b/Cargo.toml index 2b6139e7e7f3..e26644871263 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ path = "src/driver.rs" [dependencies] clippy_config = { path = "clippy_config" } clippy_lints = { path = "clippy_lints" } +clippy_lints_methods = { path = "clippy_lints_methods" } clippy_utils = { path = "clippy_utils" } declare_clippy_lint = { path = "declare_clippy_lint" } rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" } diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index 10c08dba50b9..4d5ceb8211f7 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -9,6 +9,7 @@ chrono = { version = "0.4.38", default-features = false, features = ["clock"] } clap = { version = "4.4", features = ["derive"] } indoc = "1.0" itertools = "0.12" +memchr = "2.7.5" opener = "0.7" walkdir = "2.3" diff --git a/clippy_dev/src/deprecate_lint.rs b/clippy_dev/src/deprecate_lint.rs index 3bdc5b277232..b845c033d94f 100644 --- a/clippy_dev/src/deprecate_lint.rs +++ b/clippy_dev/src/deprecate_lint.rs @@ -1,8 +1,9 @@ -use crate::update_lints::{DeprecatedLint, Lint, find_lint_decls, generate_lint_files, read_deprecated_lints}; -use crate::utils::{UpdateMode, Version}; +use crate::parse::{ActiveLint, DeprecatedLint, LintKind, ParsedData}; +use crate::update_lints::generate_lint_files; +use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists}; +use core::mem; use std::ffi::OsStr; -use std::path::{Path, PathBuf}; -use std::{fs, io}; +use std::path::Path; /// Runs the `deprecate` command /// @@ -18,68 +19,42 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) { panic!("`{name}` should not contain the `{prefix}` prefix"); } - let mut lints = find_lint_decls(); - let (mut deprecated_lints, renamed_lints) = read_deprecated_lints(); - - let Some(lint) = lints.iter().find(|l| l.name == name) else { + let mut data = ParsedData::collect(); + let Some(entry) = data.lints.get_mut(name) else { eprintln!("error: failed to find lint `{name}`"); return; }; - - let prefixed_name = String::from_iter(["clippy::", name]); - match deprecated_lints.binary_search_by(|x| x.name.cmp(&prefixed_name)) { - Ok(_) => { - println!("`{name}` is already deprecated"); - return; - }, - Err(idx) => deprecated_lints.insert( - idx, - DeprecatedLint { - name: prefixed_name, - reason: reason.into(), - version: clippy_version.rust_display().to_string(), - }, - ), - } - - let mod_path = { - let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module)); - if mod_path.is_dir() { - mod_path = mod_path.join("mod"); - } - - mod_path.set_extension("rs"); - mod_path + let LintKind::Active(lint) = mem::replace( + &mut entry.kind, + LintKind::Deprecated(DeprecatedLint { + reason: reason.into(), + version: clippy_version.rust_display().to_string(), + }), + ) else { + eprintln!("error: lint `{name}` is already deprecated"); + return; }; - if remove_lint_declaration(name, &mod_path, &mut lints).unwrap_or(false) { - generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); - println!("info: `{name}` has successfully been deprecated"); - println!("note: you must run `cargo uitest` to update the test results"); - } else { - eprintln!("error: lint not found"); - } + remove_lint_declaration(name, &lint, &data); + generate_lint_files(UpdateMode::Change, &data); + println!("info: `{name}` has successfully been deprecated"); + println!("note: you must run `cargo uitest` to update the test results"); } -fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io::Result { - fn remove_lint(name: &str, lints: &mut Vec) { - lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos)); - } - +fn remove_lint_declaration(name: &str, lint: &ActiveLint, data: &ParsedData) { fn remove_test_assets(name: &str) { let test_file_stem = format!("tests/ui/{name}"); let path = Path::new(&test_file_stem); // Some lints have their own directories, delete them if path.is_dir() { - let _ = fs::remove_dir_all(path); - return; + delete_dir_if_exists(path); + } else { + // Remove all related test files + delete_file_if_exists(&path.with_extension("rs")); + delete_file_if_exists(&path.with_extension("stderr")); + delete_file_if_exists(&path.with_extension("fixed")); } - - // Remove all related test files - let _ = fs::remove_file(path.with_extension("rs")); - let _ = fs::remove_file(path.with_extension("stderr")); - let _ = fs::remove_file(path.with_extension("fixed")); } fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) { @@ -108,54 +83,58 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec) -> io } } - if path.exists() - && let Some(lint) = lints.iter().find(|l| l.name == name) - { - if lint.module == name { - // The lint name is the same as the file, we can just delete the entire file - fs::remove_file(path)?; + let lint_file = &data.source_map.files[lint.decl_span.file]; + if data.lints.values().any(|l| { + let other_file = &data.source_map.files[l.name_span.file]; + other_file.krate == lint_file.krate && other_file.module.starts_with(&lint_file.module) + }) { + // Try to delete a sub-module that matches the lint's name + let removed_mod = if lint_file.path.file_name().map(OsStr::as_encoded_bytes) == Some(b"mod.rs") { + let mut path = lint_file.path.clone(); + path.set_file_name(name); + path.set_extension("rs"); + delete_file_if_exists(&path) } else { - // We can't delete the entire file, just remove the declaration - - if let Some(Some("mod.rs")) = path.file_name().map(OsStr::to_str) { - // Remove clippy_lints/src/some_mod/some_lint.rs - let mut lint_mod_path = path.to_path_buf(); - lint_mod_path.set_file_name(name); - lint_mod_path.set_extension("rs"); - - let _ = fs::remove_file(lint_mod_path); - } - - let mut content = - fs::read_to_string(path).unwrap_or_else(|_| panic!("failed to read `{}`", path.to_string_lossy())); - - eprintln!( - "warn: you will have to manually remove any code related to `{name}` from `{}`", - path.display() - ); - - assert!( - content[lint.declaration_range.clone()].contains(&name.to_uppercase()), - "error: `{}` does not contain lint `{}`'s declaration", - path.display(), - lint.name - ); - - // Remove lint declaration (declare_clippy_lint!) - content.replace_range(lint.declaration_range.clone(), ""); - - // Remove the module declaration (mod xyz;) - let mod_decl = format!("\nmod {name};"); - content = content.replacen(&mod_decl, "", 1); - - remove_impl_lint_pass(&lint.name.to_uppercase(), &mut content); - fs::write(path, content).unwrap_or_else(|_| panic!("failed to write to `{}`", path.to_string_lossy())); - } - - remove_test_assets(name); - remove_lint(name, lints); - return Ok(true); + false + }; + + FileUpdater::default().update_file(&lint_file.path, &mut |_, src, dst| { + let (a, b, c, d) = if removed_mod + && let mod_decl = format!("\nmod {name};") + && let Some(mod_start) = src.find(&mod_decl) + { + if mod_start < lint.decl_span.start as usize { + ( + mod_start, + mod_start + mod_decl.len(), + lint.decl_span.start as usize, + lint.decl_span.end as usize, + ) + } else { + ( + lint.decl_span.start as usize, + lint.decl_span.end as usize, + mod_start, + mod_start + mod_decl.len(), + ) + } + } else { + ( + lint.decl_span.start as usize, + lint.decl_span.end as usize, + src.len(), + src.len(), + ) + }; + dst.push_str(&src[..a]); + dst.push_str(&src[b..c]); + dst.push_str(&src[d..]); + remove_impl_lint_pass(&name.to_uppercase(), dst); + UpdateStatus::Changed + }); + } else { + // No other lint in the same module or a sub-module. + delete_file_if_exists(&lint_file.path); } - - Ok(false) + remove_test_assets(name); } diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index bd9e57c9f6da..016a57a12862 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -268,7 +268,7 @@ fn run_rustfmt(update_mode: UpdateMode) { .expect("invalid rustfmt path"); rustfmt_path.truncate(rustfmt_path.trim_end().len()); - let args: Vec<_> = walk_dir_no_dot_or_target() + let args: Vec<_> = walk_dir_no_dot_or_target(".") .filter_map(|e| { let e = expect_action(e, ErrAction::Read, "."); e.path() diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 3361443196ab..0d6ee9b92f1b 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,5 +1,6 @@ #![feature( rustc_private, + array_windows, exit_status_error, if_let_guard, let_chains, @@ -16,9 +17,11 @@ )] #![allow(clippy::missing_panics_doc)] -// The `rustc_driver` crate seems to be required in order to use the `rust_lexer` crate. -#[allow(unused_extern_crates)] +#[expect(unused_extern_crates, reason = "needed to lint to rustc crates")] extern crate rustc_driver; + +extern crate rustc_data_structures; +extern crate rustc_index; extern crate rustc_lexer; extern crate rustc_literal_escaper; @@ -34,3 +37,6 @@ pub mod setup; pub mod sync; pub mod update_lints; pub mod utils; + +mod parse; +mod source_map; diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 4121daa85e6d..b353520fb9fe 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -1,4 +1,5 @@ -use crate::utils::{RustSearcher, Token, Version}; +use crate::parse::{Capture, RustSearcher, Token}; +use crate::utils::Version; use clap::ValueEnum; use indoc::{formatdoc, writedoc}; use std::fmt::{self, Write as _}; @@ -522,7 +523,8 @@ fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) { let mut context = None; let mut decl_end = None; let mut searcher = RustSearcher::new(contents); - while let Some(name) = searcher.find_capture_token(CaptureIdent) { + let mut captures = [Capture::EMPTY]; + while let Some(name) = searcher.find_any_ident() { match name { "declare_clippy_lint" => { if searcher.match_tokens(&[Bang, OpenBrace], &mut []) && searcher.find_token(CloseBrace) { @@ -530,9 +532,8 @@ fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) { } }, "impl" => { - let mut capture = ""; - if searcher.match_tokens(&[Lt, Lifetime, Gt, CaptureIdent], &mut [&mut capture]) { - match capture { + if searcher.match_tokens(&[Lt, Lifetime, Gt, CaptureIdent], &mut captures) { + match searcher.get_capture(captures[0]) { "LateLintPass" => context = Some("LateContext"), "EarlyLintPass" => context = Some("EarlyContext"), _ => {}, diff --git a/clippy_dev/src/parse.rs b/clippy_dev/src/parse.rs new file mode 100644 index 000000000000..b4a949331040 --- /dev/null +++ b/clippy_dev/src/parse.rs @@ -0,0 +1,833 @@ +use crate::source_map::{SourceFile, SourceMap, Span}; +use crate::utils::{ErrAction, expect_action, walk_dir_no_dot_or_target}; +use core::ops::Range; +use core::slice; +use rustc_data_structures::fx::FxHashMap; +use rustc_index::{IndexVec, newtype_index}; +use rustc_lexer::{self as lexer, FrontmatterAllowed}; +use std::collections::hash_map::{Entry, VacantEntry}; +use std::panic::Location; +use std::path::{self, Path}; +use std::{fs, process}; + +#[derive(Clone, Copy)] +pub enum Token<'a> { + /// Matches any number of comments / doc comments. + AnyComment, + AnyIdent, + At, + Ident(&'a str), + CaptureIdent, + LitStr, + CaptureLitStr, + Bang, + CloseBrace, + CloseBracket, + CloseParen, + /// This will consume the first colon even if the second doesn't exist. + DoubleColon, + Comma, + Eq, + FatArrow, + Lifetime, + Literal, + Lt, + Gt, + OpenBrace, + OpenBracket, + OpenParen, + OptLifetimeArgs, + Pound, + Semi, +} + +#[derive(Clone, Copy)] +pub struct Capture { + pub pos: u32, + pub len: u32, +} +impl Capture { + pub const EMPTY: Self = Self { pos: 0, len: 0 }; + pub fn to_index(self) -> Range { + self.pos as usize..(self.pos + self.len) as usize + } + + pub fn to_span(self, file: SourceFile) -> Span { + Span { + file, + start: self.pos, + end: self.pos + self.len, + } + } +} + +pub struct RustSearcher<'txt> { + text: &'txt str, + cursor: lexer::Cursor<'txt>, + pos: u32, + next_token: lexer::Token, +} +impl<'txt> RustSearcher<'txt> { + #[must_use] + #[expect(clippy::inconsistent_struct_constructor)] + pub fn new(text: &'txt str) -> Self { + let mut cursor = lexer::Cursor::new(text, FrontmatterAllowed::Yes); + Self { + text, + pos: 0, + next_token: cursor.advance_token(), + cursor, + } + } + + #[must_use] + pub fn get_capture(&self, capture: Capture) -> &'txt str { + &self.text[capture.to_index()] + } + + #[must_use] + pub fn peek_text(&self) -> &'txt str { + &self.text[self.pos as usize..(self.pos + self.next_token.len) as usize] + } + + #[must_use] + pub fn peek_span(&self, file: SourceFile) -> Span { + Span { + file, + start: self.pos, + end: self.pos + self.next_token.len, + } + } + + #[track_caller] + fn get_unexpected_err(&self, file: SourceFile) -> Error { + ErrorKind::UnexpectedToken { + token: self.peek_text().into(), + span: self.peek_span(file), + } + .into() + } + + #[must_use] + pub fn peek_len(&self) -> u32 { + self.next_token.len + } + + #[must_use] + pub fn peek(&self) -> lexer::TokenKind { + self.next_token.kind + } + + #[must_use] + pub fn pos(&self) -> u32 { + self.pos + } + + #[must_use] + pub fn at_end(&self) -> bool { + self.next_token.kind == lexer::TokenKind::Eof + } + + /// Steps to the next token, or `TokenKind::Eof` if there are no more tokens. + pub fn step(&mut self) { + // `next_token.len` is zero for the eof marker. + self.pos += self.next_token.len; + self.next_token = self.cursor.advance_token(); + } + + /// Consumes the next token if it matches the requested value and captures the value if + /// requested. Returns `true` if a token was matched. + #[expect(clippy::too_many_lines)] + fn read_token(&mut self, token: Token<'_>, captures: &mut slice::IterMut<'_, Capture>) -> bool { + loop { + match (token, self.next_token.kind) { + (_, lexer::TokenKind::Whitespace) + | ( + Token::AnyComment, + lexer::TokenKind::BlockComment { terminated: true, .. } | lexer::TokenKind::LineComment { .. }, + ) => self.step(), + (Token::AnyComment, _) => return true, + (Token::At, lexer::TokenKind::At) + | (Token::AnyIdent, lexer::TokenKind::Ident) + | (Token::Bang, lexer::TokenKind::Bang) + | (Token::CloseBrace, lexer::TokenKind::CloseBrace) + | (Token::CloseBracket, lexer::TokenKind::CloseBracket) + | (Token::CloseParen, lexer::TokenKind::CloseParen) + | (Token::Comma, lexer::TokenKind::Comma) + | (Token::Eq, lexer::TokenKind::Eq) + | (Token::Lifetime, lexer::TokenKind::Lifetime { .. }) + | (Token::Lt, lexer::TokenKind::Lt) + | (Token::Gt, lexer::TokenKind::Gt) + | (Token::OpenBrace, lexer::TokenKind::OpenBrace) + | (Token::OpenBracket, lexer::TokenKind::OpenBracket) + | (Token::OpenParen, lexer::TokenKind::OpenParen) + | (Token::Pound, lexer::TokenKind::Pound) + | (Token::Semi, lexer::TokenKind::Semi) + | ( + Token::LitStr, + lexer::TokenKind::Literal { + kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. }, + .. + }, + ) + | ( + Token::Literal, + lexer::TokenKind::Literal { + kind: + lexer::LiteralKind::Int { .. } + | lexer::LiteralKind::Float { .. } + | lexer::LiteralKind::Byte { terminated: true } + | lexer::LiteralKind::ByteStr { terminated: true } + | lexer::LiteralKind::Char { terminated: true } + | lexer::LiteralKind::CStr { terminated: true } + | lexer::LiteralKind::Str { terminated: true } + | lexer::LiteralKind::RawByteStr { .. } + | lexer::LiteralKind::RawCStr { .. } + | lexer::LiteralKind::RawStr { .. }, + .. + }, + ) => { + self.step(); + return true; + }, + (Token::Literal, lexer::TokenKind::Ident) if matches!(self.peek_text(), "true" | "false") => { + self.step(); + return true; + }, + (Token::Ident(x), lexer::TokenKind::Ident) if x == self.peek_text() => { + self.step(); + return true; + }, + (Token::DoubleColon, lexer::TokenKind::Colon) => { + self.step(); + if matches!(self.next_token.kind, lexer::TokenKind::Colon) { + self.step(); + return true; + } + return false; + }, + (Token::FatArrow, lexer::TokenKind::Eq) => { + self.step(); + if matches!(self.next_token.kind, lexer::TokenKind::Gt) { + self.step(); + return true; + } + return false; + }, + (Token::OptLifetimeArgs, lexer::TokenKind::Lt) => { + self.step(); + while self.read_token(Token::Lifetime, captures) { + if !self.read_token(Token::Comma, captures) { + break; + } + } + return self.read_token(Token::Gt, captures); + }, + #[expect(clippy::match_same_arms)] + (Token::OptLifetimeArgs, _) => return true, + #[rustfmt::skip] + ( + Token::CaptureLitStr, + lexer::TokenKind::Literal { + kind: + lexer::LiteralKind::Str { terminated: true } + | lexer::LiteralKind::RawStr { n_hashes: Some(_) }, + .. + }, + ) + | (Token::CaptureIdent, lexer::TokenKind::Ident) => { + *captures.next().unwrap() = Capture { pos: self.pos, len: self.next_token.len }; + self.step(); + return true; + }, + _ => return false, + } + } + } + + #[must_use] + pub fn find_token(&mut self, token: Token<'_>) -> bool { + let mut captures = [].iter_mut(); + while !self.read_token(token, &mut captures) { + self.step(); + if self.at_end() { + return false; + } + } + true + } + + #[must_use] + pub fn find_any_ident(&mut self) -> Option<&'txt str> { + loop { + match self.next_token.kind { + lexer::TokenKind::Ident => { + let res = self.peek_text(); + self.step(); + return Some(res); + }, + lexer::TokenKind::Eof => return None, + _ => self.step(), + } + } + } + + #[must_use] + pub fn find_ident(&mut self, s: &str) -> bool { + while let Some(x) = self.find_any_ident() { + if x == s { + return true; + } + } + false + } + + #[must_use] + pub fn match_token(&mut self, token: Token<'_>) -> bool { + let mut captures = [].iter_mut(); + self.read_token(token, &mut captures) + } + + #[must_use] + pub fn match_tokens(&mut self, tokens: &[Token<'_>], captures: &mut [Capture]) -> bool { + let mut captures = captures.iter_mut(); + tokens.iter().all(|&t| self.read_token(t, &mut captures)) + } +} + +pub struct ActiveLint { + pub group: String, + pub decl_span: Span, +} + +pub struct DeprecatedLint { + pub reason: String, + pub version: String, +} + +pub struct RenamedLint { + pub new_name: String, + pub version: String, +} + +pub enum LintKind { + Active(ActiveLint), + Deprecated(DeprecatedLint), + Renamed(RenamedLint), +} + +pub struct Lint { + pub kind: LintKind, + pub name_span: Span, +} + +pub struct LintPassData { + pub name: String, + /// Span of the `impl_lint_pass` or `declare_lint_pass` macro call. + pub mac_span: Span, +} + +newtype_index! { + #[orderable] + pub struct LintPass {} +} + +pub struct LintRegistration { + pub name: String, + pub pass: LintPass, +} + +pub struct ParsedData { + pub source_map: SourceMap, + pub lints: FxHashMap, + pub lint_passes: IndexVec, + pub lint_registrations: Vec, + pub deprecated_span: Range, + pub renamed_span: Range, +} +impl ParsedData { + #[expect(clippy::default_trait_access)] + pub fn collect() -> Self { + // 2025-05: Initial capacities should fit everything without reallocating. + let mut parser = Parser { + source_map: SourceMap::with_capacity(8, 1000), + lints: FxHashMap::with_capacity_and_hasher(1000, Default::default()), + lint_passes: IndexVec::with_capacity(400), + lint_registrations: Vec::with_capacity(1000), + deprecated_span: 0..0, + renamed_span: 0..0, + errors: Vec::new(), + }; + parser.parse_src_files(); + parser.parse_deprecated_lints(); + + if !parser.errors.is_empty() { + for error in &parser.errors { + match &error.kind { + ErrorKind::DuplicateLint { name, span, prev_span } => { + eprint!( + "error: duplicate lint `{name}` found\n at: {}\n previous: {}\n", + span.display(&parser.source_map), + prev_span.display(&parser.source_map), + ); + }, + ErrorKind::NotLintName(span) => { + eprint!( + "error: invalid lint name found\n at: {}\n", + span.display(&parser.source_map), + ); + }, + ErrorKind::LintMissingPrefix(span) => { + eprint!( + "error: lint name missing `clippy::` prefix\n at: {}\n", + span.display(&parser.source_map), + ); + }, + ErrorKind::StrLit(span) => { + eprint!( + "error: invalid string literal\n at: {}\n", + span.display(&parser.source_map), + ); + }, + ErrorKind::StrLitEol(span) => { + eprint!( + "error: string literal contains a line ending\n at: {}\n", + span.display(&parser.source_map), + ); + }, + ErrorKind::UnexpectedToken { token, span } => { + eprint!( + "error: unexpected token `{token}`\n at: {}\n", + span.display(&parser.source_map), + ); + }, + } + eprintln!(" error-src: {}", error.loc); + } + process::exit(1); + } + + ParsedData { + source_map: parser.source_map, + lints: parser.lints, + lint_passes: parser.lint_passes, + lint_registrations: parser.lint_registrations, + deprecated_span: parser.deprecated_span, + renamed_span: parser.renamed_span, + } + } +} + +enum ErrorKind { + /// Multiple lint declarations with the same name. + DuplicateLint { + name: String, + span: Span, + prev_span: Span, + }, + /// A string literal is not a valid lint name. + NotLintName(Span), + /// A lint name is missing the `clippy::` prefix. + LintMissingPrefix(Span), + /// Error when parsing a string literal. + StrLit(Span), + // A string literal contains a line terminator. + StrLitEol(Span), + // A token not expected in the source was found. + UnexpectedToken { + token: String, + span: Span, + }, +} +struct Error { + kind: ErrorKind, + loc: &'static Location<'static>, +} +impl From for Error { + #[track_caller] + fn from(kind: ErrorKind) -> Self { + Self { + kind, + loc: Location::caller(), + } + } +} + +struct Parser { + source_map: SourceMap, + lints: FxHashMap, + lint_passes: IndexVec, + lint_registrations: Vec, + deprecated_span: Range, + renamed_span: Range, + errors: Vec, +} +impl Parser { + /// Parses all source files looking for lint declarations (`declare_clippy_lint! { .. }`). + fn parse_src_files(&mut self) { + for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") { + let e = expect_action(e, ErrAction::Read, "."); + if !expect_action(e.file_type(), ErrAction::Read, ".").is_dir() { + continue; + } + let Ok(mut crate_path) = e.file_name().into_string() else { + continue; + }; + if crate_path.starts_with("clippy_lints") && crate_path != "clippy_lints_internal" { + let krate = self.source_map.add_new_crate(&crate_path); + crate_path.push(path::MAIN_SEPARATOR); + crate_path.push_str("src"); + for e in walk_dir_no_dot_or_target(&crate_path) { + let e = expect_action(e, ErrAction::Read, &crate_path); + if let Some(path) = e.path().to_str() + && let Some(path) = path.strip_suffix(".rs") + && let Some(path) = path.get(crate_path.len() + 1..) + { + let module = if path == "lib" { + String::new() + } else { + let path = if let Some(path) = path.strip_suffix("mod") + && let Some(path) = path.strip_suffix(path::MAIN_SEPARATOR) + { + path + } else { + path + }; + path.replace(path::MAIN_SEPARATOR, "::") + }; + let file = self.source_map.load_new_file(e.path(), krate, module); + self.parse_src_file(file); + } + } + } + } + } + + /// Parse a source file looking for `declare_clippy_lint` macro invocations. + fn parse_src_file(&mut self, file: SourceFile) { + #[allow(clippy::enum_glob_use)] + use Token::*; + #[rustfmt::skip] + static LINT_DECL_TOKENS: &[Token<'_>] = &[ + // { /// docs + OpenBrace, AnyComment, + // #[clippy::version = "version"] + Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket, + // pub NAME, GROUP, "desc" + Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, AnyComment, LitStr, + ]; + #[rustfmt::skip] + static LINT_DECL_EXTRA_TOKENS: &[Token<'_>] = &[ + // , @option = value + Comma, AnyComment, At, AnyIdent, Eq, Literal, + ]; + #[rustfmt::skip] + static LINT_PASS_TOKENS: &[Token<'_>] = &[ + // ( name <'lt> => [ + OpenParen, AnyComment, CaptureIdent, OptLifetimeArgs, FatArrow, OpenBracket, + ]; + + let mut searcher = RustSearcher::new(&self.source_map.files[file].contents); + let mut captures = [Capture::EMPTY; 2]; + while let Some(ident) = searcher.find_any_ident() { + #[expect(clippy::cast_possible_truncation)] + let start = searcher.pos - ident.len() as u32; + if searcher.match_token(Bang) { + match ident { + "declare_clippy_lint" => { + if !searcher.match_tokens(LINT_DECL_TOKENS, &mut captures) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + while searcher.match_tokens(LINT_DECL_EXTRA_TOKENS, &mut []) { + // nothing + } + if !searcher.match_token(CloseBrace) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + let name_span = captures[0].to_span(file); + let name = searcher.get_capture(captures[0]).to_ascii_lowercase(); + if let Some(e) = get_vacant_lint(name, name_span, &mut self.lints, &mut self.errors) { + e.insert(Lint { + kind: LintKind::Active(ActiveLint { + group: searcher.get_capture(captures[1]).into(), + decl_span: Span { + file, + start, + end: searcher.pos(), + }, + }), + name_span, + }); + } + }, + "impl_lint_pass" | "declare_lint_pass" => { + if !searcher.match_tokens(LINT_PASS_TOKENS, &mut captures) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + let pass = self.lint_passes.next_index(); + let pass_name = captures[0]; + while searcher.match_tokens(&[AnyComment, CaptureIdent], &mut captures) { + // Read a path expression. + while searcher.match_token(DoubleColon) { + // Overwrite the previous capture. The last segment is the lint name we want. + if !searcher.match_tokens(&[CaptureIdent], &mut captures) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + } + self.lint_registrations.push(LintRegistration { + name: searcher.get_capture(captures[0]).to_ascii_lowercase(), + pass, + }); + if !searcher.match_token(Comma) { + break; + } + } + if !searcher.match_tokens(&[CloseBracket, CloseParen], &mut []) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + self.lint_passes.push(LintPassData { + name: searcher.get_capture(pass_name).to_owned(), + mac_span: Span { + file, + start, + end: searcher.pos(), + }, + }); + }, + _ => {}, + } + } + } + } + + pub fn parse_deprecated_lints(&mut self) { + #[allow(clippy::enum_glob_use)] + use Token::*; + #[rustfmt::skip] + static DECL_TOKENS: &[Token<'_>] = &[ + // #[clippy::version = "version"] + Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, CaptureLitStr, CloseBracket, + // ("first", "second"), + OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, Comma, + ]; + #[rustfmt::skip] + static DEPRECATED_TOKENS: &[Token<'_>] = &[ + // !{ DEPRECATED(DEPRECATED_VERSION) = [ + Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket, + ]; + #[rustfmt::skip] + static RENAMED_TOKENS: &[Token<'_>] = &[ + // !{ RENAMED(RENAMED_VERSION) = [ + Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket, + ]; + #[rustfmt::skip] + static END_TOKENS: &[Token<'_>] = &[ + // ]} + CloseBracket, CloseBrace, + ]; + + let krate = self.source_map.add_crate("clippy_lints"); + let file = self.source_map.load_file( + Path::new("clippy_lints/src/deprecated_lints.rs"), + krate, + "deprecated_lints", + ); + let file_data = &self.source_map.files[file]; + + let mut captures = [Capture::EMPTY; 3]; + let mut searcher = RustSearcher::new(&file_data.contents); + // First instance is the macro definition. + assert!( + searcher.find_ident("declare_with_version"), + "error parsing `clippy_lints/src/deprecated_lints.rs`", + ); + + if !searcher.find_ident("declare_with_version") || !searcher.match_tokens(DEPRECATED_TOKENS, &mut []) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + let start = searcher.pos(); + let mut end = start; + while searcher.match_tokens(DECL_TOKENS, &mut captures) { + end = searcher.pos(); + let name_span = captures[1].to_span(file); + let (Some(version), Some(name), Some(reason)) = ( + parse_str_single_line(captures[0], searcher.text, file, &mut self.errors), + parse_clippy_lint_name(captures[1], searcher.text, file, &mut self.errors), + parse_str_single_line(captures[2], searcher.text, file, &mut self.errors), + ) else { + continue; + }; + if let Some(e) = get_vacant_lint(name, name_span, &mut self.lints, &mut self.errors) { + e.insert(Lint { + kind: LintKind::Deprecated(DeprecatedLint { reason, version }), + name_span, + }); + } + } + self.deprecated_span = start..end; + if !searcher.match_tokens(END_TOKENS, &mut []) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + + if !searcher.find_ident("declare_with_version") || !searcher.match_tokens(RENAMED_TOKENS, &mut []) { + self.errors.push(searcher.get_unexpected_err(file)); + return; + } + let start = searcher.pos(); + let mut end = start; + while searcher.match_tokens(DECL_TOKENS, &mut captures) { + end = searcher.pos(); + let name_span = captures[1].to_span(file); + let (Some(version), Some(name), Some(new_name)) = ( + parse_str_single_line(captures[0], searcher.text, file, &mut self.errors), + parse_clippy_lint_name(captures[1], searcher.text, file, &mut self.errors), + parse_maybe_clippy_lint_name(captures[2], searcher.text, file, &mut self.errors), + ) else { + continue; + }; + if let Some(e) = get_vacant_lint(name, name_span, &mut self.lints, &mut self.errors) { + e.insert(Lint { + kind: LintKind::Renamed(RenamedLint { new_name, version }), + name_span, + }); + } + } + self.renamed_span = start..end; + if !searcher.match_tokens(END_TOKENS, &mut []) { + self.errors.push(searcher.get_unexpected_err(file)); + #[expect(clippy::needless_return)] + return; + } + } +} + +#[track_caller] +fn get_vacant_lint<'a>( + name: String, + name_span: Span, + lints: &'a mut FxHashMap, + errors: &mut Vec, +) -> Option> { + match lints.entry(name) { + Entry::Vacant(e) => Some(e), + Entry::Occupied(e) => { + errors.push( + ErrorKind::DuplicateLint { + name: e.key().clone(), + span: name_span, + prev_span: e.get().name_span, + } + .into(), + ); + None + }, + } +} + +#[track_caller] +fn parse_str_lit(capture: Capture, text: &str, file: SourceFile, errors: &mut Vec) -> Option { + let s = &text[capture.to_index()]; + let (s, is_raw) = if let Some(s) = s.strip_prefix("r") { + (s.trim_matches('#'), true) + } else { + (s, false) + }; + let Some(s) = s.strip_prefix('"').and_then(|s| s.strip_suffix('"')) else { + errors.push(ErrorKind::StrLit(capture.to_span(file)).into()); + return None; + }; + + let mut no_err = true; + let res = if is_raw { + #[expect(clippy::cast_possible_truncation)] + rustc_literal_escaper::check_raw_str(s, |sp, ch| { + if let Err(e) = ch + && e.is_fatal() + { + errors.push( + ErrorKind::StrLit(Span { + file, + start: capture.pos + sp.start as u32, + end: capture.pos + sp.end as u32, + }) + .into(), + ); + no_err = false; + } + }); + s.into() + } else { + let mut res = String::with_capacity(s.len()); + #[expect(clippy::cast_possible_truncation)] + rustc_literal_escaper::unescape_str(s, |sp, ch| match ch { + Ok(ch) => res.push(ch), + Err(e) if e.is_fatal() => { + errors.push( + ErrorKind::StrLit(Span { + file, + start: capture.pos + sp.start as u32, + end: capture.pos + sp.end as u32, + }) + .into(), + ); + no_err = false; + }, + _ => {}, + }); + res + }; + no_err.then_some(res) +} + +#[track_caller] +fn parse_str_single_line(capture: Capture, text: &str, file: SourceFile, errors: &mut Vec) -> Option { + let s = parse_str_lit(capture, text, file, errors)?; + if s.contains('\n') { + errors.push(ErrorKind::StrLitEol(capture.to_span(file)).into()); + None + } else { + Some(s) + } +} + +fn is_lint_name(s: &str) -> bool { + s.bytes() + .all(|c| matches!(c, b'_' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z')) +} + +#[track_caller] +fn parse_clippy_lint_name(capture: Capture, text: &str, file: SourceFile, errors: &mut Vec) -> Option { + let mut s = parse_str_lit(capture, text, file, errors)?; + let Some(name) = s.strip_prefix("clippy::") else { + errors.push(ErrorKind::LintMissingPrefix(capture.to_span(file)).into()); + return None; + }; + if !is_lint_name(name) { + errors.push(ErrorKind::NotLintName(capture.to_span(file)).into()); + return None; + } + s.drain(.."clippy::".len()); + Some(s) +} + +#[track_caller] +fn parse_maybe_clippy_lint_name( + capture: Capture, + text: &str, + file: SourceFile, + errors: &mut Vec, +) -> Option { + let s = parse_str_lit(capture, text, file, errors)?; + if !is_lint_name(s.strip_prefix("clippy::").unwrap_or(&s)) { + errors.push(ErrorKind::NotLintName(capture.to_span(file)).into()); + return None; + } + Some(s) +} diff --git a/clippy_dev/src/release.rs b/clippy_dev/src/release.rs index 15392dd1d292..c6d1564e1e46 100644 --- a/clippy_dev/src/release.rs +++ b/clippy_dev/src/release.rs @@ -4,6 +4,7 @@ use std::fmt::Write; static CARGO_TOML_FILES: &[&str] = &[ "clippy_config/Cargo.toml", "clippy_lints/Cargo.toml", + "clippy_lints_methods/Cargo.toml", "clippy_utils/Cargo.toml", "declare_clippy_lint/Cargo.toml", "Cargo.toml", diff --git a/clippy_dev/src/rename_lint.rs b/clippy_dev/src/rename_lint.rs index d62597428e21..8875de431ca3 100644 --- a/clippy_dev/src/rename_lint.rs +++ b/clippy_dev/src/rename_lint.rs @@ -1,9 +1,13 @@ -use crate::update_lints::{RenamedLint, find_lint_decls, generate_lint_files, read_deprecated_lints}; +use crate::parse::{Capture, Lint, LintKind, ParsedData, RenamedLint, RustSearcher, Token}; +use crate::update_lints::generate_lint_files; use crate::utils::{ - ErrAction, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, - delete_file_if_exists, expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target, + ErrAction, FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists, + expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target, }; +use core::mem; +use rustc_data_structures::fx::FxHashMap; use rustc_lexer::TokenKind; +use std::collections::hash_map::Entry; use std::ffi::OsString; use std::fs; use std::path::Path; @@ -33,67 +37,57 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b } let mut updater = FileUpdater::default(); - let mut lints = find_lint_decls(); - let (deprecated_lints, mut renamed_lints) = read_deprecated_lints(); + let mut data = ParsedData::collect(); - let Ok(lint_idx) = lints.binary_search_by(|x| x.name.as_str().cmp(old_name)) else { - panic!("could not find lint `{old_name}`"); - }; - let lint = &lints[lint_idx]; - - let old_name_prefixed = String::from_iter(["clippy::", old_name]); + // Update any existing renames let new_name_prefixed = if uplift { new_name.to_owned() } else { String::from_iter(["clippy::", new_name]) }; - - for lint in &mut renamed_lints { - if lint.new_name == old_name_prefixed { + for lint in data.lints.values_mut() { + if let LintKind::Renamed(lint) = &mut lint.kind + && lint.new_name.strip_prefix("clippy::") == Some(old_name) + { lint.new_name.clone_from(&new_name_prefixed); } } - match renamed_lints.binary_search_by(|x| x.old_name.cmp(&old_name_prefixed)) { - Ok(_) => { - println!("`{old_name}` already has a rename registered"); - return; - }, - Err(idx) => { - renamed_lints.insert( - idx, - RenamedLint { - old_name: old_name_prefixed, - new_name: if uplift { - new_name.to_owned() - } else { - String::from_iter(["clippy::", new_name]) - }, - version: clippy_version.rust_display().to_string(), - }, - ); - }, - } - // Some tests are named `lint_name_suffix` which should also be renamed, - // but we can't do that if the renamed lint's name overlaps with another - // lint. e.g. renaming 'foo' to 'bar' when a lint 'foo_bar' also exists. - let change_prefixed_tests = lints.get(lint_idx + 1).is_none_or(|l| !l.name.starts_with(old_name)); + // Mark the lint as renamed + let Some(entry) = data.lints.get_mut(old_name) else { + eprintln!("error: failed to find lint `{old_name}`"); + return; + }; + let LintKind::Active(lint) = mem::replace( + &mut entry.kind, + LintKind::Renamed(RenamedLint { + new_name: new_name_prefixed, + version: clippy_version.rust_display().to_string(), + }), + ) else { + eprintln!("error: lint `{old_name}` is already deprecated"); + return; + }; + let lint_name_span = entry.name_span; let mut mod_edit = ModEdit::None; if uplift { - let is_unique_mod = lints[..lint_idx].iter().any(|l| l.module == lint.module) - || lints[lint_idx + 1..].iter().any(|l| l.module == lint.module); + let lint_file = &data.source_map.files[lint_name_span.file]; + let is_unique_mod = data + .lints + .values() + .any(|x| data.source_map.files[x.name_span.file].module == lint_file.module); if is_unique_mod { - if delete_file_if_exists(lint.path.as_ref()) { + if delete_file_if_exists(lint_file.path.as_ref()) { mod_edit = ModEdit::Delete; } } else { - updater.update_file(&lint.path, &mut |_, src, dst| -> UpdateStatus { - let mut start = &src[..lint.declaration_range.start]; + updater.update_file(&lint_file.path, &mut |_, src, dst| -> UpdateStatus { + let mut start = &src[..lint.decl_span.start as usize]; if start.ends_with("\n\n") { start = &start[..start.len() - 1]; } - let mut end = &src[lint.declaration_range.end..]; + let mut end = &src[lint.decl_span.end as usize..]; if end.starts_with("\n\n") { end = &end[1..]; } @@ -102,29 +96,31 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b UpdateStatus::Changed }); } - delete_test_files(old_name, change_prefixed_tests); - lints.remove(lint_idx); - } else if lints.binary_search_by(|x| x.name.as_str().cmp(new_name)).is_err() { - let lint = &mut lints[lint_idx]; - if lint.module.ends_with(old_name) - && lint + delete_test_files(old_name, &data.lints); + } else if let Entry::Vacant(entry) = data.lints.entry(new_name.to_owned()) { + let lint_file = &mut data.source_map.files[lint.decl_span.file]; + if lint_file.module.ends_with(old_name) + && lint_file .path .file_stem() .is_some_and(|x| x.as_encoded_bytes() == old_name.as_bytes()) { - let mut new_path = lint.path.with_file_name(new_name).into_os_string(); + let mut new_path = lint_file.path.with_file_name(new_name).into_os_string(); new_path.push(".rs"); - if try_rename_file(lint.path.as_ref(), new_path.as_ref()) { + if try_rename_file(lint_file.path.as_ref(), new_path.as_ref()) { + lint_file.path = new_path.into(); mod_edit = ModEdit::Rename; - } - let mod_len = lint.module.len(); - lint.module.truncate(mod_len - old_name.len()); - lint.module.push_str(new_name); + let mod_len = lint_file.module.len(); + lint_file.module.truncate(mod_len - old_name.len()); + lint_file.module.push_str(new_name); + } } - rename_test_files(old_name, new_name, change_prefixed_tests); - new_name.clone_into(&mut lints[lint_idx].name); - lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name)); + entry.insert(Lint { + kind: LintKind::Active(lint), + name_span: lint_name_span, + }); + rename_test_files(old_name, new_name, &data.lints); } else { println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`"); println!("Since `{new_name}` already exists the existing code has not been changed"); @@ -132,13 +128,13 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b } let mut update_fn = file_update_fn(old_name, new_name, mod_edit); - for e in walk_dir_no_dot_or_target() { + for e in walk_dir_no_dot_or_target(".") { let e = expect_action(e, ErrAction::Read, "."); if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") { updater.update_file(e.path(), &mut update_fn); } } - generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); + generate_lint_files(UpdateMode::Change, &data); if uplift { println!("Uplifted `clippy::{old_name}` as `{new_name}`"); @@ -163,47 +159,67 @@ enum ModEdit { Rename, } -fn collect_ui_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) { +fn is_lint_test(lint: &str, name: &str, lints: &FxHashMap) -> bool { + let is_num = |c: char| c.is_ascii_digit(); + let Some(suffix) = name.strip_prefix(lint) else { + return false; + }; + let suffix = suffix.trim_start_matches(is_num); + let Some(suffix) = suffix.strip_prefix('_') else { + return true; + }; + + // Some lint names are a prefix of other lint names. Make sure we don't mix test files + // between the two lints. + !(lints.contains_key(name.trim_end_matches(is_num)) + || suffix + .bytes() + .zip(suffix.len() - name.len()..) + .any(|(c, i)| c == b'_' && lints.contains_key(name[..i].trim_end_matches(is_num)))) +} + +fn collect_ui_test_names(lint: &str, lints: &FxHashMap, dst: &mut Vec<(String, bool)>) { for e in fs::read_dir("tests/ui").expect("error reading `tests/ui`") { let e = e.expect("error reading `tests/ui`"); - let name = e.file_name(); - if let Some((name_only, _)) = name.as_encoded_bytes().split_once(|&x| x == b'.') { - if name_only.starts_with(lint.as_bytes()) && (rename_prefixed || name_only.len() == lint.len()) { + if let Ok(name) = e.file_name().into_string() { + if e.file_type().is_ok_and(|ty| ty.is_dir()) { + if is_lint_test(lint, &name, lints) { + dst.push((name, false)); + } + } else if let Some((name_only, _)) = name.split_once('.') + && is_lint_test(lint, name_only, lints) + { dst.push((name, true)); } - } else if name.as_encoded_bytes().starts_with(lint.as_bytes()) && (rename_prefixed || name.len() == lint.len()) - { - dst.push((name, false)); } } } -fn collect_ui_toml_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) { - if rename_prefixed { - for e in fs::read_dir("tests/ui-toml").expect("error reading `tests/ui-toml`") { - let e = e.expect("error reading `tests/ui-toml`"); - let name = e.file_name(); - if name.as_encoded_bytes().starts_with(lint.as_bytes()) && e.file_type().is_ok_and(|ty| ty.is_dir()) { - dst.push((name, false)); - } +fn collect_ui_toml_test_names(lint: &str, lints: &FxHashMap, dst: &mut Vec<(String, bool)>) { + for e in fs::read_dir("tests/ui-toml").expect("error reading `tests/ui-toml`") { + let e = e.expect("error reading `tests/ui-toml`"); + if e.file_type().is_ok_and(|ty| ty.is_dir()) + && let Ok(name) = e.file_name().into_string() + && is_lint_test(lint, &name, lints) + { + dst.push((name, false)); } - } else { - dst.push((lint.into(), false)); } } /// Renames all test files for the given lint. /// /// If `rename_prefixed` is `true` this will also rename tests which have the lint name as a prefix. -fn rename_test_files(old_name: &str, new_name: &str, rename_prefixed: bool) { +fn rename_test_files(old_name: &str, new_name: &str, lints: &FxHashMap) { let mut tests = Vec::new(); let mut old_buf = OsString::from("tests/ui/"); let mut new_buf = OsString::from("tests/ui/"); - collect_ui_test_names(old_name, rename_prefixed, &mut tests); + collect_ui_test_names(old_name, lints, &mut tests); for &(ref name, is_file) in &tests { old_buf.push(name); - new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]); + new_buf.push(new_name); + new_buf.push(&name[old_name.len()..]); if is_file { try_rename_file(old_buf.as_ref(), new_buf.as_ref()); } else { @@ -218,21 +234,22 @@ fn rename_test_files(old_name: &str, new_name: &str, rename_prefixed: bool) { new_buf.truncate("tests/ui".len()); old_buf.push("-toml/"); new_buf.push("-toml/"); - collect_ui_toml_test_names(old_name, rename_prefixed, &mut tests); + collect_ui_toml_test_names(old_name, lints, &mut tests); for (name, _) in &tests { old_buf.push(name); - new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]); + new_buf.push(new_name); + new_buf.push(&name[old_name.len()..]); try_rename_dir(old_buf.as_ref(), new_buf.as_ref()); - old_buf.truncate("tests/ui/".len()); - new_buf.truncate("tests/ui/".len()); + old_buf.truncate("tests/ui-toml/".len()); + new_buf.truncate("tests/ui-toml/".len()); } } -fn delete_test_files(lint: &str, rename_prefixed: bool) { +fn delete_test_files(lint: &str, lints: &FxHashMap) { let mut tests = Vec::new(); let mut buf = OsString::from("tests/ui/"); - collect_ui_test_names(lint, rename_prefixed, &mut tests); + collect_ui_test_names(lint, lints, &mut tests); for &(ref name, is_file) in &tests { buf.push(name); if is_file { @@ -247,11 +264,11 @@ fn delete_test_files(lint: &str, rename_prefixed: bool) { buf.push("-toml/"); tests.clear(); - collect_ui_toml_test_names(lint, rename_prefixed, &mut tests); + collect_ui_toml_test_names(lint, lints, &mut tests); for (name, _) in &tests { buf.push(name); delete_dir_if_exists(buf.as_ref()); - buf.truncate("tests/ui/".len()); + buf.truncate("tests/ui-toml/".len()); } } @@ -286,7 +303,7 @@ fn file_update_fn<'a, 'b>( let mut copy_pos = 0u32; let mut changed = false; let mut searcher = RustSearcher::new(src); - let mut capture = ""; + let mut captures = [Capture::EMPTY]; loop { match searcher.peek() { TokenKind::Eof => break, @@ -295,12 +312,12 @@ fn file_update_fn<'a, 'b>( let text = searcher.peek_text(); searcher.step(); match text { - // clippy::line_name or clippy::lint-name + // clippy::lint_name "clippy" => { - if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture]) - && capture == old_name + if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut captures) + && searcher.get_capture(captures[0]) == old_name { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]); + dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); dst.push_str(new_name); copy_pos = searcher.pos(); changed = true; @@ -309,12 +326,12 @@ fn file_update_fn<'a, 'b>( // mod lint_name "mod" => { if !matches!(mod_edit, ModEdit::None) - && searcher.match_tokens(&[Token::CaptureIdent], &mut [&mut capture]) - && capture == old_name + && searcher.match_tokens(&[Token::CaptureIdent], &mut captures) + && searcher.get_capture(captures[0]) == old_name { match mod_edit { ModEdit::Rename => { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]); + dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); dst.push_str(new_name); copy_pos = searcher.pos(); changed = true; @@ -377,10 +394,10 @@ fn file_update_fn<'a, 'b>( }, // ::lint_name TokenKind::Colon - if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture]) - && capture == old_name => + if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut captures) + && searcher.get_capture(captures[0]) == old_name => { - dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]); + dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); dst.push_str(new_name); copy_pos = searcher.pos(); changed = true; diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index 498ffeba9d67..933a035c7d00 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -22,6 +22,7 @@ pub fn run(port: u16, lint: Option) -> ! { let index_time = mtime("util/gh-pages/index.html"); let times = [ "clippy_lints/src", + "clippy_lints_methods/src", "util/gh-pages/index_template.html", "tests/compile-test.rs", ] diff --git a/clippy_dev/src/setup/intellij.rs b/clippy_dev/src/setup/intellij.rs index c56811ee0a01..c5254105289e 100644 --- a/clippy_dev/src/setup/intellij.rs +++ b/clippy_dev/src/setup/intellij.rs @@ -14,6 +14,11 @@ const DEPENDENCIES_SECTION: &str = "[dependencies]"; const CLIPPY_PROJECTS: &[ClippyProjectInfo] = &[ ClippyProjectInfo::new("root", "Cargo.toml", "src/driver.rs"), ClippyProjectInfo::new("clippy_lints", "clippy_lints/Cargo.toml", "clippy_lints/src/lib.rs"), + ClippyProjectInfo::new( + "clippy_lints_methods", + "clippy_lints_methods/Cargo.toml", + "clippy_lints_methods/src/lib.rs", + ), ClippyProjectInfo::new("clippy_utils", "clippy_utils/Cargo.toml", "clippy_utils/src/lib.rs"), ]; diff --git a/clippy_dev/src/source_map.rs b/clippy_dev/src/source_map.rs new file mode 100644 index 000000000000..555014c77fc3 --- /dev/null +++ b/clippy_dev/src/source_map.rs @@ -0,0 +1,113 @@ +use crate::utils::File; +use core::fmt::{self, Display}; +use memchr::memchr_iter; +use rustc_index::{IndexVec, newtype_index}; +use std::path::{Path, PathBuf}; + +pub struct SourceData { + pub contents: String, + pub line_starts: Vec, + pub path: PathBuf, + pub module: String, + pub krate: Crate, +} +impl SourceData { + pub fn line_col(&self, pos: u32) -> (u32, u32) { + #[expect(clippy::cast_possible_truncation)] + let (line, offset) = match self.line_starts.binary_search(&pos) { + Ok(i) => (i as u32 + 1, self.line_starts[i]), + Err(i) => (i as u32, self.line_starts[i - 1]), + }; + let mut col = 1; + let mut remain = pos - offset; + let mut chars = self.contents[offset as usize..].chars(); + #[expect(clippy::cast_possible_truncation)] + while remain != 0 + && let Some(c) = chars.next() + { + col += 1; + remain = remain.saturating_sub(c.len_utf8() as u32); + } + (line, col) + } +} + +pub struct SourceMap { + pub crates: IndexVec>, + pub files: IndexVec, +} +impl SourceMap { + pub fn with_capacity(crates: usize, files: usize) -> Self { + Self { + crates: IndexVec::with_capacity(crates), + files: IndexVec::with_capacity(files), + } + } + + pub fn add_new_crate(&mut self, name: &str) -> Crate { + let res = self.crates.next_index(); + self.crates.push(name.into()); + res + } + + pub fn add_crate(&mut self, name: &str) -> Crate { + match self.crates.iter().position(|x| **x == *name) { + Some(x) => Crate::from_usize(x), + None => self.add_new_crate(name), + } + } + + pub fn load_new_file(&mut self, path: &Path, krate: Crate, module: String) -> SourceFile { + let mut contents = String::new(); + File::open_read(path).read_append_to_string(&mut contents); + + let res = self.files.next_index(); + let mut line_starts = Vec::with_capacity(16); + line_starts.push(0); + #[expect(clippy::cast_possible_truncation)] + line_starts.extend(memchr_iter(b'\n', contents.as_bytes()).map(|x| x as u32 + 1)); + self.files.push(SourceData { + contents, + line_starts, + path: path.into(), + module, + krate, + }); + res + } + + pub fn load_file(&mut self, path: &Path, krate: Crate, module: &str) -> SourceFile { + match self.files.iter().position(|x| x.krate == krate && x.module == module) { + Some(x) => SourceFile::from_usize(x), + None => self.load_new_file(path, krate, module.into()), + } + } +} + +newtype_index! { + pub struct SourceFile {} +} +newtype_index! { + #[orderable] + pub struct Crate {} +} + +#[derive(Clone, Copy)] +pub struct Span { + pub file: SourceFile, + pub start: u32, + pub end: u32, +} +impl Span { + pub fn display(self, source_map: &SourceMap) -> impl use<'_> + Display { + struct X<'a>(Span, &'a SourceMap); + impl Display for X<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let file = &self.1.files[self.0.file]; + let (line, col) = file.line_col(self.0.start); + write!(f, "{}:{line}:{col}", self.1.files[self.0.file].path.display()) + } + } + X(self, source_map) + } +} diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 5f6e874ffe25..146230882a60 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -1,13 +1,9 @@ -use crate::utils::{ - ErrAction, File, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, expect_action, update_text_region_fn, -}; +use crate::parse::{LintKind, ParsedData}; +use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}; use itertools::Itertools; -use std::collections::HashSet; use std::fmt::Write; -use std::fs; -use std::ops::Range; -use std::path::{self, Path, PathBuf}; -use walkdir::{DirEntry, WalkDir}; +use std::path::Path; +use std::process; const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ // Use that command to update this file and do not edit by hand.\n\ @@ -25,91 +21,103 @@ const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.ht /// /// Panics if a file path could not read from or then written to pub fn update(update_mode: UpdateMode) { - let lints = find_lint_decls(); - let (deprecated, renamed) = read_deprecated_lints(); - generate_lint_files(update_mode, &lints, &deprecated, &renamed); + let mut data = ParsedData::collect(); + let mut is_missing = false; + data.lint_registrations.sort_by(|x, y| x.name.cmp(&y.name)); + for (name, lint) in &data.lints { + if matches!(lint.kind, LintKind::Active(_)) + && data.lint_registrations.binary_search_by(|x| x.name.cmp(name)).is_err() + { + is_missing = true; + eprint!( + "error: lint `{name}` is not registered in a lint pass\n declared here: {}\n", + lint.name_span.display(&data.source_map) + ); + } + } + if is_missing { + process::exit(1); + } + generate_lint_files(update_mode, &data); } #[expect(clippy::too_many_lines)] -pub fn generate_lint_files( - update_mode: UpdateMode, - lints: &[Lint], - deprecated: &[DeprecatedLint], - renamed: &[RenamedLint], -) { +pub fn generate_lint_files(update_mode: UpdateMode, data: &ParsedData) { let mut updater = FileUpdater::default(); + + let mut lints: Vec<_> = data.lints.iter().map(|(x, y)| (&**x, y)).collect(); + lints.sort_by_key(|&(x, _)| x); + updater.update_file_checked( "cargo dev update_lints", update_mode, - "README.md", - &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { - write!(dst, "{}", round_to_fifty(lints.len())).unwrap(); - }), + "CHANGELOG.md", + &mut update_text_region_fn( + "\n", + "", + |dst| { + for &(lint, _) in &lints { + writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); + } + }, + ), ); + + let mut active = Vec::with_capacity(data.lints.len()); + let mut deprecated = Vec::with_capacity(data.lints.len()); + let mut renamed = Vec::with_capacity(data.lints.len()); + for (name, lint) in lints { + match &lint.kind { + LintKind::Active(lint) => active.push((name, &data.source_map.files[lint.decl_span.file])), + LintKind::Deprecated(lint) => deprecated.push((name, lint)), + LintKind::Renamed(lint) => renamed.push((name, lint)), + } + } + active.sort_by(|(_, x), (_, y)| (x.krate, &*x.module).cmp(&(y.krate, &*y.module))); + + // Round the lint count down to avoid merge conflicts every time a new lint is added. + let lint_count = active.len() / 50 * 50; updater.update_file_checked( "cargo dev update_lints", update_mode, - "book/src/README.md", + "README.md", &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { - write!(dst, "{}", round_to_fifty(lints.len())).unwrap(); + write!(dst, "{lint_count}").unwrap(); }), ); updater.update_file_checked( "cargo dev update_lints", update_mode, - "CHANGELOG.md", - &mut update_text_region_fn( - "\n", - "", - |dst| { - for lint in lints - .iter() - .map(|l| &*l.name) - .chain(deprecated.iter().filter_map(|l| l.name.strip_prefix("clippy::"))) - .chain(renamed.iter().filter_map(|l| l.old_name.strip_prefix("clippy::"))) - .sorted() - { - writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); - } - }, - ), + "book/src/README.md", + &mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| { + write!(dst, "{lint_count}").unwrap(); + }), ); + updater.update_file_checked( "cargo dev update_lints", update_mode, "clippy_lints/src/deprecated_lints.rs", &mut |_, src, dst| { - let mut searcher = RustSearcher::new(src); - assert!( - searcher.find_token(Token::Ident("declare_with_version")) - && searcher.find_token(Token::Ident("declare_with_version")), - "error reading deprecated lints" - ); - dst.push_str(&src[..searcher.pos() as usize]); - dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n"); - for lint in deprecated { + dst.push_str(&src[..data.deprecated_span.start as usize]); + for &(name, lint) in &deprecated { write!( dst, - " #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n", - lint.version, lint.name, lint.reason, + "\n #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),", + lint.version, lint.reason, ) .unwrap(); } - dst.push_str( - "]}\n\n\ - #[rustfmt::skip]\n\ - declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\ - ", - ); - for lint in renamed { + dst.push_str(&src[data.deprecated_span.end as usize..data.renamed_span.start as usize]); + for &(name, lint) in &renamed { write!( dst, - " #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n", - lint.version, lint.old_name, lint.new_name, + "\n #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),", + lint.version, lint.new_name, ) .unwrap(); } - dst.push_str("]}\n"); + dst.push_str(&src[data.renamed_span.end as usize..]); UpdateStatus::from_changed(src != dst) }, ); @@ -119,8 +127,8 @@ pub fn generate_lint_files( "tests/ui/deprecated.rs", &mut |_, src, dst| { dst.push_str(GENERATED_FILE_COMMENT); - for lint in deprecated { - writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.name, lint.name).unwrap(); + for &(name, _) in &deprecated { + writeln!(dst, "#![warn(clippy::{name})] //~ ERROR: lint `clippy::{name}`").unwrap(); } dst.push_str("\nfn main() {}\n"); UpdateStatus::from_changed(src != dst) @@ -131,47 +139,34 @@ pub fn generate_lint_files( update_mode, "tests/ui/rename.rs", &mut move |_, src, dst| { - let mut seen_lints = HashSet::new(); dst.push_str(GENERATED_FILE_COMMENT); dst.push_str("#![allow(clippy::duplicated_attributes)]\n"); - for lint in renamed { - if seen_lints.insert(&lint.new_name) { - writeln!(dst, "#![allow({})]", lint.new_name).unwrap(); - } - } - seen_lints.clear(); - for lint in renamed { - if seen_lints.insert(&lint.old_name) { - writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap(); - } + for &(name, _) in &renamed { + writeln!(dst, "#![warn(clippy::{name})] //~ ERROR: lint `clippy::{name}`").unwrap(); } dst.push_str("\nfn main() {}\n"); UpdateStatus::from_changed(src != dst) }, ); - for (crate_name, lints) in lints.iter().into_group_map_by(|&l| { - let Some(path::Component::Normal(name)) = l.path.components().next() else { - // All paths should start with `{crate_name}/src` when parsed from `find_lint_decls` - panic!("internal error: can't read crate name from path `{}`", l.path.display()); - }; - name - }) { + + let mut active = &*active; + while let [(_, first_lint), ..] = active { + let (crate_lints, tail) = active.split_at(active.partition_point(|(_, x)| x.krate == first_lint.krate)); + let krate_name = &*data.source_map.crates[first_lint.krate]; + active = tail; + updater.update_file_checked( "cargo dev update_lints", update_mode, - Path::new(crate_name).join("src/lib.rs"), + Path::new(krate_name).join("src/lib.rs"), &mut update_text_region_fn( "// begin lints modules, do not remove this comment, it's used in `update_lints`\n", "// end lints modules, do not remove this comment, it's used in `update_lints`", |dst| { - for lint_mod in lints - .iter() - .filter(|l| !l.module.is_empty()) - .map(|l| l.module.split_once("::").map_or(&*l.module, |x| x.0)) - .sorted() - .dedup() - { - writeln!(dst, "mod {lint_mod};").unwrap(); + for (_, lint) in crate_lints.iter().dedup_by(|(_, x), (_, y)| x.module == y.module) { + if !lint.module.is_empty() { + writeln!(dst, "mod {};", lint.module).unwrap(); + } } }, ), @@ -179,15 +174,19 @@ pub fn generate_lint_files( updater.update_file_checked( "cargo dev update_lints", update_mode, - Path::new(crate_name).join("src/declared_lints.rs"), + Path::new(krate_name).join("src/declared_lints.rs"), &mut |_, src, dst| { dst.push_str(GENERATED_FILE_COMMENT); dst.push_str("pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[\n"); - for (module_path, lint_name) in lints.iter().map(|l| (&l.module, l.name.to_uppercase())).sorted() { - if module_path.is_empty() { - writeln!(dst, " crate::{lint_name}_INFO,").unwrap(); + let mut buf = String::new(); + for (name, lint) in crate_lints { + buf.clear(); + buf.push_str(name); + buf.make_ascii_uppercase(); + if lint.module.is_empty() { + writeln!(dst, " crate::{buf}_INFO,").unwrap(); } else { - writeln!(dst, " crate::{module_path}::{lint_name}_INFO,").unwrap(); + writeln!(dst, " crate::{}::{buf}_INFO,", lint.module).unwrap(); } } dst.push_str("];\n"); @@ -196,264 +195,3 @@ pub fn generate_lint_files( ); } } - -fn round_to_fifty(count: usize) -> usize { - count / 50 * 50 -} - -/// Lint data parsed from the Clippy source code. -#[derive(PartialEq, Eq, Debug)] -pub struct Lint { - pub name: String, - pub group: String, - pub module: String, - pub path: PathBuf, - pub declaration_range: Range, -} - -pub struct DeprecatedLint { - pub name: String, - pub reason: String, - pub version: String, -} - -pub struct RenamedLint { - pub old_name: String, - pub new_name: String, - pub version: String, -} - -/// Finds all lint declarations (`declare_clippy_lint!`) -#[must_use] -pub fn find_lint_decls() -> Vec { - let mut lints = Vec::with_capacity(1000); - let mut contents = String::new(); - for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") { - let e = expect_action(e, ErrAction::Read, "."); - if !expect_action(e.file_type(), ErrAction::Read, ".").is_dir() { - continue; - } - let Ok(mut name) = e.file_name().into_string() else { - continue; - }; - if name.starts_with("clippy_lints") && name != "clippy_lints_internal" { - name.push_str("/src"); - for (file, module) in read_src_with_module(name.as_ref()) { - parse_clippy_lint_decls( - file.path(), - File::open_read_to_cleared_string(file.path(), &mut contents), - &module, - &mut lints, - ); - } - } - } - lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name)); - lints -} - -/// Reads the source files from the given root directory -fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator { - WalkDir::new(src_root).into_iter().filter_map(move |e| { - let e = expect_action(e, ErrAction::Read, src_root); - let path = e.path().as_os_str().as_encoded_bytes(); - if let Some(path) = path.strip_suffix(b".rs") - && let Some(path) = path.get(src_root.as_os_str().len() + 1..) - { - if path == b"lib" { - Some((e, String::new())) - } else { - let path = if let Some(path) = path.strip_suffix(b"mod") - && let Some(path) = path.strip_suffix(b"/").or_else(|| path.strip_suffix(b"\\")) - { - path - } else { - path - }; - if let Ok(path) = str::from_utf8(path) { - let path = path.replace(['/', '\\'], "::"); - Some((e, path)) - } else { - None - } - } - } else { - None - } - }) -} - -/// Parse a source file looking for `declare_clippy_lint` macro invocations. -fn parse_clippy_lint_decls(path: &Path, contents: &str, module: &str, lints: &mut Vec) { - #[allow(clippy::enum_glob_use)] - use Token::*; - #[rustfmt::skip] - static DECL_TOKENS: &[Token<'_>] = &[ - // !{ /// docs - Bang, OpenBrace, AnyComment, - // #[clippy::version = "version"] - Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket, - // pub NAME, GROUP, - Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma, - ]; - - let mut searcher = RustSearcher::new(contents); - while searcher.find_token(Ident("declare_clippy_lint")) { - let start = searcher.pos() as usize - "declare_clippy_lint".len(); - let (mut name, mut group) = ("", ""); - if searcher.match_tokens(DECL_TOKENS, &mut [&mut name, &mut group]) && searcher.find_token(CloseBrace) { - lints.push(Lint { - name: name.to_lowercase(), - group: group.into(), - module: module.into(), - path: path.into(), - declaration_range: start..searcher.pos() as usize, - }); - } - } -} - -#[must_use] -pub fn read_deprecated_lints() -> (Vec, Vec) { - #[allow(clippy::enum_glob_use)] - use Token::*; - #[rustfmt::skip] - static DECL_TOKENS: &[Token<'_>] = &[ - // #[clippy::version = "version"] - Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, CaptureLitStr, CloseBracket, - // ("first", "second"), - OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, Comma, - ]; - #[rustfmt::skip] - static DEPRECATED_TOKENS: &[Token<'_>] = &[ - // !{ DEPRECATED(DEPRECATED_VERSION) = [ - Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket, - ]; - #[rustfmt::skip] - static RENAMED_TOKENS: &[Token<'_>] = &[ - // !{ RENAMED(RENAMED_VERSION) = [ - Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket, - ]; - - let path = "clippy_lints/src/deprecated_lints.rs"; - let mut deprecated = Vec::with_capacity(30); - let mut renamed = Vec::with_capacity(80); - let mut contents = String::new(); - File::open_read_to_cleared_string(path, &mut contents); - - let mut searcher = RustSearcher::new(&contents); - - // First instance is the macro definition. - assert!( - searcher.find_token(Ident("declare_with_version")), - "error reading deprecated lints" - ); - - if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(DEPRECATED_TOKENS, &mut []) { - let mut version = ""; - let mut name = ""; - let mut reason = ""; - while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut name, &mut reason]) { - deprecated.push(DeprecatedLint { - name: parse_str_single_line(path.as_ref(), name), - reason: parse_str_single_line(path.as_ref(), reason), - version: parse_str_single_line(path.as_ref(), version), - }); - } - } else { - panic!("error reading deprecated lints"); - } - - if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(RENAMED_TOKENS, &mut []) { - let mut version = ""; - let mut old_name = ""; - let mut new_name = ""; - while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut old_name, &mut new_name]) { - renamed.push(RenamedLint { - old_name: parse_str_single_line(path.as_ref(), old_name), - new_name: parse_str_single_line(path.as_ref(), new_name), - version: parse_str_single_line(path.as_ref(), version), - }); - } - } else { - panic!("error reading renamed lints"); - } - - deprecated.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name)); - renamed.sort_by(|lhs, rhs| lhs.old_name.cmp(&rhs.old_name)); - (deprecated, renamed) -} - -/// Removes the line splices and surrounding quotes from a string literal -fn parse_str_lit(s: &str) -> String { - let s = s.strip_prefix("r").unwrap_or(s).trim_matches('#'); - let s = s - .strip_prefix('"') - .and_then(|s| s.strip_suffix('"')) - .unwrap_or_else(|| panic!("expected quoted string, found `{s}`")); - let mut res = String::with_capacity(s.len()); - rustc_literal_escaper::unescape_str(s, &mut |_, ch| { - if let Ok(ch) = ch { - res.push(ch); - } - }); - res -} - -fn parse_str_single_line(path: &Path, s: &str) -> String { - let value = parse_str_lit(s); - assert!( - !value.contains('\n'), - "error parsing `{}`: `{s}` should be a single line string", - path.display(), - ); - value -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_clippy_lint_decls() { - static CONTENTS: &str = r#" - declare_clippy_lint! { - #[clippy::version = "Hello Clippy!"] - pub PTR_ARG, - style, - "really long \ - text" - } - - declare_clippy_lint!{ - #[clippy::version = "Test version"] - pub DOC_MARKDOWN, - pedantic, - "single line" - } - "#; - let mut result = Vec::new(); - parse_clippy_lint_decls("".as_ref(), CONTENTS, "module_name", &mut result); - for r in &mut result { - r.declaration_range = Range::default(); - } - - let expected = vec![ - Lint { - name: "ptr_arg".into(), - group: "style".into(), - module: "module_name".into(), - path: PathBuf::new(), - declaration_range: Range::default(), - }, - Lint { - name: "doc_markdown".into(), - group: "pedantic".into(), - module: "module_name".into(), - path: PathBuf::new(), - declaration_range: Range::default(), - }, - ]; - assert_eq!(expected, result); - } -} diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs index 89962a110341..e8f91385320b 100644 --- a/clippy_dev/src/utils.rs +++ b/clippy_dev/src/utils.rs @@ -1,9 +1,7 @@ use core::fmt::{self, Display}; use core::num::NonZero; use core::ops::Range; -use core::slice; use core::str::FromStr; -use rustc_lexer::{self as lexer, FrontmatterAllowed}; use std::ffi::OsStr; use std::fs::{self, OpenOptions}; use std::io::{self, Read as _, Seek as _, SeekFrom, Write}; @@ -71,6 +69,12 @@ impl<'a> File<'a> { } } + /// Opens a file for reading. Panics on failure. + #[track_caller] + pub fn open_read(path: &'a (impl AsRef + ?Sized)) -> Self { + Self::open(path, OpenOptions::new().read(true)) + } + /// Opens a file if it exists, panicking on any other failure. #[track_caller] pub fn open_if_exists(path: &'a (impl AsRef + ?Sized), options: &mut OpenOptions) -> Option { @@ -82,15 +86,6 @@ impl<'a> File<'a> { } } - /// Opens and reads a file into a string, panicking of failure. - #[track_caller] - pub fn open_read_to_cleared_string<'dst>( - path: &'a (impl AsRef + ?Sized), - dst: &'dst mut String, - ) -> &'dst mut String { - Self::open(path, OpenOptions::new().read(true)).read_to_cleared_string(dst) - } - /// Read the entire contents of a file to the given buffer. #[track_caller] pub fn read_append_to_string<'dst>(&mut self, dst: &'dst mut String) -> &'dst mut String { @@ -426,181 +421,6 @@ pub fn update_text_region_fn( move |path, src, dst| update_text_region(path, start, end, src, dst, &mut insert) } -#[derive(Clone, Copy)] -pub enum Token<'a> { - /// Matches any number of comments / doc comments. - AnyComment, - Ident(&'a str), - CaptureIdent, - LitStr, - CaptureLitStr, - Bang, - CloseBrace, - CloseBracket, - CloseParen, - /// This will consume the first colon even if the second doesn't exist. - DoubleColon, - Comma, - Eq, - Lifetime, - Lt, - Gt, - OpenBrace, - OpenBracket, - OpenParen, - Pound, - Semi, - Slash, -} - -pub struct RustSearcher<'txt> { - text: &'txt str, - cursor: lexer::Cursor<'txt>, - pos: u32, - next_token: lexer::Token, -} -impl<'txt> RustSearcher<'txt> { - #[must_use] - #[expect(clippy::inconsistent_struct_constructor)] - pub fn new(text: &'txt str) -> Self { - let mut cursor = lexer::Cursor::new(text, FrontmatterAllowed::Yes); - Self { - text, - pos: 0, - next_token: cursor.advance_token(), - cursor, - } - } - - #[must_use] - pub fn peek_text(&self) -> &'txt str { - &self.text[self.pos as usize..(self.pos + self.next_token.len) as usize] - } - - #[must_use] - pub fn peek_len(&self) -> u32 { - self.next_token.len - } - - #[must_use] - pub fn peek(&self) -> lexer::TokenKind { - self.next_token.kind - } - - #[must_use] - pub fn pos(&self) -> u32 { - self.pos - } - - #[must_use] - pub fn at_end(&self) -> bool { - self.next_token.kind == lexer::TokenKind::Eof - } - - pub fn step(&mut self) { - // `next_len` is zero for the sentinel value and the eof marker. - self.pos += self.next_token.len; - self.next_token = self.cursor.advance_token(); - } - - /// Consumes the next token if it matches the requested value and captures the value if - /// requested. Returns true if a token was matched. - fn read_token(&mut self, token: Token<'_>, captures: &mut slice::IterMut<'_, &mut &'txt str>) -> bool { - loop { - match (token, self.next_token.kind) { - (_, lexer::TokenKind::Whitespace) - | ( - Token::AnyComment, - lexer::TokenKind::BlockComment { terminated: true, .. } | lexer::TokenKind::LineComment { .. }, - ) => self.step(), - (Token::AnyComment, _) => return true, - (Token::Bang, lexer::TokenKind::Bang) - | (Token::CloseBrace, lexer::TokenKind::CloseBrace) - | (Token::CloseBracket, lexer::TokenKind::CloseBracket) - | (Token::CloseParen, lexer::TokenKind::CloseParen) - | (Token::Comma, lexer::TokenKind::Comma) - | (Token::Eq, lexer::TokenKind::Eq) - | (Token::Lifetime, lexer::TokenKind::Lifetime { .. }) - | (Token::Lt, lexer::TokenKind::Lt) - | (Token::Gt, lexer::TokenKind::Gt) - | (Token::OpenBrace, lexer::TokenKind::OpenBrace) - | (Token::OpenBracket, lexer::TokenKind::OpenBracket) - | (Token::OpenParen, lexer::TokenKind::OpenParen) - | (Token::Pound, lexer::TokenKind::Pound) - | (Token::Semi, lexer::TokenKind::Semi) - | (Token::Slash, lexer::TokenKind::Slash) - | ( - Token::LitStr, - lexer::TokenKind::Literal { - kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. }, - .. - }, - ) => { - self.step(); - return true; - }, - (Token::Ident(x), lexer::TokenKind::Ident) if x == self.peek_text() => { - self.step(); - return true; - }, - (Token::DoubleColon, lexer::TokenKind::Colon) => { - self.step(); - if !self.at_end() && matches!(self.next_token.kind, lexer::TokenKind::Colon) { - self.step(); - return true; - } - return false; - }, - ( - Token::CaptureLitStr, - lexer::TokenKind::Literal { - kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. }, - .. - }, - ) - | (Token::CaptureIdent, lexer::TokenKind::Ident) => { - **captures.next().unwrap() = self.peek_text(); - self.step(); - return true; - }, - _ => return false, - } - } - } - - #[must_use] - pub fn find_token(&mut self, token: Token<'_>) -> bool { - let mut capture = [].iter_mut(); - while !self.read_token(token, &mut capture) { - self.step(); - if self.at_end() { - return false; - } - } - true - } - - #[must_use] - pub fn find_capture_token(&mut self, token: Token<'_>) -> Option<&'txt str> { - let mut res = ""; - let mut capture = &mut res; - let mut capture = slice::from_mut(&mut capture).iter_mut(); - while !self.read_token(token, &mut capture) { - self.step(); - if self.at_end() { - return None; - } - } - Some(res) - } - - #[must_use] - pub fn match_tokens(&mut self, tokens: &[Token<'_>], captures: &mut [&mut &'txt str]) -> bool { - let mut captures = captures.iter_mut(); - tokens.iter().all(|&t| self.read_token(t, &mut captures)) - } -} - #[expect(clippy::must_use_candidate)] pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool { match OpenOptions::new().create_new(true).write(true).open(new_name) { @@ -756,8 +576,8 @@ pub fn delete_dir_if_exists(path: &Path) { } /// Walks all items excluding top-level dot files/directories and any target directories. -pub fn walk_dir_no_dot_or_target() -> impl Iterator> { - WalkDir::new(".").into_iter().filter_entry(|e| { +pub fn walk_dir_no_dot_or_target(p: impl AsRef) -> impl Iterator> { + WalkDir::new(p.as_ref()).into_iter().filter_entry(|e| { e.path() .file_name() .is_none_or(|x| x != "target" && x.as_encoded_bytes().first().copied() != Some(b'.')) diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index c3f8e02b4c06..d4be135f8ce9 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -84,8 +84,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::collection_is_never_read::COLLECTION_IS_NEVER_READ_INFO, crate::comparison_chain::COMPARISON_CHAIN_INFO, crate::copies::BRANCHES_SHARING_CODE_INFO, - crate::copies::IFS_SAME_COND_INFO, crate::copies::IF_SAME_THEN_ELSE_INFO, + crate::copies::IFS_SAME_COND_INFO, crate::copies::SAME_FUNCTIONS_IN_IF_CONDITION_INFO, crate::copy_iterator::COPY_ITERATOR_INFO, crate::crate_in_macro_def::CRATE_IN_MACRO_DEF_INFO, @@ -102,9 +102,9 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::dereference::NEEDLESS_BORROW_INFO, crate::dereference::REF_BINDING_TO_REFERENCE_INFO, crate::derivable_impls::DERIVABLE_IMPLS_INFO, - crate::derive::DERIVED_HASH_WITH_MANUAL_EQ_INFO, crate::derive::DERIVE_ORD_XOR_PARTIAL_ORD_INFO, crate::derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ_INFO, + crate::derive::DERIVED_HASH_WITH_MANUAL_EQ_INFO, crate::derive::EXPL_IMPL_CLONE_ON_COPY_INFO, crate::derive::UNSAFE_DERIVE_DESERIALIZE_INFO, crate::disallowed_macros::DISALLOWED_MACROS_INFO, @@ -333,8 +333,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::matches::MATCH_SAME_ARMS_INFO, crate::matches::MATCH_SINGLE_BINDING_INFO, crate::matches::MATCH_STR_CASE_MISMATCH_INFO, - crate::matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS_INFO, crate::matches::MATCH_WILD_ERR_ARM_INFO, + crate::matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS_INFO, crate::matches::NEEDLESS_MATCH_INFO, crate::matches::REDUNDANT_GUARDS_INFO, crate::matches::REDUNDANT_PATTERN_MATCHING_INFO, @@ -349,154 +349,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::mem_replace::MEM_REPLACE_OPTION_WITH_SOME_INFO, crate::mem_replace::MEM_REPLACE_WITH_DEFAULT_INFO, crate::mem_replace::MEM_REPLACE_WITH_UNINIT_INFO, - crate::methods::BIND_INSTEAD_OF_MAP_INFO, - crate::methods::BYTES_COUNT_TO_LEN_INFO, - crate::methods::BYTES_NTH_INFO, - crate::methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS_INFO, - crate::methods::CHARS_LAST_CMP_INFO, - crate::methods::CHARS_NEXT_CMP_INFO, - crate::methods::CLEAR_WITH_DRAIN_INFO, - crate::methods::CLONED_INSTEAD_OF_COPIED_INFO, - crate::methods::CLONE_ON_COPY_INFO, - crate::methods::CLONE_ON_REF_PTR_INFO, - crate::methods::COLLAPSIBLE_STR_REPLACE_INFO, - crate::methods::CONST_IS_EMPTY_INFO, - crate::methods::DOUBLE_ENDED_ITERATOR_LAST_INFO, - crate::methods::DRAIN_COLLECT_INFO, - crate::methods::ERR_EXPECT_INFO, - crate::methods::EXPECT_FUN_CALL_INFO, - crate::methods::EXPECT_USED_INFO, - crate::methods::EXTEND_WITH_DRAIN_INFO, - crate::methods::FILETYPE_IS_FILE_INFO, - crate::methods::FILTER_MAP_BOOL_THEN_INFO, - crate::methods::FILTER_MAP_IDENTITY_INFO, - crate::methods::FILTER_MAP_NEXT_INFO, - crate::methods::FILTER_NEXT_INFO, - crate::methods::FLAT_MAP_IDENTITY_INFO, - crate::methods::FLAT_MAP_OPTION_INFO, - crate::methods::FORMAT_COLLECT_INFO, - crate::methods::FROM_ITER_INSTEAD_OF_COLLECT_INFO, - crate::methods::GET_FIRST_INFO, - crate::methods::GET_LAST_WITH_LEN_INFO, - crate::methods::GET_UNWRAP_INFO, - crate::methods::IMPLICIT_CLONE_INFO, - crate::methods::INEFFICIENT_TO_STRING_INFO, - crate::methods::INSPECT_FOR_EACH_INFO, - crate::methods::INTO_ITER_ON_REF_INFO, - crate::methods::IO_OTHER_ERROR_INFO, - crate::methods::IP_CONSTANT_INFO, - crate::methods::IS_DIGIT_ASCII_RADIX_INFO, - crate::methods::ITERATOR_STEP_BY_ZERO_INFO, - crate::methods::ITER_CLONED_COLLECT_INFO, - crate::methods::ITER_COUNT_INFO, - crate::methods::ITER_FILTER_IS_OK_INFO, - crate::methods::ITER_FILTER_IS_SOME_INFO, - crate::methods::ITER_KV_MAP_INFO, - crate::methods::ITER_NEXT_SLICE_INFO, - crate::methods::ITER_NTH_INFO, - crate::methods::ITER_NTH_ZERO_INFO, - crate::methods::ITER_ON_EMPTY_COLLECTIONS_INFO, - crate::methods::ITER_ON_SINGLE_ITEMS_INFO, - crate::methods::ITER_OUT_OF_BOUNDS_INFO, - crate::methods::ITER_OVEREAGER_CLONED_INFO, - crate::methods::ITER_SKIP_NEXT_INFO, - crate::methods::ITER_SKIP_ZERO_INFO, - crate::methods::ITER_WITH_DRAIN_INFO, - crate::methods::JOIN_ABSOLUTE_PATHS_INFO, - crate::methods::MANUAL_CONTAINS_INFO, - crate::methods::MANUAL_C_STR_LITERALS_INFO, - crate::methods::MANUAL_FILTER_MAP_INFO, - crate::methods::MANUAL_FIND_MAP_INFO, - crate::methods::MANUAL_INSPECT_INFO, - crate::methods::MANUAL_IS_VARIANT_AND_INFO, - crate::methods::MANUAL_NEXT_BACK_INFO, - crate::methods::MANUAL_OK_OR_INFO, - crate::methods::MANUAL_REPEAT_N_INFO, - crate::methods::MANUAL_SATURATING_ARITHMETIC_INFO, - crate::methods::MANUAL_SPLIT_ONCE_INFO, - crate::methods::MANUAL_STR_REPEAT_INFO, - crate::methods::MANUAL_TRY_FOLD_INFO, - crate::methods::MAP_ALL_ANY_IDENTITY_INFO, - crate::methods::MAP_CLONE_INFO, - crate::methods::MAP_COLLECT_RESULT_UNIT_INFO, - crate::methods::MAP_ERR_IGNORE_INFO, - crate::methods::MAP_FLATTEN_INFO, - crate::methods::MAP_IDENTITY_INFO, - crate::methods::MAP_UNWRAP_OR_INFO, - crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO, - crate::methods::MUT_MUTEX_LOCK_INFO, - crate::methods::NAIVE_BYTECOUNT_INFO, - crate::methods::NEEDLESS_AS_BYTES_INFO, - crate::methods::NEEDLESS_CHARACTER_ITERATION_INFO, - crate::methods::NEEDLESS_COLLECT_INFO, - crate::methods::NEEDLESS_OPTION_AS_DEREF_INFO, - crate::methods::NEEDLESS_OPTION_TAKE_INFO, - crate::methods::NEEDLESS_SPLITN_INFO, - crate::methods::NEW_RET_NO_SELF_INFO, - crate::methods::NONSENSICAL_OPEN_OPTIONS_INFO, - crate::methods::NO_EFFECT_REPLACE_INFO, - crate::methods::OBFUSCATED_IF_ELSE_INFO, - crate::methods::OK_EXPECT_INFO, - crate::methods::OPTION_AS_REF_CLONED_INFO, - crate::methods::OPTION_AS_REF_DEREF_INFO, - crate::methods::OPTION_FILTER_MAP_INFO, - crate::methods::OPTION_MAP_OR_NONE_INFO, - crate::methods::OR_FUN_CALL_INFO, - crate::methods::OR_THEN_UNWRAP_INFO, - crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO, - crate::methods::PATH_ENDS_WITH_EXT_INFO, - crate::methods::RANGE_ZIP_WITH_LEN_INFO, - crate::methods::READONLY_WRITE_LOCK_INFO, - crate::methods::READ_LINE_WITHOUT_TRIM_INFO, - crate::methods::REDUNDANT_AS_STR_INFO, - crate::methods::REPEAT_ONCE_INFO, - crate::methods::RESULT_FILTER_MAP_INFO, - crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO, - crate::methods::RETURN_AND_THEN_INFO, - crate::methods::SEARCH_IS_SOME_INFO, - crate::methods::SEEK_FROM_CURRENT_INFO, - crate::methods::SEEK_TO_START_INSTEAD_OF_REWIND_INFO, - crate::methods::SHOULD_IMPLEMENT_TRAIT_INFO, - crate::methods::SINGLE_CHAR_ADD_STR_INFO, - crate::methods::SKIP_WHILE_NEXT_INFO, - crate::methods::SLICED_STRING_AS_BYTES_INFO, - crate::methods::STABLE_SORT_PRIMITIVE_INFO, - crate::methods::STRING_EXTEND_CHARS_INFO, - crate::methods::STRING_LIT_CHARS_ANY_INFO, - crate::methods::STR_SPLIT_AT_NEWLINE_INFO, - crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO, - crate::methods::SUSPICIOUS_MAP_INFO, - crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO, - crate::methods::SUSPICIOUS_SPLITN_INFO, - crate::methods::SUSPICIOUS_TO_OWNED_INFO, - crate::methods::SWAP_WITH_TEMPORARY_INFO, - crate::methods::TYPE_ID_ON_BOX_INFO, - crate::methods::UNBUFFERED_BYTES_INFO, - crate::methods::UNINIT_ASSUMED_INIT_INFO, - crate::methods::UNIT_HASH_INFO, - crate::methods::UNNECESSARY_FALLIBLE_CONVERSIONS_INFO, - crate::methods::UNNECESSARY_FILTER_MAP_INFO, - crate::methods::UNNECESSARY_FIND_MAP_INFO, - crate::methods::UNNECESSARY_FIRST_THEN_CHECK_INFO, - crate::methods::UNNECESSARY_FOLD_INFO, - crate::methods::UNNECESSARY_GET_THEN_CHECK_INFO, - crate::methods::UNNECESSARY_JOIN_INFO, - crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO, - crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO, - crate::methods::UNNECESSARY_MAP_OR_INFO, - crate::methods::UNNECESSARY_MIN_OR_MAX_INFO, - crate::methods::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO, - crate::methods::UNNECESSARY_SORT_BY_INFO, - crate::methods::UNNECESSARY_TO_OWNED_INFO, - crate::methods::UNWRAP_OR_DEFAULT_INFO, - crate::methods::UNWRAP_USED_INFO, - crate::methods::USELESS_ASREF_INFO, - crate::methods::USELESS_NONZERO_NEW_UNCHECKED_INFO, - crate::methods::VEC_RESIZE_TO_ZERO_INFO, - crate::methods::VERBOSE_FILE_READS_INFO, - crate::methods::WAKER_CLONE_WAKE_INFO, - crate::methods::WRONG_SELF_CONVENTION_INFO, - crate::methods::ZST_OFFSET_INFO, crate::min_ident_chars::MIN_IDENT_CHARS_INFO, crate::minmax::MIN_MAX_INFO, crate::misc::SHORT_CIRCUIT_STATEMENT_INFO, @@ -633,8 +485,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::ranges::RANGE_MINUS_ONE_INFO, crate::ranges::RANGE_PLUS_ONE_INFO, crate::ranges::REVERSED_EMPTY_RANGES_INFO, - crate::raw_strings::NEEDLESS_RAW_STRINGS_INFO, crate::raw_strings::NEEDLESS_RAW_STRING_HASHES_INFO, + crate::raw_strings::NEEDLESS_RAW_STRINGS_INFO, crate::rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT_INFO, crate::read_zero_byte_vec::READ_ZERO_BYTE_VEC_INFO, crate::redundant_async_block::REDUNDANT_ASYNC_BLOCK_INFO, @@ -685,13 +537,13 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::std_instead_of_core::STD_INSTEAD_OF_CORE_INFO, crate::string_patterns::MANUAL_PATTERN_CHAR_COMPARISON_INFO, crate::string_patterns::SINGLE_CHAR_PATTERN_INFO, + crate::strings::STR_TO_STRING_INFO, crate::strings::STRING_ADD_INFO, crate::strings::STRING_ADD_ASSIGN_INFO, crate::strings::STRING_FROM_UTF8_AS_BYTES_INFO, crate::strings::STRING_LIT_AS_BYTES_INFO, crate::strings::STRING_SLICE_INFO, crate::strings::STRING_TO_STRING_INFO, - crate::strings::STR_TO_STRING_INFO, crate::strings::TRIM_SPLIT_WHITESPACE_INFO, crate::strlen_on_c_strings::STRLEN_ON_C_STRINGS_INFO, crate::suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS_INFO, @@ -712,7 +564,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::transmute::CROSSPOINTER_TRANSMUTE_INFO, crate::transmute::EAGER_TRANSMUTE_INFO, crate::transmute::MISSING_TRANSMUTE_ANNOTATIONS_INFO, - crate::transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS_INFO, crate::transmute::TRANSMUTE_BYTES_TO_STR_INFO, crate::transmute::TRANSMUTE_INT_TO_BOOL_INFO, crate::transmute::TRANSMUTE_INT_TO_NON_ZERO_INFO, @@ -720,6 +571,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::transmute::TRANSMUTE_PTR_TO_PTR_INFO, crate::transmute::TRANSMUTE_PTR_TO_REF_INFO, crate::transmute::TRANSMUTE_UNDEFINED_REPR_INFO, + crate::transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS_INFO, crate::transmute::TRANSMUTING_NULL_INFO, crate::transmute::UNSOUND_COLLECTION_TRANSMUTE_INFO, crate::transmute::USELESS_TRANSMUTE_INFO, @@ -776,19 +628,19 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::vec::USELESS_VEC_INFO, crate::vec_init_then_push::VEC_INIT_THEN_PUSH_INFO, crate::visibility::NEEDLESS_PUB_SELF_INFO, - crate::visibility::PUB_WITHOUT_SHORTHAND_INFO, crate::visibility::PUB_WITH_SHORTHAND_INFO, + crate::visibility::PUB_WITHOUT_SHORTHAND_INFO, crate::wildcard_imports::ENUM_GLOB_USE_INFO, crate::wildcard_imports::WILDCARD_IMPORTS_INFO, - crate::write::PRINTLN_EMPTY_STRING_INFO, crate::write::PRINT_LITERAL_INFO, crate::write::PRINT_STDERR_INFO, crate::write::PRINT_STDOUT_INFO, crate::write::PRINT_WITH_NEWLINE_INFO, + crate::write::PRINTLN_EMPTY_STRING_INFO, crate::write::USE_DEBUG_INFO, - crate::write::WRITELN_EMPTY_STRING_INFO, crate::write::WRITE_LITERAL_INFO, crate::write::WRITE_WITH_NEWLINE_INFO, + crate::write::WRITELN_EMPTY_STRING_INFO, crate::zero_div_zero::ZERO_DIVIDED_BY_ZERO_INFO, crate::zero_repeat_side_effects::ZERO_REPEAT_SIDE_EFFECTS_INFO, crate::zero_sized_map_values::ZERO_SIZED_MAP_VALUES_INFO, diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 49397938ca73..66f240f6f71f 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -1,5 +1,3 @@ -#![allow(clippy::lint_without_lint_pass)] - use clippy_config::Conf; use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then}; diff --git a/clippy_lints/src/ineffective_open_options.rs b/clippy_lints/src/ineffective_open_options.rs index 7a751514b647..a159f6157183 100644 --- a/clippy_lints/src/ineffective_open_options.rs +++ b/clippy_lints/src/ineffective_open_options.rs @@ -1,13 +1,12 @@ -use crate::methods::method_call; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::{peel_blocks, sym}; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{peel_blocks, peel_hir_expr_while, sym}; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; use rustc_session::declare_lint_pass; -use rustc_span::{BytePos, Span}; declare_clippy_lint! { /// ### What it does @@ -43,53 +42,58 @@ declare_clippy_lint! { declare_lint_pass!(IneffectiveOpenOptions => [INEFFECTIVE_OPEN_OPTIONS]); -fn index_if_arg_is_boolean(args: &[Expr<'_>], call_span: Span) -> Option { - if let [arg] = args - && let ExprKind::Lit(lit) = peel_blocks(arg).kind - && lit.node == LitKind::Bool(true) - { - // The `.` is not included in the span so we cheat a little bit to include it as well. - Some(call_span.with_lo(call_span.lo() - BytePos(1))) - } else { - None - } -} - impl<'tcx> LateLintPass<'tcx> for IneffectiveOpenOptions { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let Some((sym::open, mut receiver, [_arg], _, _)) = method_call(expr) else { - return; - }; - let receiver_ty = cx.typeck_results().expr_ty(receiver); - match receiver_ty.peel_refs().kind() { - ty::Adt(adt, _) if cx.tcx.is_diagnostic_item(sym::FsOpenOptions, adt.did()) => {}, - _ => return, - } - - let mut append = None; - let mut write = None; + if let ExprKind::MethodCall(name, recv, [_], _) = expr.kind + && name.ident.name == sym::open + && !expr.span.from_expansion() + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv).peel_refs(), sym::FsOpenOptions) + { + let mut append = false; + let mut write = None; + peel_hir_expr_while(recv, |e| { + if let ExprKind::MethodCall(name, recv, args, call_span) = e.kind + && !e.span.from_expansion() + { + if let [arg] = args + && let ExprKind::Lit(lit) = peel_blocks(arg).kind + && matches!(lit.node, LitKind::Bool(true)) + && !arg.span.from_expansion() + && !lit.span.from_expansion() + { + match name.ident.name { + sym::append => append = true, + sym::write + if let Some(range) = call_span.map_range(cx, |_, text, range| { + if text.get(..range.start)?.ends_with('.') { + Some(range.start - 1..range.end) + } else { + None + } + }) => + { + write = Some(call_span.with_lo(range.start)); + }, + _ => {}, + } + } + Some(recv) + } else { + None + } + }); - while let Some((name, recv, args, _, span)) = method_call(receiver) { - if name == sym::append { - append = index_if_arg_is_boolean(args, span); - } else if name == sym::write { - write = index_if_arg_is_boolean(args, span); + if append && let Some(write_span) = write { + span_lint_and_sugg( + cx, + INEFFECTIVE_OPEN_OPTIONS, + write_span, + "unnecessary use of `.write(true)` because there is `.append(true)`", + "remove `.write(true)`", + String::new(), + Applicability::MachineApplicable, + ); } - receiver = recv; - } - - if let Some(write_span) = write - && append.is_some() - { - span_lint_and_sugg( - cx, - INEFFECTIVE_OPEN_OPTIONS, - write_span, - "unnecessary use of `.write(true)` because there is `.append(true)`", - "remove `.write(true)`", - String::new(), - Applicability::MachineApplicable, - ); } } } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 96a6dee58852..ab56fc93732a 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -55,7 +55,6 @@ extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; extern crate rustc_trait_selection; -extern crate smallvec; extern crate thin_vec; #[macro_use] @@ -231,7 +230,6 @@ mod map_unit_fn; mod match_result_ok; mod matches; mod mem_replace; -mod methods; mod min_ident_chars; mod minmax; mod misc; @@ -440,7 +438,7 @@ pub fn explain(name: &str) -> i32 { /// /// Used in `./src/driver.rs`. #[expect(clippy::too_many_lines)] -pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Conf) { +pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Conf) -> FormatArgsStorage { for (old_name, new_name) in deprecated_lints::RENAMED { store.register_renamed(old_name, new_name); } @@ -503,8 +501,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions)); store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports)); store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(conf))); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(methods::Methods::new(conf, format_args.clone()))); store.register_late_pass(move |_| Box::new(matches::Matches::new(conf))); store.register_late_pass(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustive::new(conf))); store.register_late_pass(move |_| Box::new(manual_strip::ManualStrip::new(conf))); @@ -831,4 +827,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom)); store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)); // add lints here, do not remove this comment, it's used in `new_lint` + + format_args_storage } diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 01c36b8cb12f..5b46922c5ab3 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -876,6 +876,15 @@ impl<'tcx> LateLintPass<'tcx> for Loops { missing_spin_loop::check(cx, condition, body); manual_while_let_some::check(cx, condition, body, span); } + + if let ExprKind::MethodCall(path, recv, [arg], _) = expr.kind + && matches!( + path.ident.name, + sym::all | sym::any | sym::filter_map | sym::find_map | sym::flat_map | sym::for_each | sym::map + ) + { + unused_enumerate_index::check_method(cx, expr, recv, arg); + } } } @@ -904,7 +913,7 @@ impl Loops { same_item_push::check(cx, pat, arg, body, expr, self.msrv); manual_flatten::check(cx, pat, arg, body, span, self.msrv); manual_find::check(cx, pat, arg, body, span, expr); - unused_enumerate_index::check(cx, pat, arg, body); + unused_enumerate_index::check_loop(cx, arg, pat, None, body); char_indices_as_byte_indices::check(cx, pat, arg, body); } diff --git a/clippy_lints/src/loops/unused_enumerate_index.rs b/clippy_lints/src/loops/unused_enumerate_index.rs index 51e21aa9734e..d4cbde53afb3 100644 --- a/clippy_lints/src/loops/unused_enumerate_index.rs +++ b/clippy_lints/src/loops/unused_enumerate_index.rs @@ -1,40 +1,84 @@ use super::UNUSED_ENUMERATE_INDEX; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet; -use clippy_utils::{pat_is_wild, sugg}; +use clippy_utils::source::{SpanRangeExt, walk_span_to_context}; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{expr_or_init, is_lint_allowed, is_trait_method, pat_is_wild}; use rustc_errors::Applicability; -use rustc_hir::def::DefKind; -use rustc_hir::{Expr, ExprKind, Pat, PatKind}; +use rustc_hir::{Expr, ExprKind, Pat, PatKind, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty; -use rustc_span::sym; +use rustc_span::{Span, SyntaxContext, sym}; -/// Checks for the `UNUSED_ENUMERATE_INDEX` lint. -/// -/// The lint is also partially implemented in `clippy_lints/src/methods/unused_enumerate_index.rs`. -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_>, body: &'tcx Expr<'tcx>) { - if let PatKind::Tuple([index, elem], _) = pat.kind - && let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind - && let ty = cx.typeck_results().expr_ty(arg) - && pat_is_wild(cx, &index.kind, body) - && let ty::Adt(base, _) = *ty.kind() - && cx.tcx.is_diagnostic_item(sym::Enumerate, base.did()) - && let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id) - && cx.tcx.is_diagnostic_item(sym::enumerate_method, call_id) +pub(super) fn check_method<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + recv: &'tcx Expr<'tcx>, + arg: &'tcx Expr<'tcx>, +) { + if let ExprKind::Closure(closure) = arg.kind + && let body = cx.tcx.hir_body(closure.body) + && let [param] = body.params + && is_trait_method(cx, e, sym::Iterator) + && let [input] = closure.fn_decl.inputs + && !arg.span.from_expansion() + && !input.span.from_expansion() + && !recv.span.from_expansion() + && !param.span.from_expansion() { + let ty_spans = if let TyKind::Tup([_, inner]) = input.kind { + let Some(inner) = walk_span_to_context(inner.span, SyntaxContext::root()) else { + return; + }; + Some((input.span, inner)) + } else { + None + }; + check_loop(cx, recv, param.pat, ty_spans, body.value); + } +} + +pub(super) fn check_loop<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + pat: &'tcx Pat<'tcx>, + ty_spans: Option<(Span, Span)>, + body: &'tcx Expr<'tcx>, +) { + if let PatKind::Tuple([idx_pat, inner_pat], _) = pat.kind + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(e), sym::Enumerate) + && pat_is_wild(cx, &idx_pat.kind, body) + && let enumerate_call = expr_or_init(cx, e) + && let ExprKind::MethodCall(_, _, [], enumerate_span) = enumerate_call.kind + && let Some(enumerate_id) = cx.typeck_results().type_dependent_def_id(enumerate_call.hir_id) + && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_id) + && !is_lint_allowed(cx, UNUSED_ENUMERATE_INDEX, enumerate_call.hir_id) + && !enumerate_call.span.from_expansion() + && !pat.span.from_expansion() + && !idx_pat.span.from_expansion() + && !inner_pat.span.from_expansion() + && let Some(enumerate_range) = enumerate_span.map_range(cx, |_, text, range| { + text.get(..range.start)? + .ends_with('.') + .then_some(range.start - 1..range.end) + }) + { + let enumerate_span = Span::new(enumerate_range.start, enumerate_range.end, SyntaxContext::root(), None); span_lint_and_then( cx, UNUSED_ENUMERATE_INDEX, - arg.span, + enumerate_span, "you seem to use `.enumerate()` and immediately discard the index", |diag| { - let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter"); + let mut spans = Vec::with_capacity(5); + spans.push((enumerate_span, String::new())); + spans.push((pat.span.with_hi(inner_pat.span.lo()), String::new())); + spans.push((pat.span.with_lo(inner_pat.span.hi()), String::new())); + if let Some((outer, inner)) = ty_spans { + spans.push((outer.with_hi(inner.lo()), String::new())); + spans.push((outer.with_lo(inner.hi()), String::new())); + } diag.multipart_suggestion( "remove the `.enumerate()` call", - vec![ - (pat.span, snippet(cx, elem.span, "..").into_owned()), - (arg.span, base_iter.to_string()), - ], + spans, Applicability::MachineApplicable, ); }, diff --git a/clippy_lints/src/methods/chars_cmp.rs b/clippy_lints/src/methods/chars_cmp.rs deleted file mode 100644 index de27a45ba4d9..000000000000 --- a/clippy_lints/src/methods/chars_cmp.rs +++ /dev/null @@ -1,49 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{method_chain_args, path_def_id}; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::{LateContext, Lint}; -use rustc_middle::ty; -use rustc_span::Symbol; - -/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints. -pub(super) fn check( - cx: &LateContext<'_>, - info: &crate::methods::BinaryExprInfo<'_>, - chain_methods: &[Symbol], - lint: &'static Lint, - suggest: &str, -) -> bool { - if let Some(args) = method_chain_args(info.chain, chain_methods) - && let hir::ExprKind::Call(fun, [arg_char]) = info.other.kind - && let Some(id) = path_def_id(cx, fun).map(|ctor_id| cx.tcx.parent(ctor_id)) - && Some(id) == cx.tcx.lang_items().option_some_variant() - { - let mut applicability = Applicability::MachineApplicable; - let self_ty = cx.typeck_results().expr_ty_adjusted(args[0].0).peel_refs(); - - if *self_ty.kind() != ty::Str { - return false; - } - - span_lint_and_sugg( - cx, - lint, - info.expr.span, - format!("you should use the `{suggest}` method"), - "like this", - format!( - "{}{}.{suggest}({})", - if info.eq { "" } else { "!" }, - snippet_with_applicability(cx, args[0].0.span, "..", &mut applicability), - snippet_with_applicability(cx, arg_char.span, "..", &mut applicability) - ), - applicability, - ); - - return true; - } - - false -} diff --git a/clippy_lints/src/methods/chars_cmp_with_unwrap.rs b/clippy_lints/src/methods/chars_cmp_with_unwrap.rs deleted file mode 100644 index 1c72a973cfa1..000000000000 --- a/clippy_lints/src/methods/chars_cmp_with_unwrap.rs +++ /dev/null @@ -1,42 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::method_chain_args; -use clippy_utils::source::snippet_with_applicability; -use rustc_ast::ast; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::{LateContext, Lint}; -use rustc_span::Symbol; - -/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints with `unwrap()`. -pub(super) fn check( - cx: &LateContext<'_>, - info: &crate::methods::BinaryExprInfo<'_>, - chain_methods: &[Symbol], - lint: &'static Lint, - suggest: &str, -) -> bool { - if let Some(args) = method_chain_args(info.chain, chain_methods) - && let hir::ExprKind::Lit(lit) = info.other.kind - && let ast::LitKind::Char(c) = lit.node - { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - lint, - info.expr.span, - format!("you should use the `{suggest}` method"), - "like this", - format!( - "{}{}.{suggest}('{}')", - if info.eq { "" } else { "!" }, - snippet_with_applicability(cx, args[0].0.span, "..", &mut applicability), - c.escape_default() - ), - applicability, - ); - - true - } else { - false - } -} diff --git a/clippy_lints/src/methods/chars_last_cmp.rs b/clippy_lints/src/methods/chars_last_cmp.rs deleted file mode 100644 index 8729e91d191f..000000000000 --- a/clippy_lints/src/methods/chars_last_cmp.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::methods::chars_cmp; -use clippy_utils::sym; -use rustc_lint::LateContext; - -use super::CHARS_LAST_CMP; - -/// Checks for the `CHARS_LAST_CMP` lint. -pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool { - if chars_cmp::check(cx, info, &[sym::chars, sym::last], CHARS_LAST_CMP, "ends_with") { - true - } else { - chars_cmp::check(cx, info, &[sym::chars, sym::next_back], CHARS_LAST_CMP, "ends_with") - } -} diff --git a/clippy_lints/src/methods/chars_last_cmp_with_unwrap.rs b/clippy_lints/src/methods/chars_last_cmp_with_unwrap.rs deleted file mode 100644 index 027d0a3947bf..000000000000 --- a/clippy_lints/src/methods/chars_last_cmp_with_unwrap.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::methods::chars_cmp_with_unwrap; -use clippy_utils::sym; -use rustc_lint::LateContext; - -use super::CHARS_LAST_CMP; - -/// Checks for the `CHARS_LAST_CMP` lint with `unwrap()`. -pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool { - if chars_cmp_with_unwrap::check( - cx, - info, - &[sym::chars, sym::last, sym::unwrap], - CHARS_LAST_CMP, - "ends_with", - ) { - true - } else { - chars_cmp_with_unwrap::check( - cx, - info, - &[sym::chars, sym::next_back, sym::unwrap], - CHARS_LAST_CMP, - "ends_with", - ) - } -} diff --git a/clippy_lints/src/methods/chars_next_cmp.rs b/clippy_lints/src/methods/chars_next_cmp.rs deleted file mode 100644 index 2438843bf3ab..000000000000 --- a/clippy_lints/src/methods/chars_next_cmp.rs +++ /dev/null @@ -1,9 +0,0 @@ -use clippy_utils::sym; -use rustc_lint::LateContext; - -use super::CHARS_NEXT_CMP; - -/// Checks for the `CHARS_NEXT_CMP` lint. -pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool { - crate::methods::chars_cmp::check(cx, info, &[sym::chars, sym::next], CHARS_NEXT_CMP, "starts_with") -} diff --git a/clippy_lints/src/methods/chars_next_cmp_with_unwrap.rs b/clippy_lints/src/methods/chars_next_cmp_with_unwrap.rs deleted file mode 100644 index 9b3609f19d72..000000000000 --- a/clippy_lints/src/methods/chars_next_cmp_with_unwrap.rs +++ /dev/null @@ -1,15 +0,0 @@ -use clippy_utils::sym; -use rustc_lint::LateContext; - -use super::CHARS_NEXT_CMP; - -/// Checks for the `CHARS_NEXT_CMP` lint with `unwrap()`. -pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool { - crate::methods::chars_cmp_with_unwrap::check( - cx, - info, - &[sym::chars, sym::next, sym::unwrap], - CHARS_NEXT_CMP, - "starts_with", - ) -} diff --git a/clippy_lints/src/methods/filetype_is_file.rs b/clippy_lints/src/methods/filetype_is_file.rs deleted file mode 100644 index 35008c39c084..000000000000 --- a/clippy_lints/src/methods/filetype_is_file.rs +++ /dev/null @@ -1,41 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::get_parent_expr; -use clippy_utils::ty::is_type_diagnostic_item; -use rustc_hir as hir; -use rustc_lint::LateContext; -use rustc_span::{Span, sym}; - -use super::FILETYPE_IS_FILE; - -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { - let ty = cx.typeck_results().expr_ty(recv); - - if !is_type_diagnostic_item(cx, ty, sym::FileType) { - return; - } - - let span: Span; - let verb: &str; - let lint_unary: &str; - let help_unary: &str; - if let Some(parent) = get_parent_expr(cx, expr) - && let hir::ExprKind::Unary(op, _) = parent.kind - && op == hir::UnOp::Not - { - lint_unary = "!"; - verb = "denies"; - help_unary = ""; - span = parent.span; - } else { - lint_unary = ""; - verb = "covers"; - help_unary = "!"; - span = expr.span; - } - let lint_msg = format!("`{lint_unary}FileType::is_file()` only {verb} regular files"); - - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then(cx, FILETYPE_IS_FILE, span, lint_msg, |diag| { - diag.help(format!("use `{help_unary}FileType::is_dir()` instead")); - }); -} diff --git a/clippy_lints/src/methods/flat_map_option.rs b/clippy_lints/src/methods/flat_map_option.rs deleted file mode 100644 index 3242dcadb701..000000000000 --- a/clippy_lints/src/methods/flat_map_option.rs +++ /dev/null @@ -1,34 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_trait_method; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::LateContext; -use rustc_middle::ty; -use rustc_span::{Span, sym}; - -use super::FLAT_MAP_OPTION; -use clippy_utils::ty::is_type_diagnostic_item; - -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, span: Span) { - if !is_trait_method(cx, expr, sym::Iterator) { - return; - } - let arg_ty = cx.typeck_results().expr_ty_adjusted(arg); - let sig = match arg_ty.kind() { - ty::Closure(_, args) => args.as_closure().sig(), - _ if arg_ty.is_fn() => arg_ty.fn_sig(cx.tcx), - _ => return, - }; - if !is_type_diagnostic_item(cx, sig.output().skip_binder(), sym::Option) { - return; - } - span_lint_and_sugg( - cx, - FLAT_MAP_OPTION, - span, - "used `flat_map` where `filter_map` could be used instead", - "try", - "filter_map".into(), - Applicability::MachineApplicable, - ); -} diff --git a/clippy_lints/src/methods/inspect_for_each.rs b/clippy_lints/src/methods/inspect_for_each.rs deleted file mode 100644 index 3706f3b670ba..000000000000 --- a/clippy_lints/src/methods/inspect_for_each.rs +++ /dev/null @@ -1,23 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::is_trait_method; -use rustc_hir as hir; -use rustc_lint::LateContext; -use rustc_span::{Span, sym}; - -use super::INSPECT_FOR_EACH; - -/// lint use of `inspect().for_each()` for `Iterators` -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, inspect_span: Span) { - if is_trait_method(cx, expr, sym::Iterator) { - let msg = "called `inspect(..).for_each(..)` on an `Iterator`"; - let hint = "move the code from `inspect(..)` to `for_each(..)` and remove the `inspect(..)`"; - span_lint_and_help( - cx, - INSPECT_FOR_EACH, - inspect_span.with_hi(expr.span.hi()), - msg, - None, - hint, - ); - } -} diff --git a/clippy_lints/src/methods/map_err_ignore.rs b/clippy_lints/src/methods/map_err_ignore.rs deleted file mode 100644 index 5d0d4dae35fa..000000000000 --- a/clippy_lints/src/methods/map_err_ignore.rs +++ /dev/null @@ -1,38 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::is_type_diagnostic_item; -use rustc_hir::{CaptureBy, Closure, Expr, ExprKind, PatKind}; -use rustc_lint::LateContext; -use rustc_span::sym; - -use super::MAP_ERR_IGNORE; - -pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, arg: &Expr<'_>) { - if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(method_id) - && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::Result) - && let ExprKind::Closure(&Closure { - capture_clause: CaptureBy::Ref, - body, - fn_decl_span, - .. - }) = arg.kind - && let closure_body = cx.tcx.hir_body(body) - && let [param] = closure_body.params - && let PatKind::Wild = param.pat.kind - { - // span the area of the closure capture and warn that the - // original error will be thrown away - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then( - cx, - MAP_ERR_IGNORE, - fn_decl_span, - "`map_err(|_|...` wildcard pattern discards the original error", - |diag| { - diag.help( - "consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)", - ); - }, - ); - } -} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs deleted file mode 100644 index f2dabdd34387..000000000000 --- a/clippy_lints/src/methods/mod.rs +++ /dev/null @@ -1,5820 +0,0 @@ -mod bind_instead_of_map; -mod bytecount; -mod bytes_count_to_len; -mod bytes_nth; -mod case_sensitive_file_extension_comparisons; -mod chars_cmp; -mod chars_cmp_with_unwrap; -mod chars_last_cmp; -mod chars_last_cmp_with_unwrap; -mod chars_next_cmp; -mod chars_next_cmp_with_unwrap; -mod clear_with_drain; -mod clone_on_copy; -mod clone_on_ref_ptr; -mod cloned_instead_of_copied; -mod collapsible_str_replace; -mod double_ended_iterator_last; -mod drain_collect; -mod err_expect; -mod expect_fun_call; -mod extend_with_drain; -mod filetype_is_file; -mod filter_map; -mod filter_map_bool_then; -mod filter_map_identity; -mod filter_map_next; -mod filter_next; -mod flat_map_identity; -mod flat_map_option; -mod format_collect; -mod from_iter_instead_of_collect; -mod get_first; -mod get_last_with_len; -mod get_unwrap; -mod implicit_clone; -mod inefficient_to_string; -mod inspect_for_each; -mod into_iter_on_ref; -mod io_other_error; -mod ip_constant; -mod is_digit_ascii_radix; -mod is_empty; -mod iter_cloned_collect; -mod iter_count; -mod iter_filter; -mod iter_kv_map; -mod iter_next_slice; -mod iter_nth; -mod iter_nth_zero; -mod iter_on_single_or_empty_collections; -mod iter_out_of_bounds; -mod iter_overeager_cloned; -mod iter_skip_next; -mod iter_skip_zero; -mod iter_with_drain; -mod iterator_step_by_zero; -mod join_absolute_paths; -mod manual_c_str_literals; -mod manual_contains; -mod manual_inspect; -mod manual_is_variant_and; -mod manual_next_back; -mod manual_ok_or; -mod manual_repeat_n; -mod manual_saturating_arithmetic; -mod manual_str_repeat; -mod manual_try_fold; -mod map_all_any_identity; -mod map_clone; -mod map_collect_result_unit; -mod map_err_ignore; -mod map_flatten; -mod map_identity; -mod map_unwrap_or; -mod map_with_unused_argument_over_ranges; -mod mut_mutex_lock; -mod needless_as_bytes; -mod needless_character_iteration; -mod needless_collect; -mod needless_option_as_deref; -mod needless_option_take; -mod no_effect_replace; -mod obfuscated_if_else; -mod ok_expect; -mod open_options; -mod option_as_ref_cloned; -mod option_as_ref_deref; -mod option_map_or_none; -mod option_map_unwrap_or; -mod or_fun_call; -mod or_then_unwrap; -mod path_buf_push_overwrite; -mod path_ends_with_ext; -mod range_zip_with_len; -mod read_line_without_trim; -mod readonly_write_lock; -mod redundant_as_str; -mod repeat_once; -mod result_map_or_else_none; -mod return_and_then; -mod search_is_some; -mod seek_from_current; -mod seek_to_start_instead_of_rewind; -mod single_char_add_str; -mod single_char_insert_string; -mod single_char_push_string; -mod skip_while_next; -mod sliced_string_as_bytes; -mod stable_sort_primitive; -mod str_split; -mod str_splitn; -mod string_extend_chars; -mod string_lit_chars_any; -mod suspicious_command_arg_space; -mod suspicious_map; -mod suspicious_splitn; -mod suspicious_to_owned; -mod swap_with_temporary; -mod type_id_on_box; -mod unbuffered_bytes; -mod uninit_assumed_init; -mod unit_hash; -mod unnecessary_fallible_conversions; -mod unnecessary_filter_map; -mod unnecessary_first_then_check; -mod unnecessary_fold; -mod unnecessary_get_then_check; -mod unnecessary_iter_cloned; -mod unnecessary_join; -mod unnecessary_lazy_eval; -mod unnecessary_literal_unwrap; -mod unnecessary_map_or; -mod unnecessary_min_or_max; -mod unnecessary_result_map_or_else; -mod unnecessary_sort_by; -mod unnecessary_to_owned; -mod unused_enumerate_index; -mod unwrap_expect_used; -mod useless_asref; -mod useless_nonzero_new_unchecked; -mod utils; -mod vec_resize_to_zero; -mod verbose_file_reads; -mod waker_clone_wake; -mod wrong_self_convention; -mod zst_offset; - -use clippy_config::Conf; -use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; -use clippy_utils::macros::FormatArgsStorage; -use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item}; -use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty, sym}; -pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES; -use rustc_abi::ExternAbi; -use rustc_data_structures::fx::FxHashSet; -use rustc_hir as hir; -use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::{self, TraitRef, Ty}; -use rustc_session::impl_lint_pass; -use rustc_span::{Span, Symbol, kw}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `cloned()` on an `Iterator` or `Option` where - /// `copied()` could be used instead. - /// - /// ### Why is this bad? - /// `copied()` is better because it guarantees that the type being cloned - /// implements `Copy`. - /// - /// ### Example - /// ```no_run - /// [1, 2, 3].iter().cloned(); - /// ``` - /// Use instead: - /// ```no_run - /// [1, 2, 3].iter().copied(); - /// ``` - #[clippy::version = "1.53.0"] - pub CLONED_INSTEAD_OF_COPIED, - pedantic, - "used `cloned` where `copied` could be used instead" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for consecutive calls to `str::replace` (2 or more) - /// that can be collapsed into a single call. - /// - /// ### Why is this bad? - /// Consecutive `str::replace` calls scan the string multiple times - /// with repetitive code. - /// - /// ### Example - /// ```no_run - /// let hello = "hesuo worpd" - /// .replace('s', "l") - /// .replace("u", "l") - /// .replace('p', "l"); - /// ``` - /// Use instead: - /// ```no_run - /// let hello = "hesuo worpd".replace(['s', 'u', 'p'], "l"); - /// ``` - #[clippy::version = "1.65.0"] - pub COLLAPSIBLE_STR_REPLACE, - perf, - "collapse consecutive calls to str::replace (2 or more) into a single call" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.cloned().()` where call to `.cloned()` can be postponed. - /// - /// ### Why is this bad? - /// It's often inefficient to clone all elements of an iterator, when eventually, only some - /// of them will be consumed. - /// - /// ### Known Problems - /// This `lint` removes the side of effect of cloning items in the iterator. - /// A code that relies on that side-effect could fail. - /// - /// ### Examples - /// ```no_run - /// # let vec = vec!["string".to_string()]; - /// vec.iter().cloned().take(10); - /// vec.iter().cloned().last(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let vec = vec!["string".to_string()]; - /// vec.iter().take(10).cloned(); - /// vec.iter().last().cloned(); - /// ``` - #[clippy::version = "1.60.0"] - pub ITER_OVEREAGER_CLONED, - perf, - "using `cloned()` early with `Iterator::iter()` can lead to some performance inefficiencies" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `Iterator::flat_map()` where `filter_map()` could be - /// used instead. - /// - /// ### Why is this bad? - /// `filter_map()` is known to always produce 0 or 1 output items per input item, - /// rather than however many the inner iterator type produces. - /// Therefore, it maintains the upper bound in `Iterator::size_hint()`, - /// and communicates to the reader that the input items are not being expanded into - /// multiple output items without their having to notice that the mapping function - /// returns an `Option`. - /// - /// ### Example - /// ```no_run - /// let nums: Vec = ["1", "2", "whee!"].iter().flat_map(|x| x.parse().ok()).collect(); - /// ``` - /// Use instead: - /// ```no_run - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.unwrap()` or `.unwrap_err()` calls on `Result`s and `.unwrap()` call on `Option`s. - /// - /// ### Why restrict this? - /// It is better to handle the `None` or `Err` case, - /// or at least call `.expect(_)` with a more helpful message. Still, for a lot of - /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is - /// `Allow` by default. - /// - /// `result.unwrap()` will let the thread panic on `Err` values. - /// Normally, you want to implement more sophisticated error handling, - /// and propagate errors upwards with `?` operator. - /// - /// Even if you want to panic on errors, not all `Error`s implement good - /// messages on display. Therefore, it may be beneficial to look at the places - /// where they may get displayed. Activate this lint to do just that. - /// - /// ### Examples - /// ```no_run - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option.unwrap(); - /// result.unwrap(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option.expect("more helpful message"); - /// result.expect("more helpful message"); - /// ``` - /// - /// If [expect_used](#expect_used) is enabled, instead: - /// ```rust,ignore - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option?; - /// - /// // or - /// - /// result?; - /// ``` - #[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()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.unwrap()` related calls on `Result`s and `Option`s that are constructed. - /// - /// ### Why is this bad? - /// It is better to write the value directly without the indirection. - /// - /// ### Examples - /// ```no_run - /// let val1 = Some(1).unwrap(); - /// let val2 = Ok::<_, ()>(1).unwrap(); - /// let val3 = Err::<(), _>(1).unwrap_err(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let val1 = 1; - /// let val2 = 1; - /// let val3 = 1; - /// ``` - #[clippy::version = "1.72.0"] - pub UNNECESSARY_LITERAL_UNWRAP, - complexity, - "using `unwrap()` related calls on `Result` and `Option` constructors" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.expect()` or `.expect_err()` calls on `Result`s and `.expect()` call on `Option`s. - /// - /// ### Why restrict this? - /// Usually it is better to handle the `None` or `Err` case. - /// Still, for a lot of quick-and-dirty code, `expect` is a good choice, which is why - /// this lint is `Allow` by default. - /// - /// `result.expect()` will let the thread panic on `Err` - /// values. Normally, you want to implement more sophisticated error handling, - /// and propagate errors upwards with `?` operator. - /// - /// ### Examples - /// ```rust,ignore - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option.expect("one"); - /// result.expect("one"); - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option?; - /// - /// // or - /// - /// result?; - /// ``` - #[clippy::version = "1.45.0"] - pub EXPECT_USED, - restriction, - "using `.expect()` on `Result` or `Option`, which might be better handled" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for methods that should live in a trait - /// implementation of a `std` trait (see [llogiq's blog - /// post](http://llogiq.github.io/2015/07/30/traits.html) for further - /// information) instead of an inherent implementation. - /// - /// ### Why is this bad? - /// Implementing the traits improve ergonomics for users of - /// the code, often with very little cost. Also people seeing a `mul(...)` - /// method - /// may expect `*` to work equally, so you should have good reason to disappoint - /// them. - /// - /// ### Example - /// ```no_run - /// struct X; - /// impl X { - /// fn add(&self, other: &X) -> X { - /// // .. - /// # X - /// } - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub SHOULD_IMPLEMENT_TRAIT, - style, - "defining a method that should be implementing a std trait" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for methods with certain name prefixes or suffixes, and which - /// do not adhere to standard conventions regarding how `self` is taken. - /// The actual rules are: - /// - /// |Prefix |Postfix |`self` taken | `self` type | - /// |-------|------------|-------------------------------|--------------| - /// |`as_` | none |`&self` or `&mut self` | any | - /// |`from_`| none | none | any | - /// |`into_`| none |`self` | any | - /// |`is_` | none |`&mut self` or `&self` or none | any | - /// |`to_` | `_mut` |`&mut self` | any | - /// |`to_` | not `_mut` |`self` | `Copy` | - /// |`to_` | not `_mut` |`&self` | not `Copy` | - /// - /// Note: Clippy doesn't trigger methods with `to_` prefix in: - /// - Traits definition. - /// Clippy can not tell if a type that implements a trait is `Copy` or not. - /// - Traits implementation, when `&self` is taken. - /// The method signature is controlled by the trait and often `&self` is required for all types that implement the trait - /// (see e.g. the `std::string::ToString` trait). - /// - /// Clippy allows `Pin<&Self>` and `Pin<&mut Self>` if `&self` and `&mut self` is required. - /// - /// Please find more info here: - /// https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv - /// - /// ### Why is this bad? - /// Consistency breeds readability. If you follow the - /// conventions, your users won't be surprised that they, e.g., need to supply a - /// mutable reference to a `as_..` function. - /// - /// ### Example - /// ```no_run - /// # struct X; - /// impl X { - /// fn as_str(self) -> &'static str { - /// // .. - /// # "" - /// } - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// # struct X; - /// impl X { - /// fn as_str(&self) -> &'static str { - /// // .. - /// # "" - /// } - /// } - /// ``` - #[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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `ok().expect(..)`. - /// - /// ### Why is this bad? - /// Because you usually call `expect()` on the `Result` - /// directly to get a better error message. - /// - /// ### Example - /// ```no_run - /// # let x = Ok::<_, ()>(()); - /// x.ok().expect("why did I do this again?"); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let x = Ok::<_, ()>(()); - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.err().expect()` calls on the `Result` type. - /// - /// ### Why is this bad? - /// `.expect_err()` can be called directly to avoid the extra type conversion from `err()`. - /// - /// ### Example - /// ```should_panic - /// let x: Result = Ok(10); - /// x.err().expect("Testing err().expect()"); - /// ``` - /// Use instead: - /// ```should_panic - /// let x: Result = Ok(10); - /// x.expect_err("Testing expect_err"); - /// ``` - #[clippy::version = "1.62.0"] - pub ERR_EXPECT, - style, - r#"using `.err().expect("")` when `.expect_err("")` can be used"# -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usages of the following functions with an argument that constructs a default value - /// (e.g., `Default::default` or `String::new`): - /// - `unwrap_or` - /// - `unwrap_or_else` - /// - `or_insert` - /// - `or_insert_with` - /// - /// ### Why is this bad? - /// Readability. Using `unwrap_or_default` in place of `unwrap_or`/`unwrap_or_else`, or `or_default` - /// in place of `or_insert`/`or_insert_with`, is simpler and more concise. - /// - /// ### Known problems - /// In some cases, the argument of `unwrap_or`, etc. is needed for type inference. The lint uses a - /// heuristic to try to identify such cases. However, the heuristic can produce false negatives. - /// - /// ### Examples - /// ```no_run - /// # let x = Some(1); - /// # let mut map = std::collections::HashMap::::new(); - /// x.unwrap_or(Default::default()); - /// map.entry(42).or_insert_with(String::new); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let x = Some(1); - /// # let mut map = std::collections::HashMap::::new(); - /// x.unwrap_or_default(); - /// map.entry(42).or_default(); - /// ``` - #[clippy::version = "1.56.0"] - pub UNWRAP_OR_DEFAULT, - style, - "using `.unwrap_or`, etc. with an argument that constructs a default value" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `option.map(_).unwrap_or(_)` or `option.map(_).unwrap_or_else(_)` or - /// `result.map(_).unwrap_or_else(_)`. - /// - /// ### Why is this bad? - /// Readability, these can be written more concisely (resp.) as - /// `option.map_or(_, _)`, `option.map_or_else(_, _)` and `result.map_or_else(_, _)`. - /// - /// ### Known problems - /// The order of the arguments is not in execution order - /// - /// ### Examples - /// ```no_run - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// # fn some_function(foo: ()) -> usize { 1 } - /// option.map(|a| a + 1).unwrap_or(0); - /// option.map(|a| a > 10).unwrap_or(false); - /// result.map(|a| a + 1).unwrap_or_else(some_function); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// # fn some_function(foo: ()) -> usize { 1 } - /// option.map_or(0, |a| a + 1); - /// option.is_some_and(|a| a > 10); - /// result.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)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.map_or(None, _)`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.and_then(_)`. - /// - /// ### Known problems - /// The order of the arguments is not in execution order. - /// - /// ### Example - /// ```no_run - /// # let opt = Some(1); - /// opt.map_or(None, |a| Some(a + 1)); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let opt = Some(1); - /// 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)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.map_or(None, Some)`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.ok()`. - /// - /// ### Example - /// ```no_run - /// # let r: Result = Ok(1); - /// assert_eq!(Some(1), r.map_or(None, Some)); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # 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()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` - /// or `_.or_else(|x| Err(y))`. - /// - /// ### Why is this bad? - /// This can be written more concisely as `_.map(|x| y)` or `_.map_err(|x| y)`. - /// - /// ### Example - /// ```no_run - /// # fn opt() -> Option<&'static str> { Some("42") } - /// # fn res() -> Result<&'static str, &'static str> { Ok("42") } - /// let _ = opt().and_then(|s| Some(s.len())); - /// let _ = res().and_then(|s| if s.len() == 42 { Ok(10) } else { Ok(20) }); - /// let _ = res().or_else(|s| if s.len() == 42 { Err(10) } else { Err(20) }); - /// ``` - /// - /// The correct use would be: - /// - /// ```no_run - /// # fn opt() -> Option<&'static str> { Some("42") } - /// # fn res() -> Result<&'static str, &'static str> { Ok("42") } - /// let _ = opt().map(|s| s.len()); - /// 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)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.filter(_).next()`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.find(_)`. - /// - /// ### Example - /// ```no_run - /// # let vec = vec![1]; - /// vec.iter().filter(|x| **x == 0).next(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # 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)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.skip_while(condition).next()`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.find(!condition)`. - /// - /// ### Example - /// ```no_run - /// # let vec = vec![1]; - /// vec.iter().skip_while(|x| **x == 0).next(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # 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)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.map(_).flatten(_)` on `Iterator` and `Option` - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.flat_map(_)` for `Iterator` or `_.and_then(_)` for `Option` - /// - /// ### Example - /// ```no_run - /// let vec = vec![vec![1]]; - /// let opt = Some(5); - /// - /// vec.iter().map(|x| x.iter()).flatten(); - /// opt.map(|x| Some(x * 2)).flatten(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let vec = vec![vec![1]]; - /// # let opt = Some(5); - /// vec.iter().flat_map(|x| x.iter()); - /// opt.and_then(|x| Some(x * 2)); - /// ``` - #[clippy::version = "1.31.0"] - pub MAP_FLATTEN, - complexity, - "using combinations of `flatten` and `map` which can usually be written as a single method call" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.filter(_).map(_)` that can be written more simply - /// as `filter_map(_)`. - /// - /// ### Why is this bad? - /// Redundant code in the `filter` and `map` operations is poor style and - /// less performant. - /// - /// ### Example - /// ```no_run - /// (0_i32..10) - /// .filter(|n| n.checked_add(1).is_some()) - /// .map(|n| n.checked_add(1).unwrap()); - /// ``` - /// - /// Use instead: - /// ```no_run - /// (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(_)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.find(_).map(_)` that can be written more simply - /// as `find_map(_)`. - /// - /// ### Why is this bad? - /// Redundant code in the `find` and `map` operations is poor style and - /// less performant. - /// - /// ### Example - /// ```no_run - /// (0_i32..10) - /// .find(|n| n.checked_add(1).is_some()) - /// .map(|n| n.checked_add(1).unwrap()); - /// ``` - /// - /// Use instead: - /// ```no_run - /// (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(_)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.filter_map(_).next()`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.find_map(_)`. - /// - /// ### Example - /// ```no_run - /// (0..3).filter_map(|x| if x == 2 { Some(x) } else { None }).next(); - /// ``` - /// Can be written as - /// - /// ```no_run - /// (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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `flat_map(|x| x)`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely by using `flatten`. - /// - /// ### Example - /// ```no_run - /// # let iter = vec![vec![0]].into_iter(); - /// iter.flat_map(|x| x); - /// ``` - /// Can be written as - /// ```no_run - /// # 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for an iterator or string search (such as `find()`, - /// `position()`, or `rposition()`) followed by a call to `is_some()` or `is_none()`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as: - /// * `_.any(_)`, or `_.contains(_)` for `is_some()`, - /// * `!_.any(_)`, or `!_.contains(_)` for `is_none()`. - /// - /// ### Example - /// ```no_run - /// let vec = vec![1]; - /// vec.iter().find(|x| **x == 0).is_some(); - /// - /// "hello world".find("world").is_none(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let vec = vec![1]; - /// vec.iter().any(|x| *x == 0); - /// - /// !"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()`)" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.chars().next()` on a `str` to check - /// if it starts with a given char. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.starts_with(_)`. - /// - /// ### Example - /// ```no_run - /// let name = "foo"; - /// if name.chars().next() == Some('_') {}; - /// ``` - /// - /// Use instead: - /// ```no_run - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`, - /// `.or_insert(foo(..))` etc., and suggests to use `.or_else(|| foo(..))`, - /// `.unwrap_or_else(|| foo(..))`, `.unwrap_or_default()` or `.or_default()` - /// etc. instead. - /// - /// ### Why is this bad? - /// The function will always be called. This is only bad if it allocates or - /// does some non-trivial amount of work. - /// - /// ### Known problems - /// If the function has side-effects, not calling it will change the - /// semantic of the program, but you shouldn't rely on that. - /// - /// The lint also cannot figure out whether the function you call is - /// actually expensive to call or not. - /// - /// ### Example - /// ```no_run - /// # let foo = Some(String::new()); - /// foo.unwrap_or(String::from("empty")); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let foo = Some(String::new()); - /// foo.unwrap_or_else(|| String::from("empty")); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub OR_FUN_CALL, - nursery, - "using any `*or` method with a function call, which suggests `*or_else`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.or(…).unwrap()` calls to Options and Results. - /// - /// ### Why is this bad? - /// You should use `.unwrap_or(…)` instead for clarity. - /// - /// ### Example - /// ```no_run - /// # let fallback = "fallback"; - /// // Result - /// # type Error = &'static str; - /// # let result: Result<&str, Error> = Err("error"); - /// let value = result.or::(Ok(fallback)).unwrap(); - /// - /// // Option - /// # let option: Option<&str> = None; - /// let value = option.or(Some(fallback)).unwrap(); - /// ``` - /// Use instead: - /// ```no_run - /// # let fallback = "fallback"; - /// // Result - /// # let result: Result<&str, &str> = Err("error"); - /// let value = result.unwrap_or(fallback); - /// - /// // Option - /// # let option: Option<&str> = None; - /// let value = option.unwrap_or(fallback); - /// ``` - #[clippy::version = "1.61.0"] - pub OR_THEN_UNWRAP, - complexity, - "checks for `.or(…).unwrap()` calls to Options and Results." -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`, - /// etc., and suggests to use `unwrap_or_else` instead - /// - /// ### Why is this bad? - /// The function will always be called. - /// - /// ### Known problems - /// If the function has side-effects, not calling it will - /// change the semantics of the program, but you shouldn't rely on that anyway. - /// - /// ### Example - /// ```no_run - /// # let foo = Some(String::new()); - /// # let err_code = "418"; - /// # let err_msg = "I'm a teapot"; - /// foo.expect(&format!("Err {}: {}", err_code, err_msg)); - /// - /// // or - /// - /// # let foo = Some(String::new()); - /// foo.expect(format!("Err {}: {}", err_code, err_msg).as_str()); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let foo = Some(String::new()); - /// # let err_code = "418"; - /// # 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.clone()` on a `Copy` type. - /// - /// ### Why is this bad? - /// The only reason `Copy` types implement `Clone` is for - /// generics, not for using the `clone` method on a concrete type. - /// - /// ### Example - /// ```no_run - /// 42u64.clone(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CLONE_ON_COPY, - complexity, - "using `clone` on a `Copy` type" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.clone()` on a ref-counted pointer, - /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified - /// function syntax instead (e.g., `Rc::clone(foo)`). - /// - /// ### Why restrict this? - /// Calling `.clone()` on an `Rc`, `Arc`, or `Weak` - /// can obscure the fact that only the pointer is being cloned, not the underlying - /// data. - /// - /// ### Example - /// ```no_run - /// # use std::rc::Rc; - /// let x = Rc::new(1); - /// - /// x.clone(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::rc::Rc; - /// # let x = Rc::new(1); - /// Rc::clone(&x); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CLONE_ON_REF_PTR, - restriction, - "using `clone` on a ref-counted pointer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.to_string()` on an `&&T` where - /// `T` implements `ToString` directly (like `&&str` or `&&String`). - /// - /// ### Why is this bad? - /// This bypasses the specialized implementation of - /// `ToString` and instead goes through the more expensive string formatting - /// facilities. - /// - /// ### Example - /// ```no_run - /// // Generic implementation for `T: Display` is used (slow) - /// ["foo", "bar"].iter().map(|s| s.to_string()); - /// - /// // 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`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `new` not returning a type that contains `Self`. - /// - /// ### Why is this bad? - /// As a convention, `new` methods are used to make a new - /// instance of a type. - /// - /// ### Example - /// In an impl block: - /// ```no_run - /// # struct Foo; - /// # struct NotAFoo; - /// impl Foo { - /// fn new() -> NotAFoo { - /// # NotAFoo - /// } - /// } - /// ``` - /// - /// ```no_run - /// # struct Foo; - /// struct Bar(Foo); - /// impl Foo { - /// // Bad. The type name must contain `Self` - /// fn new() -> Bar { - /// # Bar(Foo) - /// } - /// } - /// ``` - /// - /// ```no_run - /// # struct Foo; - /// # struct FooError; - /// impl Foo { - /// // Good. Return type contains `Self` - /// fn new() -> Result { - /// # Ok(Foo) - /// } - /// } - /// ``` - /// - /// Or in a trait definition: - /// ```no_run - /// pub trait Trait { - /// // Bad. The type name must contain `Self` - /// fn new(); - /// } - /// ``` - /// - /// ```no_run - /// pub trait Trait { - /// // Good. Return type contains `Self` - /// fn new() -> Self; - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub NEW_RET_NO_SELF, - style, - "not returning type containing `Self` in a `new` method" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calling `.step_by(0)` on iterators which panics. - /// - /// ### Why is this bad? - /// This very much looks like an oversight. Use `panic!()` instead if you - /// actually intend to panic. - /// - /// ### Example - /// ```rust,should_panic - /// for x in (0..100).step_by(0) { - /// //.. - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub ITERATOR_STEP_BY_ZERO, - correctness, - "using `Iterator::step_by(0)`, which will panic at runtime" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for iterators of `Option`s using `.filter(Option::is_some).map(Option::unwrap)` that may - /// be replaced with a `.flatten()` call. - /// - /// ### Why is this bad? - /// `Option` is like a collection of 0-1 things, so `flatten` - /// automatically does this without suspicious-looking `unwrap` calls. - /// - /// ### Example - /// ```no_run - /// let _ = std::iter::empty::>().filter(Option::is_some).map(Option::unwrap); - /// ``` - /// Use instead: - /// ```no_run - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the use of `iter.nth(0)`. - /// - /// ### Why is this bad? - /// `iter.next()` is equivalent to - /// `iter.nth(0)`, as they both consume the next element, - /// but is more readable. - /// - /// ### Example - /// ```no_run - /// # use std::collections::HashSet; - /// # let mut s = HashSet::new(); - /// # s.insert(1); - /// let x = s.iter().nth(0); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::collections::HashSet; - /// # let mut s = HashSet::new(); - /// # 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()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.iter().nth()`/`.iter_mut().nth()` on standard library types that have - /// equivalent `.get()`/`.get_mut()` methods. - /// - /// ### Why is this bad? - /// `.get()` and `.get_mut()` are equivalent but more concise. - /// - /// ### Example - /// ```no_run - /// let some_vec = vec![0, 1, 2, 3]; - /// let bad_vec = some_vec.iter().nth(3); - /// let bad_slice = &some_vec[..].iter().nth(3); - /// ``` - /// The correct use would be: - /// ```no_run - /// let some_vec = vec![0, 1, 2, 3]; - /// let bad_vec = some_vec.get(3); - /// let bad_slice = &some_vec[..].get(3); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub ITER_NTH, - style, - "using `.iter().nth()` on a standard library type with O(1) element access" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.skip(x).next()` on iterators. - /// - /// ### Why is this bad? - /// `.nth(x)` is cleaner - /// - /// ### Example - /// ```no_run - /// let some_vec = vec![0, 1, 2, 3]; - /// let bad_vec = some_vec.iter().skip(3).next(); - /// let bad_slice = &some_vec[..].iter().skip(3).next(); - /// ``` - /// The correct use would be: - /// ```no_run - /// let some_vec = vec![0, 1, 2, 3]; - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.drain(..)` on `Vec` and `VecDeque` for iteration. - /// - /// ### Why is this bad? - /// `.into_iter()` is simpler with better performance. - /// - /// ### Example - /// ```no_run - /// # use std::collections::HashSet; - /// let mut foo = vec![0, 1, 2, 3]; - /// let bar: HashSet = foo.drain(..).collect(); - /// ``` - /// Use instead: - /// ```no_run - /// # use std::collections::HashSet; - /// let foo = vec![0, 1, 2, 3]; - /// let bar: HashSet = foo.into_iter().collect(); - /// ``` - #[clippy::version = "1.61.0"] - pub ITER_WITH_DRAIN, - nursery, - "replace `.drain(..)` with `.into_iter()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `x.get(x.len() - 1)` instead of - /// `x.last()`. - /// - /// ### Why is this bad? - /// Using `x.last()` is easier to read and has the same - /// result. - /// - /// Note that using `x[x.len() - 1]` is semantically different from - /// `x.last()`. Indexing into the array will panic on out-of-bounds - /// accesses, while `x.get()` and `x.last()` will return `None`. - /// - /// There is another lint (get_unwrap) that covers the case of using - /// `x.get(index).unwrap()` instead of `x[index]`. - /// - /// ### Example - /// ```no_run - /// let x = vec![2, 3, 5]; - /// let last_element = x.get(x.len() - 1); - /// ``` - /// - /// Use instead: - /// ```no_run - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.get().unwrap()` (or - /// `.get_mut().unwrap`) on a standard library type which implements `Index` - /// - /// ### Why restrict this? - /// Using the Index trait (`[]`) is more clear and more - /// concise. - /// - /// ### Known problems - /// Not a replacement for error handling: Using either - /// `.unwrap()` or the Index trait (`[]`) carries the risk of causing a `panic` - /// if the value being accessed is `None`. If the use of `.get().unwrap()` is a - /// temporary placeholder for dealing with the `Option` type, then this does - /// not mitigate the need for error handling. If there is a chance that `.get()` - /// will be `None` in your program, then it is advisable that the `None` case - /// is handled in a future refactor instead of using `.unwrap()` or the Index - /// trait. - /// - /// ### Example - /// ```no_run - /// let mut some_vec = vec![0, 1, 2, 3]; - /// let last = some_vec.get(3).unwrap(); - /// *some_vec.get_mut(0).unwrap() = 1; - /// ``` - /// The correct use would be: - /// ```no_run - /// let mut some_vec = vec![0, 1, 2, 3]; - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for occurrences where one vector gets extended instead of append - /// - /// ### Why is this bad? - /// Using `append` instead of `extend` is more concise and faster - /// - /// ### Example - /// ```no_run - /// let mut a = vec![1, 2, 3]; - /// let mut b = vec![4, 5, 6]; - /// - /// a.extend(b.drain(..)); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let mut a = vec![1, 2, 3]; - /// let mut b = vec![4, 5, 6]; - /// - /// 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 vector to another" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the use of `.extend(s.chars())` where s is a - /// `&str` or `String`. - /// - /// ### Why is this bad? - /// `.push_str(s)` is clearer - /// - /// ### Example - /// ```no_run - /// let abc = "abc"; - /// let def = String::from("def"); - /// let mut s = String::new(); - /// s.extend(abc.chars()); - /// s.extend(def.chars()); - /// ``` - /// The correct use would be: - /// ```no_run - /// let abc = "abc"; - /// let def = String::from("def"); - /// let mut s = String::new(); - /// 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`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the use of `.cloned().collect()` on slice to - /// create a `Vec`. - /// - /// ### Why is this bad? - /// `.to_vec()` is clearer - /// - /// ### Example - /// ```no_run - /// let s = [1, 2, 3, 4, 5]; - /// let s2: Vec = s[..].iter().cloned().collect(); - /// ``` - /// The better use would be: - /// ```no_run - /// 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`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.chars().last()` or - /// `_.chars().next_back()` on a `str` to check if it ends with a given char. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.ends_with(_)`. - /// - /// ### Example - /// ```no_run - /// # let name = "_"; - /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-'); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let name = "_"; - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.as_ref()` or `.as_mut()` where the - /// types before and after the call are the same. - /// - /// ### Why is this bad? - /// The call is unnecessary. - /// - /// ### Example - /// ```no_run - /// # fn do_stuff(x: &[i32]) {} - /// let x: &[i32] = &[1, 2, 3, 4, 5]; - /// do_stuff(x.as_ref()); - /// ``` - /// The correct use would be: - /// ```no_run - /// # fn do_stuff(x: &[i32]) {} - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `fold` when a more succinct alternative exists. - /// Specifically, this checks for `fold`s which could be replaced by `any`, `all`, - /// `sum` or `product`. - /// - /// ### Why is this bad? - /// Readability. - /// - /// ### Example - /// ```no_run - /// (0..3).fold(false, |acc, x| acc || x > 2); - /// ``` - /// - /// Use instead: - /// ```no_run - /// (0..3).any(|x| x > 2); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub UNNECESSARY_FOLD, - style, - "using `fold` when a more succinct alternative exists" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `filter_map` calls that could be replaced by `filter` or `map`. - /// More specifically it checks if the closure provided is only performing one of the - /// filter or map operations and suggests the appropriate option. - /// - /// ### Why is this bad? - /// Complexity. The intent is also clearer if only a single - /// operation is being performed. - /// - /// ### Example - /// ```no_run - /// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None }); - /// - /// // As there is no transformation of the argument this could be written as: - /// let _ = (0..3).filter(|&x| x > 2); - /// ``` - /// - /// ```no_run - /// let _ = (0..4).filter_map(|x| Some(x + 1)); - /// - /// // 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `find_map` calls that could be replaced by `find` or `map`. More - /// specifically it checks if the closure provided is only performing one of the - /// find or map operations and suggests the appropriate option. - /// - /// ### Why is this bad? - /// Complexity. The intent is also clearer if only a single - /// operation is being performed. - /// - /// ### Example - /// ```no_run - /// let _ = (0..3).find_map(|x| if x > 2 { Some(x) } else { None }); - /// - /// // As there is no transformation of the argument this could be written as: - /// let _ = (0..3).find(|&x| x > 2); - /// ``` - /// - /// ```no_run - /// let _ = (0..4).find_map(|x| Some(x + 1)); - /// - /// // As there is no conditional check on the argument this could be written as: - /// let _ = (0..4).map(|x| x + 1).next(); - /// ``` - #[clippy::version = "1.61.0"] - pub UNNECESSARY_FIND_MAP, - complexity, - "using `find_map` when a more succinct alternative exists" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `into_iter` calls on references which should be replaced by `iter` - /// or `iter_mut`. - /// - /// ### Why is this bad? - /// Readability. Calling `into_iter` on a reference will not move out its - /// content into the resulting iterator, which is confusing. It is better just call `iter` or - /// `iter_mut` directly. - /// - /// ### Example - /// ```no_run - /// # let vec = vec![3, 4, 5]; - /// (&vec).into_iter(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let vec = vec![3, 4, 5]; - /// (&vec).iter(); - /// ``` - #[clippy::version = "1.32.0"] - pub INTO_ITER_ON_REF, - style, - "using `.into_iter()` on a reference" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `map` followed by a `count`. - /// - /// ### Why is this bad? - /// It looks suspicious. Maybe `map` was confused with `filter`. - /// If the `map` call is intentional, this should be rewritten - /// using `inspect`. Or, if you intend to drive the iterator to - /// completion, you can just use `for_each` instead. - /// - /// ### Example - /// ```no_run - /// let _ = (0..3).map(|x| x + 2).count(); - /// ``` - #[clippy::version = "1.39.0"] - pub SUSPICIOUS_MAP, - suspicious, - "suspicious usage of map" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `MaybeUninit::uninit().assume_init()`. - /// - /// ### Why is this bad? - /// For most types, this is undefined behavior. - /// - /// ### Known problems - /// For now, we accept empty tuples and tuples / arrays - /// of `MaybeUninit`. There may be other types that allow uninitialized - /// data, but those are not yet rigorously defined. - /// - /// ### Example - /// ```no_run - /// // Beware the UB - /// use std::mem::MaybeUninit; - /// - /// let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; - /// ``` - /// - /// Note that the following is OK: - /// - /// ```no_run - /// use std::mem::MaybeUninit; - /// - /// let _: [MaybeUninit; 5] = unsafe { - /// MaybeUninit::uninit().assume_init() - /// }; - /// ``` - #[clippy::version = "1.39.0"] - pub UNINIT_ASSUMED_INIT, - correctness, - "`MaybeUninit::uninit().assume_init()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`. - /// - /// ### Why is this bad? - /// These can be written simply with `saturating_add/sub` methods. - /// - /// ### Example - /// ```no_run - /// # let y: u32 = 0; - /// # let x: u32 = 100; - /// let add = x.checked_add(y).unwrap_or(u32::MAX); - /// let sub = x.checked_sub(y).unwrap_or(u32::MIN); - /// ``` - /// - /// can be written using dedicated methods for saturating addition/subtraction as: - /// - /// ```no_run - /// # let y: u32 = 0; - /// # let x: u32 = 100; - /// let add = x.saturating_add(y); - /// let sub = x.saturating_sub(y); - /// ``` - #[clippy::version = "1.39.0"] - pub MANUAL_SATURATING_ARITHMETIC, - style, - "`.checked_add/sub(x).unwrap_or(MAX/MIN)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `offset(_)`, `wrapping_`{`add`, `sub`}, etc. on raw pointers to - /// zero-sized types - /// - /// ### Why is this bad? - /// This is a no-op, and likely unintended - /// - /// ### Example - /// ```no_run - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `FileType::is_file()`. - /// - /// ### Why restrict this? - /// When people testing a file type with `FileType::is_file` - /// they are testing whether a path is something they can get bytes from. But - /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover - /// symlink in windows. Using `!FileType::is_dir()` is a better way to that intention. - /// - /// ### Example - /// ```no_run - /// # || { - /// let metadata = std::fs::metadata("foo.txt")?; - /// let filetype = metadata.file_type(); - /// - /// if filetype.is_file() { - /// // read file - /// } - /// # Ok::<_, std::io::Error>(()) - /// # }; - /// ``` - /// - /// should be written as: - /// - /// ```no_run - /// # || { - /// let metadata = std::fs::metadata("foo.txt")?; - /// let filetype = metadata.file_type(); - /// - /// if !filetype.is_dir() { - /// // read file - /// } - /// # 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.as_ref().map(Deref::deref)` or its aliases (such as String::as_str). - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.as_deref()`. - /// - /// ### Example - /// ```no_run - /// # let opt = Some("".to_string()); - /// opt.as_ref().map(String::as_str) - /// # ; - /// ``` - /// Can be written as - /// ```no_run - /// # let opt = Some("".to_string()); - /// 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()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `iter().next()` on a Slice or an Array - /// - /// ### Why is this bad? - /// These can be shortened into `.get()` - /// - /// ### Example - /// ```no_run - /// # let a = [1, 2, 3]; - /// # let b = vec![1, 2, 3]; - /// a[2..].iter().next(); - /// b.iter().next(); - /// ``` - /// should be written as: - /// ```no_run - /// # let a = [1, 2, 3]; - /// # let b = vec![1, 2, 3]; - /// 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()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Warns when using `push_str`/`insert_str` with a single-character string literal - /// where `push`/`insert` with a `char` would work fine. - /// - /// ### Why is this bad? - /// It's less clear that we are pushing a single character. - /// - /// ### Example - /// ```no_run - /// # let mut string = String::new(); - /// string.insert_str(0, "R"); - /// string.push_str("R"); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let mut string = String::new(); - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// As the counterpart to `or_fun_call`, this lint looks for unnecessary - /// lazily evaluated closures on `Option` and `Result`. - /// - /// This lint suggests changing the following functions, when eager evaluation results in - /// simpler code: - /// - `unwrap_or_else` to `unwrap_or` - /// - `and_then` to `and` - /// - `or_else` to `or` - /// - `get_or_insert_with` to `get_or_insert` - /// - `ok_or_else` to `ok_or` - /// - `then` to `then_some` (for msrv >= 1.62.0) - /// - /// ### Why is this bad? - /// Using eager evaluation is shorter and simpler in some cases. - /// - /// ### Known problems - /// It is possible, but not recommended for `Deref` and `Index` to have - /// side effects. Eagerly evaluating them can change the semantics of the program. - /// - /// ### Example - /// ```no_run - /// let opt: Option = None; - /// - /// opt.unwrap_or_else(|| 42); - /// ``` - /// Use instead: - /// ```no_run - /// let opt: Option = None; - /// - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `_.map(_).collect::()`. - /// - /// ### Why is this bad? - /// Using `try_for_each` instead is more readable and idiomatic. - /// - /// ### Example - /// ```no_run - /// (0..3).map(|t| Err(t)).collect::>(); - /// ``` - /// Use instead: - /// ```no_run - /// (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`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `from_iter()` function calls on types that implement the `FromIterator` - /// trait. - /// - /// ### Why is this bad? - /// If it's needed to create a collection from the contents of an iterator, the `Iterator::collect(_)` - /// method is preferred. However, when it's needed to specify the container type, - /// `Vec::from_iter(_)` can be more readable than using a turbofish (e.g. `_.collect::>()`). See - /// [FromIterator documentation](https://doc.rust-lang.org/std/iter/trait.FromIterator.html) - /// - /// ### Example - /// ```no_run - /// let five_fives = std::iter::repeat(5).take(5); - /// - /// let v = Vec::from_iter(five_fives); - /// - /// assert_eq!(v, vec![5, 5, 5, 5, 5]); - /// ``` - /// Use instead: - /// ```no_run - /// let five_fives = std::iter::repeat(5).take(5); - /// - /// let v: Vec = five_fives.collect(); - /// - /// assert_eq!(v, vec![5, 5, 5, 5, 5]); - /// ``` - /// but prefer to use - /// ```no_run - /// let numbers: Vec = FromIterator::from_iter(1..=5); - /// ``` - /// instead of - /// ```no_run - /// let numbers = (1..=5).collect::>(); - /// ``` - #[clippy::version = "1.49.0"] - pub FROM_ITER_INSTEAD_OF_COLLECT, - pedantic, - "use `.collect()` instead of `::from_iter()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `inspect().for_each()`. - /// - /// ### Why is this bad? - /// It is the same as performing the computation - /// inside `inspect` at the beginning of the closure in `for_each`. - /// - /// ### Example - /// ```no_run - /// [1,2,3,4,5].iter() - /// .inspect(|&x| println!("inspect the number: {}", x)) - /// .for_each(|&x| { - /// assert!(x >= 0); - /// }); - /// ``` - /// Can be written as - /// ```no_run - /// [1,2,3,4,5].iter() - /// .for_each(|&x| { - /// println!("inspect the number: {}", x); - /// assert!(x >= 0); - /// }); - /// ``` - #[clippy::version = "1.51.0"] - pub INSPECT_FOR_EACH, - complexity, - "using `.inspect().for_each()`, which can be replaced with `.for_each()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `filter_map(|x| x)`. - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely by using `flatten`. - /// - /// ### Example - /// ```no_run - /// # let iter = vec![Some(1)].into_iter(); - /// iter.filter_map(|x| x); - /// ``` - /// Use instead: - /// ```no_run - /// # 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for instances of `map(f)` where `f` is the identity function. - /// - /// ### Why is this bad? - /// It can be written more concisely without the call to `map`. - /// - /// ### Example - /// ```no_run - /// let x = [1, 2, 3]; - /// let y: Vec<_> = x.iter().map(|x| x).map(|x| 2*x).collect(); - /// ``` - /// Use instead: - /// ```no_run - /// let x = [1, 2, 3]; - /// let y: Vec<_> = x.iter().map(|x| 2*x).collect(); - /// ``` - #[clippy::version = "1.47.0"] - pub MAP_IDENTITY, - complexity, - "using iterator.map(|x| x)" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the use of `.bytes().nth()`. - /// - /// ### Why is this bad? - /// `.as_bytes().get()` is more efficient and more - /// readable. - /// - /// ### Example - /// ```no_run - /// "Hello".bytes().nth(3); - /// ``` - /// - /// Use instead: - /// ```no_run - /// "Hello".as_bytes().get(3); - /// ``` - #[clippy::version = "1.52.0"] - pub BYTES_NTH, - style, - "replace `.bytes().nth()` with `.as_bytes().get()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `_.to_owned()`, `vec.to_vec()`, or similar when calling `_.clone()` would be clearer. - /// - /// ### Why is this bad? - /// These methods do the same thing as `_.clone()` but may be confusing as - /// to why we are calling `to_vec` on something that is already a `Vec` or calling `to_owned` on something that is already owned. - /// - /// ### Example - /// ```no_run - /// let a = vec![1, 2, 3]; - /// let b = a.to_vec(); - /// let c = a.to_owned(); - /// ``` - /// Use instead: - /// ```no_run - /// let a = vec![1, 2, 3]; - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the use of `.iter().count()`. - /// - /// ### Why is this bad? - /// `.len()` is more efficient and more - /// readable. - /// - /// ### Example - /// ```no_run - /// let some_vec = vec![0, 1, 2, 3]; - /// - /// some_vec.iter().count(); - /// &some_vec[..].iter().count(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let some_vec = vec![0, 1, 2, 3]; - /// - /// some_vec.len(); - /// &some_vec[..].len(); - /// ``` - #[clippy::version = "1.52.0"] - pub ITER_COUNT, - complexity, - "replace `.iter().count()` with `.len()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `_.to_owned()`, on a `Cow<'_, _>`. - /// - /// ### Why is this bad? - /// Calling `to_owned()` on a `Cow` creates a clone of the `Cow` - /// itself, without taking ownership of the `Cow` contents (i.e. - /// it's equivalent to calling `Cow::clone`). - /// The similarly named `into_owned` method, on the other hand, - /// clones the `Cow` contents, effectively turning any `Cow::Borrowed` - /// into a `Cow::Owned`. - /// - /// Given the potential ambiguity, consider replacing `to_owned` - /// with `clone` for better readability or, if getting a `Cow::Owned` - /// was the original intent, using `into_owned` instead. - /// - /// ### Example - /// ```no_run - /// # use std::borrow::Cow; - /// let s = "Hello world!"; - /// let cow = Cow::Borrowed(s); - /// - /// let data = cow.to_owned(); - /// assert!(matches!(data, Cow::Borrowed(_))) - /// ``` - /// Use instead: - /// ```no_run - /// # use std::borrow::Cow; - /// let s = "Hello world!"; - /// let cow = Cow::Borrowed(s); - /// - /// let data = cow.clone(); - /// assert!(matches!(data, Cow::Borrowed(_))) - /// ``` - /// or - /// ```no_run - /// # use std::borrow::Cow; - /// let s = "Hello world!"; - /// let cow = Cow::Borrowed(s); - /// - /// let _data: String = cow.into_owned(); - /// ``` - #[clippy::version = "1.65.0"] - pub SUSPICIOUS_TO_OWNED, - suspicious, - "calls to `to_owned` on a `Cow<'_, _>` might not do what they are expected" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to [`splitn`] - /// (https://doc.rust-lang.org/std/primitive.str.html#method.splitn) and - /// related functions with either zero or one splits. - /// - /// ### Why is this bad? - /// These calls don't actually split the value and are - /// likely to be intended as a different number. - /// - /// ### Example - /// ```no_run - /// # let s = ""; - /// for x in s.splitn(1, ":") { - /// // .. - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let s = ""; - /// for x in s.splitn(2, ":") { - /// // .. - /// } - /// ``` - #[clippy::version = "1.54.0"] - pub SUSPICIOUS_SPLITN, - correctness, - "checks for `.splitn(0, ..)` and `.splitn(1, ..)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for manual implementations of `str::repeat` - /// - /// ### Why is this bad? - /// These are both harder to read, as well as less performant. - /// - /// ### Example - /// ```no_run - /// let x: String = std::iter::repeat('x').take(10).collect(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let x: String = "x".repeat(10); - /// ``` - #[clippy::version = "1.54.0"] - pub MANUAL_STR_REPEAT, - perf, - "manual implementation of `str::repeat`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `str::splitn(2, _)` - /// - /// ### Why is this bad? - /// `split_once` is both clearer in intent and slightly more efficient. - /// - /// ### Example - /// ```rust,ignore - /// let s = "key=value=add"; - /// let (key, value) = s.splitn(2, '=').next_tuple()?; - /// let value = s.splitn(2, '=').nth(1)?; - /// - /// let mut parts = s.splitn(2, '='); - /// let key = parts.next()?; - /// let value = parts.next()?; - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// let s = "key=value=add"; - /// let (key, value) = s.split_once('=')?; - /// let value = s.split_once('=')?.1; - /// - /// let (key, value) = s.split_once('=')?; - /// ``` - /// - /// ### Limitations - /// The multiple statement variant currently only detects `iter.next()?`/`iter.next().unwrap()` - /// in two separate `let` statements that immediately follow the `splitn()` - #[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 usage 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 - /// ```no_run - /// let str = "key=value=add"; - /// let _ = str.splitn(3, '=').next().unwrap(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let str = "key=value=add"; - /// let _ = str.split('=').next().unwrap(); - /// ``` - #[clippy::version = "1.59.0"] - pub NEEDLESS_SPLITN, - complexity, - "usages of `str::splitn` that can be replaced with `str::split`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for unnecessary calls to [`ToOwned::to_owned`](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned) - /// and other `to_owned`-like functions. - /// - /// ### Why is this bad? - /// The unnecessary calls result in useless allocations. - /// - /// ### Known problems - /// `unnecessary_to_owned` can falsely trigger if `IntoIterator::into_iter` is applied to an - /// owned copy of a resource and the resource is later used mutably. See - /// [#8148](https://github.com/rust-lang/rust-clippy/issues/8148). - /// - /// ### Example - /// ```no_run - /// let path = std::path::Path::new("x"); - /// foo(&path.to_string_lossy().to_string()); - /// fn foo(s: &str) {} - /// ``` - /// Use instead: - /// ```no_run - /// let path = std::path::Path::new("x"); - /// foo(&path.to_string_lossy()); - /// fn foo(s: &str) {} - /// ``` - #[clippy::version = "1.59.0"] - pub UNNECESSARY_TO_OWNED, - perf, - "unnecessary calls to `to_owned`-like functions" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.collect::>().join("")` on iterators. - /// - /// ### Why is this bad? - /// `.collect::()` is more concise and might be more performant - /// - /// ### Example - /// ```no_run - /// let vector = vec!["hello", "world"]; - /// let output = vector.iter().map(|item| item.to_uppercase()).collect::>().join(""); - /// println!("{}", output); - /// ``` - /// The correct use would be: - /// ```no_run - /// let vector = vec!["hello", "world"]; - /// let output = vector.iter().map(|item| item.to_uppercase()).collect::(); - /// println!("{}", output); - /// ``` - /// ### Known problems - /// While `.collect::()` is sometimes more performant, there are cases where - /// using `.collect::()` over `.collect::>().join("")` - /// will prevent loop unrolling and will result in a negative performance impact. - /// - /// Additionally, differences have been observed between aarch64 and x86_64 assembly output, - /// with aarch64 tending to producing faster assembly in more cases when using `.collect::()` - #[clippy::version = "1.61.0"] - pub UNNECESSARY_JOIN, - pedantic, - "using `.collect::>().join(\"\")` on an iterator" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for no-op uses of `Option::{as_deref, as_deref_mut}`, - /// for example, `Option<&T>::as_deref()` returns the same type. - /// - /// ### Why is this bad? - /// Redundant code and improving readability. - /// - /// ### Example - /// ```no_run - /// let a = Some(&1); - /// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32> - /// ``` - /// - /// Use instead: - /// ```no_run - /// 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`." -} - -declare_clippy_lint! { - /// ### What it does - /// Finds usages of [`char::is_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that - /// can be replaced with [`is_ascii_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or - /// [`is_ascii_hexdigit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit). - /// - /// ### Why is this bad? - /// `is_digit(..)` is slower and requires specifying the radix. - /// - /// ### Example - /// ```no_run - /// let c: char = '6'; - /// c.is_digit(10); - /// c.is_digit(16); - /// ``` - /// Use instead: - /// ```no_run - /// let c: char = '6'; - /// c.is_ascii_digit(); - /// c.is_ascii_hexdigit(); - /// ``` - #[clippy::version = "1.62.0"] - pub IS_DIGIT_ASCII_RADIX, - style, - "use of `char::is_digit(..)` with literal radix of 10 or 16" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calling `take` function after `as_ref`. - /// - /// ### Why is this bad? - /// Redundant code. `take` writes `None` to its argument. - /// In this case the modification is useless as it's a temporary that cannot be read from afterwards. - /// - /// ### Example - /// ```no_run - /// let x = Some(3); - /// x.as_ref().take(); - /// ``` - /// Use instead: - /// ```no_run - /// let x = Some(3); - /// x.as_ref(); - /// ``` - #[clippy::version = "1.62.0"] - pub NEEDLESS_OPTION_TAKE, - complexity, - "using `.as_ref().take()` on a temporary value" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `replace` statements which have no effect. - /// - /// ### Why is this bad? - /// It's either a mistake or confusing. - /// - /// ### Example - /// ```no_run - /// "1234".replace("12", "12"); - /// "1234".replacen("12", "12", 1); - /// ``` - #[clippy::version = "1.63.0"] - pub NO_EFFECT_REPLACE, - suspicious, - "replace with no effect" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for unnecessary method chains that can be simplified into `if .. else ..`. - /// - /// ### Why is this bad? - /// This can be written more clearly with `if .. else ..` - /// - /// ### Limitations - /// This lint currently only looks for usages of - /// `.{then, then_some}(..).{unwrap_or, unwrap_or_else, unwrap_or_default}(..)`, but will be expanded - /// to account for similar patterns. - /// - /// ### Example - /// ```no_run - /// let x = true; - /// x.then_some("a").unwrap_or("b"); - /// ``` - /// Use instead: - /// ```no_run - /// let x = true; - /// if x { "a" } else { "b" }; - /// ``` - #[clippy::version = "1.64.0"] - pub OBFUSCATED_IF_ELSE, - style, - "use of `.then_some(..).unwrap_or(..)` can be written \ - more clearly with `if .. else ..`" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for calls to `iter`, `iter_mut` or `into_iter` on collections containing a single item - /// - /// ### Why is this bad? - /// - /// It is simpler to use the once function from the standard library: - /// - /// ### Example - /// - /// ```no_run - /// let a = [123].iter(); - /// let b = Some(123).into_iter(); - /// ``` - /// Use instead: - /// ```no_run - /// use std::iter; - /// let a = iter::once(&123); - /// let b = iter::once(123); - /// ``` - /// - /// ### Known problems - /// - /// The type of the resulting iterator might become incompatible with its usage - #[clippy::version = "1.65.0"] - pub ITER_ON_SINGLE_ITEMS, - nursery, - "Iterator for array of length 1" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for calls to `iter`, `iter_mut` or `into_iter` on empty collections - /// - /// ### Why is this bad? - /// - /// It is simpler to use the empty function from the standard library: - /// - /// ### Example - /// - /// ```no_run - /// use std::{slice, option}; - /// let a: slice::Iter = [].iter(); - /// let f: option::IntoIter = None.into_iter(); - /// ``` - /// Use instead: - /// ```no_run - /// use std::iter; - /// let a: iter::Empty = iter::empty(); - /// let b: iter::Empty = iter::empty(); - /// ``` - /// - /// ### Known problems - /// - /// The type of the resulting iterator might become incompatible with its usage - #[clippy::version = "1.65.0"] - pub ITER_ON_EMPTY_COLLECTIONS, - nursery, - "Iterator for empty array" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for naive byte counts - /// - /// ### Why is this bad? - /// The [`bytecount`](https://crates.io/crates/bytecount) - /// crate has methods to count your bytes faster, especially for large slices. - /// - /// ### Known problems - /// If you have predominantly small slices, the - /// `bytecount::count(..)` method may actually be slower. However, if you can - /// ensure that less than 2³²-1 matches arise, the `naive_count_32(..)` can be - /// faster in those cases. - /// - /// ### Example - /// ```no_run - /// # let vec = vec![1_u8]; - /// let count = vec.iter().filter(|x| **x == 0u8).count(); - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// # let vec = vec![1_u8]; - /// let count = bytecount::count(&vec, 0u8); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub NAIVE_BYTECOUNT, - pedantic, - "use of naive `.filter(|&x| x == y).count()` to count byte values" -} - -declare_clippy_lint! { - /// ### What it does - /// It checks for `str::bytes().count()` and suggests replacing it with - /// `str::len()`. - /// - /// ### Why is this bad? - /// `str::bytes().count()` is longer and may not be as performant as using - /// `str::len()`. - /// - /// ### Example - /// ```no_run - /// "hello".bytes().count(); - /// String::from("hello").bytes().count(); - /// ``` - /// Use instead: - /// ```no_run - /// "hello".len(); - /// String::from("hello").len(); - /// ``` - #[clippy::version = "1.62.0"] - pub BYTES_COUNT_TO_LEN, - complexity, - "Using `bytes().count()` when `len()` performs the same functionality" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `ends_with` with possible file extensions - /// and suggests to use a case-insensitive approach instead. - /// - /// ### Why is this bad? - /// `ends_with` is case-sensitive and may not detect files with a valid extension. - /// - /// ### Example - /// ```no_run - /// fn is_rust_file(filename: &str) -> bool { - /// filename.ends_with(".rs") - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn is_rust_file(filename: &str) -> bool { - /// let filename = std::path::Path::new(filename); - /// filename.extension() - /// .map_or(false, |ext| ext.eq_ignore_ascii_case("rs")) - /// } - /// ``` - #[clippy::version = "1.51.0"] - pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, - pedantic, - "Checks for calls to ends_with with case-sensitive file extensions" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `x.get(0)` instead of - /// `x.first()` or `x.front()`. - /// - /// ### Why is this bad? - /// Using `x.first()` for `Vec`s and slices or `x.front()` - /// for `VecDeque`s is easier to read and has the same result. - /// - /// ### Example - /// ```no_run - /// let x = vec![2, 3, 5]; - /// let first_element = x.get(0); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let x = vec![2, 3, 5]; - /// let first_element = x.first(); - /// ``` - #[clippy::version = "1.63.0"] - pub GET_FIRST, - style, - "Using `x.get(0)` when `x.first()` or `x.front()` is simpler" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Finds patterns that reimplement `Option::ok_or`. - /// - /// ### Why is this bad? - /// - /// Concise code helps focusing on behavior instead of boilerplate. - /// - /// ### Examples - /// ```no_run - /// let foo: Option = None; - /// foo.map_or(Err("error"), |v| Ok(v)); - /// ``` - /// - /// Use instead: - /// ```no_run - /// let foo: Option = None; - /// foo.ok_or("error"); - /// ``` - #[clippy::version = "1.49.0"] - pub MANUAL_OK_OR, - style, - "finds patterns that can be encoded more concisely with `Option::ok_or`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `map(|x| x.clone())` or - /// dereferencing closures for `Copy` types, on `Iterator` or `Option`, - /// and suggests `cloned()` or `copied()` instead - /// - /// ### Why is this bad? - /// Readability, this can be written more concisely - /// - /// ### Example - /// ```no_run - /// let x = vec![42, 43]; - /// let y = x.iter(); - /// let z = y.map(|i| *i); - /// ``` - /// - /// The correct use would be: - /// - /// ```no_run - /// let x = vec![42, 43]; - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for instances of `map_err(|_| Some::Enum)` - /// - /// ### Why restrict this? - /// This `map_err` throws away the original error rather than allowing the enum to - /// contain and report the cause of the error. - /// - /// ### Example - /// Before: - /// ```no_run - /// use std::fmt; - /// - /// #[derive(Debug)] - /// enum Error { - /// Indivisible, - /// Remainder(u8), - /// } - /// - /// impl fmt::Display for Error { - /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - /// match self { - /// Error::Indivisible => write!(f, "could not divide input by three"), - /// Error::Remainder(remainder) => write!( - /// f, - /// "input is not divisible by three, remainder = {}", - /// remainder - /// ), - /// } - /// } - /// } - /// - /// impl std::error::Error for Error {} - /// - /// fn divisible_by_3(input: &str) -> Result<(), Error> { - /// input - /// .parse::() - /// .map_err(|_| Error::Indivisible) - /// .map(|v| v % 3) - /// .and_then(|remainder| { - /// if remainder == 0 { - /// Ok(()) - /// } else { - /// Err(Error::Remainder(remainder as u8)) - /// } - /// }) - /// } - /// ``` - /// - /// After: - /// ```rust - /// use std::{fmt, num::ParseIntError}; - /// - /// #[derive(Debug)] - /// enum Error { - /// Indivisible(ParseIntError), - /// Remainder(u8), - /// } - /// - /// impl fmt::Display for Error { - /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - /// match self { - /// Error::Indivisible(_) => write!(f, "could not divide input by three"), - /// Error::Remainder(remainder) => write!( - /// f, - /// "input is not divisible by three, remainder = {}", - /// remainder - /// ), - /// } - /// } - /// } - /// - /// impl std::error::Error for Error { - /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - /// match self { - /// Error::Indivisible(source) => Some(source), - /// _ => None, - /// } - /// } - /// } - /// - /// fn divisible_by_3(input: &str) -> Result<(), Error> { - /// input - /// .parse::() - /// .map_err(Error::Indivisible) - /// .map(|v| v % 3) - /// .and_then(|remainder| { - /// if remainder == 0 { - /// Ok(()) - /// } else { - /// Err(Error::Remainder(remainder as u8)) - /// } - /// }) - /// } - /// ``` - #[clippy::version = "1.48.0"] - pub MAP_ERR_IGNORE, - restriction, - "`map_err` should not ignore the original error" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `&mut Mutex::lock` calls - /// - /// ### Why is this bad? - /// `Mutex::lock` is less efficient than - /// calling `Mutex::get_mut`. In addition you also have a statically - /// guarantee that the mutex isn't locked, instead of just a runtime - /// guarantee. - /// - /// ### Example - /// ```no_run - /// use std::sync::{Arc, Mutex}; - /// - /// let mut value_rc = Arc::new(Mutex::new(42_u8)); - /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); - /// - /// let mut value = value_mutex.lock().unwrap(); - /// *value += 1; - /// ``` - /// Use instead: - /// ```no_run - /// use std::sync::{Arc, Mutex}; - /// - /// let mut value_rc = Arc::new(Mutex::new(42_u8)); - /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); - /// - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for duplicate open options as well as combinations - /// that make no sense. - /// - /// ### Why is this bad? - /// In the best case, the code will be harder to read than - /// necessary. I don't know the worst case. - /// - /// ### Example - /// ```no_run - /// use std::fs::OpenOptions; - /// - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the suspicious use of `OpenOptions::create()` - /// without an explicit `OpenOptions::truncate()`. - /// - /// ### Why is this bad? - /// `create()` alone will either create a new file or open an - /// existing file. If the file already exists, it will be - /// overwritten when written to, but the file will not be - /// truncated by default. - /// If less data is written to the file - /// than it already contains, the remainder of the file will - /// remain unchanged, and the end of the file will contain old - /// data. - /// In most cases, one should either use `create_new` to ensure - /// the file is created from scratch, or ensure `truncate` is - /// called so that the truncation behaviour is explicit. `truncate(true)` - /// will ensure the file is entirely overwritten with new data, whereas - /// `truncate(false)` will explicitly keep the default behavior. - /// - /// ### Example - /// ```rust,no_run - /// use std::fs::OpenOptions; - /// - /// OpenOptions::new().create(true); - /// ``` - /// Use instead: - /// ```rust,no_run - /// use std::fs::OpenOptions; - /// - /// OpenOptions::new().create(true).truncate(true); - /// ``` - #[clippy::version = "1.77.0"] - pub SUSPICIOUS_OPEN_OPTIONS, - suspicious, - "suspicious combination of options for opening a file" -} - -declare_clippy_lint! { - /// ### What it does - ///* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push) - /// calls on `PathBuf` that can cause overwrites. - /// - /// ### Why is this bad? - /// Calling `push` with a root path at the start can overwrite the - /// previous defined path. - /// - /// ### Example - /// ```no_run - /// use std::path::PathBuf; - /// - /// let mut x = PathBuf::from("/foo"); - /// x.push("/bar"); - /// assert_eq!(x, PathBuf::from("/bar")); - /// ``` - /// Could be written: - /// - /// ```no_run - /// use std::path::PathBuf; - /// - /// let mut x = PathBuf::from("/foo"); - /// 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for zipping a collection with the range of - /// `0.._.len()`. - /// - /// ### Why is this bad? - /// The code is better expressed with `.enumerate()`. - /// - /// ### Example - /// ```no_run - /// # let x = vec![1]; - /// let _ = x.iter().zip(0..x.len()); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let x = vec![1]; - /// let _ = x.iter().enumerate(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub RANGE_ZIP_WITH_LEN, - complexity, - "zipping iterator with a range when `enumerate()` would do" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.repeat(1)` and suggest the following method for each types. - /// - `.to_string()` for `str` - /// - `.clone()` for `String` - /// - `.to_vec()` for `slice` - /// - /// The lint will evaluate constant expressions and values as arguments of `.repeat(..)` and emit a message if - /// they are equivalent to `1`. (Related discussion in [rust-clippy#7306](https://github.com/rust-lang/rust-clippy/issues/7306)) - /// - /// ### Why is this bad? - /// For example, `String.repeat(1)` is equivalent to `.clone()`. If cloning - /// the string is the intention behind this, `clone()` should be used. - /// - /// ### Example - /// ```no_run - /// fn main() { - /// let x = String::from("hello world").repeat(1); - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn main() { - /// 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()` " -} - -declare_clippy_lint! { - /// ### What it does - /// When sorting primitive values (integers, bools, chars, as well - /// as arrays, slices, and tuples of such items), it is typically better to - /// use an unstable sort than a stable sort. - /// - /// ### Why is this bad? - /// Typically, using a stable sort consumes more memory and cpu cycles. - /// Because values which compare equal are identical, preserving their - /// relative order (the guarantee that a stable sort provides) means - /// nothing, while the extra costs still apply. - /// - /// ### Known problems - /// - /// As pointed out in - /// [issue #8241](https://github.com/rust-lang/rust-clippy/issues/8241), - /// a stable sort can instead be significantly faster for certain scenarios - /// (eg. when a sorted vector is extended with new data and resorted). - /// - /// For more information and benchmarking results, please refer to the - /// issue linked above. - /// - /// ### Example - /// ```no_run - /// let mut vec = vec![2, 1, 3]; - /// vec.sort(); - /// ``` - /// Use instead: - /// ```no_run - /// let mut vec = vec![2, 1, 3]; - /// vec.sort_unstable(); - /// ``` - #[clippy::version = "1.47.0"] - pub STABLE_SORT_PRIMITIVE, - pedantic, - "use of sort() when sort_unstable() is equivalent" -} - -declare_clippy_lint! { - /// ### What it does - /// Looks for calls to `.type_id()` on a `Box`. - /// - /// ### Why is this bad? - /// This almost certainly does not do what the user expects and can lead to subtle bugs. - /// Calling `.type_id()` on a `Box` returns a fixed `TypeId` of the `Box` itself, - /// rather than returning the `TypeId` of the underlying type behind the trait object. - /// - /// For `Box` specifically (and trait objects that have `Any` as its supertrait), - /// this lint will provide a suggestion, which is to dereference the receiver explicitly - /// to go from `Box` to `dyn Any`. - /// This makes sure that `.type_id()` resolves to a dynamic call on the trait object - /// and not on the box. - /// - /// If the fixed `TypeId` of the `Box` is the intended behavior, it's better to be explicit about it - /// and write `TypeId::of::>()`: - /// this makes it clear that a fixed `TypeId` is returned and not the `TypeId` of the implementor. - /// - /// ### Example - /// ```rust,ignore - /// use std::any::{Any, TypeId}; - /// - /// let any_box: Box = Box::new(42_i32); - /// assert_eq!(any_box.type_id(), TypeId::of::()); // ⚠️ this fails! - /// ``` - /// Use instead: - /// ```no_run - /// use std::any::{Any, TypeId}; - /// - /// let any_box: Box = Box::new(42_i32); - /// assert_eq!((*any_box).type_id(), TypeId::of::()); - /// // ^ dereference first, to call `type_id` on `dyn Any` - /// ``` - #[clippy::version = "1.73.0"] - pub TYPE_ID_ON_BOX, - suspicious, - "calling `.type_id()` on a boxed trait object" -} - -declare_clippy_lint! { - /// ### What it does - /// Detects `().hash(_)`. - /// - /// ### Why is this bad? - /// Hashing a unit value doesn't do anything as the implementation of `Hash` for `()` is a no-op. - /// - /// ### Example - /// ```no_run - /// # use std::hash::Hash; - /// # use std::collections::hash_map::DefaultHasher; - /// # enum Foo { Empty, WithValue(u8) } - /// # use Foo::*; - /// # let mut state = DefaultHasher::new(); - /// # let my_enum = Foo::Empty; - /// match my_enum { - /// Empty => ().hash(&mut state), - /// WithValue(x) => x.hash(&mut state), - /// } - /// ``` - /// Use instead: - /// ```no_run - /// # use std::hash::Hash; - /// # use std::collections::hash_map::DefaultHasher; - /// # enum Foo { Empty, WithValue(u8) } - /// # use Foo::*; - /// # let mut state = DefaultHasher::new(); - /// # let my_enum = Foo::Empty; - /// match my_enum { - /// Empty => 0_u8.hash(&mut state), - /// WithValue(x) => x.hash(&mut state), - /// } - /// ``` - #[clippy::version = "1.58.0"] - pub UNIT_HASH, - correctness, - "hashing a unit value, which does nothing" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `Vec::sort_by` passing in a closure - /// which compares the two arguments, either directly or indirectly. - /// - /// ### Why is this bad? - /// It is more clear to use `Vec::sort_by_key` (or `Vec::sort` if - /// possible) than to use `Vec::sort_by` and a more complicated - /// closure. - /// - /// ### Known problems - /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't already - /// imported by a use statement, then it will need to be added manually. - /// - /// ### Example - /// ```no_run - /// # struct A; - /// # impl A { fn foo(&self) {} } - /// # let mut vec: Vec = Vec::new(); - /// vec.sort_by(|a, b| a.foo().cmp(&b.foo())); - /// ``` - /// Use instead: - /// ```no_run - /// # struct A; - /// # impl A { fn foo(&self) {} } - /// # 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" -} - -declare_clippy_lint! { - /// ### What it does - /// Finds occurrences of `Vec::resize(0, an_int)` - /// - /// ### Why is this bad? - /// This is probably an argument inversion mistake. - /// - /// ### Example - /// ```no_run - /// vec![1, 2, 3, 4, 5].resize(0, 5) - /// ``` - /// - /// Use instead: - /// ```no_run - /// vec![1, 2, 3, 4, 5].clear() - /// ``` - #[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" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of File::read_to_end and File::read_to_string. - /// - /// ### Why restrict this? - /// `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. - /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) - /// - /// ### Example - /// ```rust,no_run - /// # use std::io::Read; - /// # use std::fs::File; - /// let mut f = File::open("foo.txt").unwrap(); - /// let mut bytes = Vec::new(); - /// f.read_to_end(&mut bytes).unwrap(); - /// ``` - /// Can be written more concisely as - /// ```rust,no_run - /// # 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`" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for iterating a map (`HashMap` or `BTreeMap`) and - /// ignoring either the keys or values. - /// - /// ### Why is this bad? - /// - /// Readability. There are `keys` and `values` methods that - /// can be used to express that we only need the keys or the values. - /// - /// ### Example - /// - /// ```no_run - /// # use std::collections::HashMap; - /// let map: HashMap = HashMap::new(); - /// let values = map.iter().map(|(_, value)| value).collect::>(); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::collections::HashMap; - /// let map: HashMap = HashMap::new(); - /// let values = map.values().collect::>(); - /// ``` - #[clippy::version = "1.66.0"] - pub ITER_KV_MAP, - complexity, - "iterating on map using `iter` when `keys` or `values` would do" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks if the `seek` method of the `Seek` trait is called with `SeekFrom::Current(0)`, - /// and if it is, suggests using `stream_position` instead. - /// - /// ### Why is this bad? - /// - /// Readability. Use dedicated method. - /// - /// ### Example - /// - /// ```rust,no_run - /// use std::fs::File; - /// use std::io::{self, Write, Seek, SeekFrom}; - /// - /// fn main() -> io::Result<()> { - /// let mut f = File::create("foo.txt")?; - /// f.write_all(b"Hello")?; - /// eprintln!("Written {} bytes", f.seek(SeekFrom::Current(0))?); - /// - /// Ok(()) - /// } - /// ``` - /// Use instead: - /// ```rust,no_run - /// use std::fs::File; - /// use std::io::{self, Write, Seek, SeekFrom}; - /// - /// fn main() -> io::Result<()> { - /// let mut f = File::create("foo.txt")?; - /// f.write_all(b"Hello")?; - /// eprintln!("Written {} bytes", f.stream_position()?); - /// - /// Ok(()) - /// } - /// ``` - #[clippy::version = "1.67.0"] - pub SEEK_FROM_CURRENT, - complexity, - "use dedicated method for seek from current position" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for jumps to the start of a stream that implements `Seek` - /// and uses the `seek` method providing `Start` as parameter. - /// - /// ### Why is this bad? - /// - /// Readability. There is a specific method that was implemented for - /// this exact scenario. - /// - /// ### Example - /// ```no_run - /// # use std::io; - /// fn foo(t: &mut T) { - /// t.seek(io::SeekFrom::Start(0)); - /// } - /// ``` - /// Use instead: - /// ```no_run - /// # use std::io; - /// fn foo(t: &mut T) { - /// t.rewind(); - /// } - /// ``` - #[clippy::version = "1.67.0"] - pub SEEK_TO_START_INSTEAD_OF_REWIND, - complexity, - "jumping to the start of stream using `seek` method" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for functions collecting an iterator when collect - /// is not needed. - /// - /// ### Why is this bad? - /// `collect` causes the allocation of a new data structure, - /// when this allocation may not be needed. - /// - /// ### Example - /// ```no_run - /// # let iterator = vec![1].into_iter(); - /// let len = iterator.collect::>().len(); - /// ``` - /// Use instead: - /// ```no_run - /// # let iterator = vec![1].into_iter(); - /// let len = iterator.count(); - /// ``` - #[clippy::version = "1.30.0"] - pub NEEDLESS_COLLECT, - nursery, - "collecting an iterator when collect is not needed" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for `Command::arg()` invocations that look like they - /// should be multiple arguments instead, such as `arg("-t ext2")`. - /// - /// ### Why is this bad? - /// - /// `Command::arg()` does not split arguments by space. An argument like `arg("-t ext2")` - /// will be passed as a single argument to the command, - /// which is likely not what was intended. - /// - /// ### Example - /// ```no_run - /// std::process::Command::new("echo").arg("-n hello").spawn().unwrap(); - /// ``` - /// Use instead: - /// ```no_run - /// std::process::Command::new("echo").args(["-n", "hello"]).spawn().unwrap(); - /// ``` - #[clippy::version = "1.69.0"] - pub SUSPICIOUS_COMMAND_ARG_SPACE, - suspicious, - "single command line argument that looks like it should be multiple arguments" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.drain(..)` for the sole purpose of clearing a container. - /// - /// ### Why is this bad? - /// This creates an unnecessary iterator that is dropped immediately. - /// - /// Calling `.clear()` also makes the intent clearer. - /// - /// ### Example - /// ```no_run - /// let mut v = vec![1, 2, 3]; - /// v.drain(..); - /// ``` - /// Use instead: - /// ```no_run - /// let mut v = vec![1, 2, 3]; - /// v.clear(); - /// ``` - #[clippy::version = "1.70.0"] - pub CLEAR_WITH_DRAIN, - nursery, - "calling `drain` in order to `clear` a container" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.rev().next()` on a `DoubleEndedIterator` - /// - /// ### Why is this bad? - /// `.next_back()` is cleaner. - /// - /// ### Example - /// ```no_run - /// # let foo = [0; 10]; - /// foo.iter().rev().next(); - /// ``` - /// Use instead: - /// ```no_run - /// # let foo = [0; 10]; - /// foo.iter().next_back(); - /// ``` - #[clippy::version = "1.71.0"] - pub MANUAL_NEXT_BACK, - style, - "manual reverse iteration of `DoubleEndedIterator`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `.drain()` that clear the collection, immediately followed by a call to `.collect()`. - /// - /// > "Collection" in this context refers to any type with a `drain` method: - /// > `Vec`, `VecDeque`, `BinaryHeap`, `HashSet`,`HashMap`, `String` - /// - /// ### Why is this bad? - /// Using `mem::take` is faster as it avoids the allocation. - /// When using `mem::take`, the old collection is replaced with an empty one and ownership of - /// the old collection is returned. - /// - /// ### Known issues - /// `mem::take(&mut vec)` is almost equivalent to `vec.drain(..).collect()`, except that - /// it also moves the **capacity**. The user might have explicitly written it this way - /// to keep the capacity on the original `Vec`. - /// - /// ### Example - /// ```no_run - /// fn remove_all(v: &mut Vec) -> Vec { - /// v.drain(..).collect() - /// } - /// ``` - /// Use instead: - /// ```no_run - /// use std::mem; - /// fn remove_all(v: &mut Vec) -> Vec { - /// mem::take(v) - /// } - /// ``` - #[clippy::version = "1.72.0"] - pub DRAIN_COLLECT, - perf, - "calling `.drain(..).collect()` to move all elements into a new collection" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `Iterator::fold` with a type that implements `Try`. - /// - /// ### Why is this bad? - /// The code should use `try_fold` instead, which short-circuits on failure, thus opening the - /// door for additional optimizations not possible with `fold` as rustc can guarantee the - /// function is never called on `None`, `Err`, etc., alleviating otherwise necessary checks. It's - /// also slightly more idiomatic. - /// - /// ### Known issues - /// This lint doesn't take into account whether a function does something on the failure case, - /// i.e., whether short-circuiting will affect behavior. Refactoring to `try_fold` is not - /// desirable in those cases. - /// - /// ### Example - /// ```no_run - /// vec![1, 2, 3].iter().fold(Some(0i32), |sum, i| sum?.checked_add(*i)); - /// ``` - /// Use instead: - /// ```no_run - /// vec![1, 2, 3].iter().try_fold(0i32, |sum, i| sum.checked_add(*i)); - /// ``` - #[clippy::version = "1.72.0"] - pub MANUAL_TRY_FOLD, - perf, - "checks for usage of `Iterator::fold` with a type that implements `Try`" -} - -declare_clippy_lint! { - /// ### What it does - /// Looks for calls to [`Stdin::read_line`] to read a line from the standard input - /// into a string, then later attempting to use that string for an operation that will never - /// work for strings with a trailing newline character in it (e.g. parsing into a `i32`). - /// - /// ### Why is this bad? - /// The operation will always fail at runtime no matter what the user enters, thus - /// making it a useless operation. - /// - /// ### Example - /// ```rust,ignore - /// let mut input = String::new(); - /// std::io::stdin().read_line(&mut input).expect("Failed to read a line"); - /// let num: i32 = input.parse().expect("Not a number!"); - /// assert_eq!(num, 42); // we never even get here! - /// ``` - /// Use instead: - /// ```rust,ignore - /// let mut input = String::new(); - /// std::io::stdin().read_line(&mut input).expect("Failed to read a line"); - /// let num: i32 = input.trim_end().parse().expect("Not a number!"); - /// // ^^^^^^^^^^^ remove the trailing newline - /// assert_eq!(num, 42); - /// ``` - #[clippy::version = "1.73.0"] - pub READ_LINE_WITHOUT_TRIM, - correctness, - "calling `Stdin::read_line`, then trying to parse it without first trimming" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `.chars().any(|i| i == c)`. - /// - /// ### Why is this bad? - /// It's significantly slower than using a pattern instead, like - /// `matches!(c, '\\' | '.' | '+')`. - /// - /// Despite this being faster, this is not `perf` as this is pretty common, and is a rather nice - /// way to check if a `char` is any in a set. In any case, this `restriction` lint is available - /// for situations where that additional performance is absolutely necessary. - /// - /// ### Example - /// ```no_run - /// # let c = 'c'; - /// "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); - /// ``` - /// Use instead: - /// ```no_run - /// # let c = 'c'; - /// matches!(c, '\\' | '.' | '+' | '*' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); - /// ``` - #[clippy::version = "1.73.0"] - pub STRING_LIT_CHARS_ANY, - restriction, - "checks for `.chars().any(|i| i == c)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.map(|_| format!(..)).collect::()`. - /// - /// ### Why is this bad? - /// This allocates a new string for every element in the iterator. - /// This can be done more efficiently by creating the `String` once and appending to it in `Iterator::fold`, - /// using either the `write!` macro which supports exactly the same syntax as the `format!` macro, - /// or concatenating with `+` in case the iterator yields `&str`/`String`. - /// - /// Note also that `write!`-ing into a `String` can never fail, despite the return type of `write!` being `std::fmt::Result`, - /// so it can be safely ignored or unwrapped. - /// - /// ### Example - /// ```no_run - /// fn hex_encode(bytes: &[u8]) -> String { - /// bytes.iter().map(|b| format!("{b:02X}")).collect() - /// } - /// ``` - /// Use instead: - /// ```no_run - /// use std::fmt::Write; - /// fn hex_encode(bytes: &[u8]) -> String { - /// bytes.iter().fold(String::new(), |mut output, b| { - /// let _ = write!(output, "{b:02X}"); - /// output - /// }) - /// } - /// ``` - #[clippy::version = "1.73.0"] - pub FORMAT_COLLECT, - pedantic, - "`format!`ing every element in a collection, then collecting the strings into a new `String`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.skip(0)` on iterators. - /// - /// ### Why is this bad? - /// This was likely intended to be `.skip(1)` to skip the first element, as `.skip(0)` does - /// nothing. If not, the call should be removed. - /// - /// ### Example - /// ```no_run - /// let v = vec![1, 2, 3]; - /// let x = v.iter().skip(0).collect::>(); - /// let y = v.iter().collect::>(); - /// assert_eq!(x, y); - /// ``` - #[clippy::version = "1.73.0"] - pub ITER_SKIP_ZERO, - correctness, - "disallows `.skip(0)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `bool::then` in `Iterator::filter_map`. - /// - /// ### Why is this bad? - /// This can be written with `filter` then `map` instead, which would reduce nesting and - /// separates the filtering from the transformation phase. This comes with no cost to - /// performance and is just cleaner. - /// - /// ### Limitations - /// Does not lint `bool::then_some`, as it eagerly evaluates its arguments rather than lazily. - /// This can create differing behavior, so better safe than sorry. - /// - /// ### Example - /// ```no_run - /// # fn really_expensive_fn(i: i32) -> i32 { i } - /// # let v = vec![]; - /// _ = v.into_iter().filter_map(|i| (i % 2 == 0).then(|| really_expensive_fn(i))); - /// ``` - /// Use instead: - /// ```no_run - /// # fn really_expensive_fn(i: i32) -> i32 { i } - /// # let v = vec![]; - /// _ = v.into_iter().filter(|i| i % 2 == 0).map(|i| really_expensive_fn(i)); - /// ``` - #[clippy::version = "1.73.0"] - pub FILTER_MAP_BOOL_THEN, - style, - "checks for usage of `bool::then` in `Iterator::filter_map`" -} - -declare_clippy_lint! { - /// ### What it does - /// Looks for calls to `RwLock::write` where the lock is only used for reading. - /// - /// ### Why is this bad? - /// The write portion of `RwLock` is exclusive, meaning that no other thread - /// can access the lock while this writer is active. - /// - /// ### Example - /// ```no_run - /// use std::sync::RwLock; - /// fn assert_is_zero(lock: &RwLock) { - /// let num = lock.write().unwrap(); - /// assert_eq!(*num, 0); - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// use std::sync::RwLock; - /// fn assert_is_zero(lock: &RwLock) { - /// let num = lock.read().unwrap(); - /// assert_eq!(*num, 0); - /// } - /// ``` - #[clippy::version = "1.73.0"] - pub READONLY_WRITE_LOCK, - perf, - "acquiring a write lock when a read lock would work" -} - -declare_clippy_lint! { - /// ### What it does - /// Looks for iterator combinator calls such as `.take(x)` or `.skip(x)` - /// where `x` is greater than the amount of items that an iterator will produce. - /// - /// ### Why is this bad? - /// Taking or skipping more items than there are in an iterator either creates an iterator - /// with all items from the original iterator or an iterator with no items at all. - /// This is most likely not what the user intended to do. - /// - /// ### Example - /// ```no_run - /// for _ in [1, 2, 3].iter().take(4) {} - /// ``` - /// Use instead: - /// ```no_run - /// for _ in [1, 2, 3].iter() {} - /// ``` - #[clippy::version = "1.74.0"] - pub ITER_OUT_OF_BOUNDS, - suspicious, - "calls to `.take()` or `.skip()` that are out of bounds" -} - -declare_clippy_lint! { - /// ### What it does - /// Looks for calls to `Path::ends_with` calls where the argument looks like a file extension. - /// - /// By default, Clippy has a short list of known filenames that start with a dot - /// but aren't necessarily file extensions (e.g. the `.git` folder), which are allowed by default. - /// The `allowed-dotfiles` configuration can be used to allow additional - /// file extensions that Clippy should not lint. - /// - /// ### Why is this bad? - /// This doesn't actually compare file extensions. Rather, `ends_with` compares the given argument - /// to the last **component** of the path and checks if it matches exactly. - /// - /// ### Known issues - /// File extensions are often at most three characters long, so this only lints in those cases - /// in an attempt to avoid false positives. - /// Any extension names longer than that are assumed to likely be real path components and are - /// therefore ignored. - /// - /// ### Example - /// ```no_run - /// # use std::path::Path; - /// fn is_markdown(path: &Path) -> bool { - /// path.ends_with(".md") - /// } - /// ``` - /// Use instead: - /// ```no_run - /// # use std::path::Path; - /// fn is_markdown(path: &Path) -> bool { - /// path.extension().is_some_and(|ext| ext == "md") - /// } - /// ``` - #[clippy::version = "1.74.0"] - pub PATH_ENDS_WITH_EXT, - suspicious, - "attempting to compare file extensions using `Path::ends_with`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `as_str()` on a `String` chained with a method available on the `String` itself. - /// - /// ### Why is this bad? - /// The `as_str()` conversion is pointless and can be removed for simplicity and cleanliness. - /// - /// ### Example - /// ```no_run - /// let owned_string = "This is a string".to_owned(); - /// owned_string.as_str().as_bytes() - /// # ; - /// ``` - /// - /// Use instead: - /// ```no_run - /// let owned_string = "This is a string".to_owned(); - /// owned_string.as_bytes() - /// # ; - /// ``` - #[clippy::version = "1.74.0"] - pub REDUNDANT_AS_STR, - complexity, - "`as_str` used to call a method on `str` that is also available on `String`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `waker.clone().wake()` - /// - /// ### Why is this bad? - /// Cloning the waker is not necessary, `wake_by_ref()` enables the same operation - /// without extra cloning/dropping. - /// - /// ### Example - /// ```rust,ignore - /// waker.clone().wake(); - /// ``` - /// Should be written - /// ```rust,ignore - /// waker.wake_by_ref(); - /// ``` - #[clippy::version = "1.75.0"] - pub WAKER_CLONE_WAKE, - perf, - "cloning a `Waker` only to wake it" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `TryInto::try_into` and `TryFrom::try_from` when their infallible counterparts - /// could be used. - /// - /// ### Why is this bad? - /// In those cases, the `TryInto` and `TryFrom` trait implementation is a blanket impl that forwards - /// to `Into` or `From`, which always succeeds. - /// The returned `Result<_, Infallible>` requires error handling to get the contained value - /// even though the conversion can never fail. - /// - /// ### Example - /// ```rust - /// let _: Result = 1i32.try_into(); - /// let _: Result = <_>::try_from(1i32); - /// ``` - /// Use `from`/`into` instead: - /// ```rust - /// let _: i64 = 1i32.into(); - /// let _: i64 = <_>::from(1i32); - /// ``` - #[clippy::version = "1.75.0"] - pub UNNECESSARY_FALLIBLE_CONVERSIONS, - style, - "calling the `try_from` and `try_into` trait methods when `From`/`Into` is implemented" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `Path::join` that start with a path separator (`\\` or `/`). - /// - /// ### Why is this bad? - /// If the argument to `Path::join` starts with a separator, it will overwrite - /// the original path. If this is intentional, prefer using `Path::new` instead. - /// - /// Note the behavior is platform dependent. A leading `\\` will be accepted - /// on unix systems as part of the file name - /// - /// See [`Path::join`](https://doc.rust-lang.org/std/path/struct.Path.html#method.join) - /// - /// ### Example - /// ```rust - /// # use std::path::{Path, PathBuf}; - /// let path = Path::new("/bin"); - /// let joined_path = path.join("/sh"); - /// assert_eq!(joined_path, PathBuf::from("/sh")); - /// ``` - /// - /// Use instead; - /// ```rust - /// # use std::path::{Path, PathBuf}; - /// let path = Path::new("/bin"); - /// - /// // If this was unintentional, remove the leading separator - /// let joined_path = path.join("sh"); - /// assert_eq!(joined_path, PathBuf::from("/bin/sh")); - /// - /// // If this was intentional, create a new path instead - /// let new = Path::new("/sh"); - /// assert_eq!(new, PathBuf::from("/sh")); - /// ``` - #[clippy::version = "1.76.0"] - pub JOIN_ABSOLUTE_PATHS, - suspicious, - "calls to `Path::join` which will overwrite the original path" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for iterators of `Result`s using `.filter(Result::is_ok).map(Result::unwrap)` that may - /// be replaced with a `.flatten()` call. - /// - /// ### Why is this bad? - /// `Result` implements `IntoIterator`. This means that `Result` can be flattened - /// automatically without suspicious-looking `unwrap` calls. - /// - /// ### Example - /// ```no_run - /// let _ = std::iter::empty::>().filter(Result::is_ok).map(Result::unwrap); - /// ``` - /// Use instead: - /// ```no_run - /// let _ = std::iter::empty::>().flatten(); - /// ``` - #[clippy::version = "1.77.0"] - pub RESULT_FILTER_MAP, - complexity, - "filtering `Result` for `Ok` then force-unwrapping, which can be one type-safe operation" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.filter(Option::is_some)` that may be replaced with a `.flatten()` call. - /// This lint will require additional changes to the follow-up calls as it affects the type. - /// - /// ### Why is this bad? - /// This pattern is often followed by manual unwrapping of the `Option`. The simplification - /// results in more readable and succinct code without the need for manual unwrapping. - /// - /// ### Example - /// ```no_run - /// vec![Some(1)].into_iter().filter(Option::is_some); - /// - /// ``` - /// Use instead: - /// ```no_run - /// vec![Some(1)].into_iter().flatten(); - /// ``` - #[clippy::version = "1.77.0"] - pub ITER_FILTER_IS_SOME, - pedantic, - "filtering an iterator over `Option`s for `Some` can be achieved with `flatten`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.filter(Result::is_ok)` that may be replaced with a `.flatten()` call. - /// This lint will require additional changes to the follow-up calls as it affects the type. - /// - /// ### Why is this bad? - /// This pattern is often followed by manual unwrapping of `Result`. The simplification - /// results in more readable and succinct code without the need for manual unwrapping. - /// - /// ### Example - /// ```no_run - /// vec![Ok::(1)].into_iter().filter(Result::is_ok); - /// - /// ``` - /// Use instead: - /// ```no_run - /// vec![Ok::(1)].into_iter().flatten(); - /// ``` - #[clippy::version = "1.77.0"] - pub ITER_FILTER_IS_OK, - pedantic, - "filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type. - /// - /// ### Why is this bad? - /// Readability. These can be written more concisely as `option.is_some_and(f)` and `result.is_ok_and(f)`. - /// - /// ### Example - /// ```no_run - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option.map(|a| a > 10).unwrap_or_default(); - /// result.map(|a| a > 10).unwrap_or_default(); - /// ``` - /// Use instead: - /// ```no_run - /// # let option = Some(1); - /// # let result: Result = Ok(1); - /// option.is_some_and(|a| a > 10); - /// result.is_ok_and(|a| a > 10); - /// ``` - #[clippy::version = "1.77.0"] - pub MANUAL_IS_VARIANT_AND, - pedantic, - "using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for usages of `str.trim().split("\n")` and `str.trim().split("\r\n")`. - /// - /// ### Why is this bad? - /// - /// Hard-coding the line endings makes the code less compatible. `str.lines` should be used instead. - /// - /// ### Example - /// ```no_run - /// "some\ntext\nwith\nnewlines\n".trim().split('\n'); - /// ``` - /// Use instead: - /// ```no_run - /// "some\ntext\nwith\nnewlines\n".lines(); - /// ``` - /// - /// ### Known Problems - /// - /// This lint cannot detect if the split is intentionally restricted to a single type of newline (`"\n"` or - /// `"\r\n"`), for example during the parsing of a specific file format in which precisely one newline type is - /// valid. - #[clippy::version = "1.77.0"] - pub STR_SPLIT_AT_NEWLINE, - pedantic, - "splitting a trimmed string at hard-coded newlines" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.as_ref().cloned()` and `.as_mut().cloned()` on `Option`s - /// - /// ### Why is this bad? - /// This can be written more concisely by cloning the `Option` directly. - /// - /// ### Example - /// ```no_run - /// fn foo(bar: &Option>) -> Option> { - /// bar.as_ref().cloned() - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn foo(bar: &Option>) -> Option> { - /// bar.clone() - /// } - /// ``` - #[clippy::version = "1.77.0"] - pub OPTION_AS_REF_CLONED, - pedantic, - "cloning an `Option` via `as_ref().cloned()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for unnecessary calls to `min()` or `max()` in the following cases - /// - Either both side is constant - /// - One side is clearly larger than the other, like i32::MIN and an i32 variable - /// - /// ### Why is this bad? - /// - /// In the aforementioned cases it is not necessary to call `min()` or `max()` - /// to compare values, it may even cause confusion. - /// - /// ### Example - /// ```no_run - /// let _ = 0.min(7_u32); - /// ``` - /// Use instead: - /// ```no_run - /// let _ = 0; - /// ``` - #[clippy::version = "1.81.0"] - pub UNNECESSARY_MIN_OR_MAX, - complexity, - "using 'min()/max()' when there is no need for it" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.map_or_else()` "map closure" for `Result` type. - /// - /// ### Why is this bad? - /// This can be written more concisely by using `unwrap_or_else()`. - /// - /// ### Example - /// ```no_run - /// # fn handle_error(_: ()) -> u32 { 0 } - /// let x: Result = Ok(0); - /// let y = x.map_or_else(|err| handle_error(err), |n| n); - /// ``` - /// Use instead: - /// ```no_run - /// # fn handle_error(_: ()) -> u32 { 0 } - /// let x: Result = Ok(0); - /// let y = x.unwrap_or_else(|err| handle_error(err)); - /// ``` - #[clippy::version = "1.78.0"] - pub UNNECESSARY_RESULT_MAP_OR_ELSE, - suspicious, - "making no use of the \"map closure\" when calling `.map_or_else(|err| handle_error(err), |n| n)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the manual creation of C strings (a string with a `NUL` byte at the end), either - /// through one of the `CStr` constructor functions, or more plainly by calling `.as_ptr()` - /// on a (byte) string literal with a hardcoded `\0` byte at the end. - /// - /// ### Why is this bad? - /// This can be written more concisely using `c"str"` literals and is also less error-prone, - /// because the compiler checks for interior `NUL` bytes and the terminating `NUL` byte is inserted automatically. - /// - /// ### Example - /// ```no_run - /// # use std::ffi::CStr; - /// # mod libc { pub unsafe fn puts(_: *const i8) {} } - /// fn needs_cstr(_: &CStr) {} - /// - /// needs_cstr(CStr::from_bytes_with_nul(b"Hello\0").unwrap()); - /// unsafe { libc::puts("World\0".as_ptr().cast()) } - /// ``` - /// Use instead: - /// ```no_run - /// # use std::ffi::CStr; - /// # mod libc { pub unsafe fn puts(_: *const i8) {} } - /// fn needs_cstr(_: &CStr) {} - /// - /// needs_cstr(c"Hello"); - /// unsafe { libc::puts(c"World".as_ptr()) } - /// ``` - #[clippy::version = "1.78.0"] - pub MANUAL_C_STR_LITERALS, - complexity, - r#"creating a `CStr` through functions when `c""` literals can be used"# -} - -declare_clippy_lint! { - /// ### What it does - /// Checks the usage of `.get().is_some()` or `.get().is_none()` on std map types. - /// - /// ### Why is this bad? - /// It can be done in one call with `.contains()`/`.contains_key()`. - /// - /// ### Example - /// ```no_run - /// # use std::collections::HashSet; - /// let s: HashSet = HashSet::new(); - /// if s.get("a").is_some() { - /// // code - /// } - /// ``` - /// Use instead: - /// ```no_run - /// # use std::collections::HashSet; - /// let s: HashSet = HashSet::new(); - /// if s.contains("a") { - /// // code - /// } - /// ``` - #[clippy::version = "1.78.0"] - pub UNNECESSARY_GET_THEN_CHECK, - suspicious, - "calling `.get().is_some()` or `.get().is_none()` instead of `.contains()` or `.contains_key()`" -} - -declare_clippy_lint! { - /// ### What it does - /// It identifies calls to `.is_empty()` on constant values. - /// - /// ### Why is this bad? - /// String literals and constant values are known at compile time. Checking if they - /// are empty will always return the same value. This might not be the intention of - /// the expression. - /// - /// ### Example - /// ```no_run - /// let value = ""; - /// if value.is_empty() { - /// println!("the string is empty"); - /// } - /// ``` - /// Use instead: - /// ```no_run - /// println!("the string is empty"); - /// ``` - #[clippy::version = "1.79.0"] - pub CONST_IS_EMPTY, - suspicious, - "is_empty() called on strings known at compile time" -} - -declare_clippy_lint! { - /// ### What it does - /// Converts some constructs mapping an Enum value for equality comparison. - /// - /// ### Why is this bad? - /// Calls such as `opt.map_or(false, |val| val == 5)` are needlessly long and cumbersome, - /// and can be reduced to, for example, `opt == Some(5)` assuming `opt` implements `PartialEq`. - /// Also, calls such as `opt.map_or(true, |val| val == 5)` can be reduced to - /// `opt.is_none_or(|val| val == 5)`. - /// This lint offers readability and conciseness improvements. - /// - /// ### Example - /// ```no_run - /// pub fn a(x: Option) -> (bool, bool) { - /// ( - /// x.map_or(false, |n| n == 5), - /// x.map_or(true, |n| n > 5), - /// ) - /// } - /// ``` - /// Use instead: - /// ```no_run - /// pub fn a(x: Option) -> (bool, bool) { - /// ( - /// x == Some(5), - /// x.is_none_or(|n| n > 5), - /// ) - /// } - /// ``` - #[clippy::version = "1.84.0"] - pub UNNECESSARY_MAP_OR, - style, - "reduce unnecessary calls to `.map_or(bool, …)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks if an iterator is used to check if a string is ascii. - /// - /// ### Why is this bad? - /// The `str` type already implements the `is_ascii` method. - /// - /// ### Example - /// ```no_run - /// "foo".chars().all(|c| c.is_ascii()); - /// ``` - /// Use instead: - /// ```no_run - /// "foo".is_ascii(); - /// ``` - #[clippy::version = "1.81.0"] - pub NEEDLESS_CHARACTER_ITERATION, - suspicious, - "is_ascii() called on a char iterator" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for uses of `map` which return the original item. - /// - /// ### Why is this bad? - /// `inspect` is both clearer in intent and shorter. - /// - /// ### Example - /// ```no_run - /// let x = Some(0).map(|x| { println!("{x}"); x }); - /// ``` - /// Use instead: - /// ```no_run - /// let x = Some(0).inspect(|x| println!("{x}")); - /// ``` - #[clippy::version = "1.81.0"] - pub MANUAL_INSPECT, - complexity, - "use of `map` returning the original item" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks the usage of `.first().is_some()` or `.first().is_none()` to check if a slice is - /// empty. - /// - /// ### Why is this bad? - /// Using `.is_empty()` is shorter and better communicates the intention. - /// - /// ### Example - /// ```no_run - /// let v = vec![1, 2, 3]; - /// if v.first().is_none() { - /// // The vector is empty... - /// } - /// ``` - /// Use instead: - /// ```no_run - /// let v = vec![1, 2, 3]; - /// if v.is_empty() { - /// // The vector is empty... - /// } - /// ``` - #[clippy::version = "1.83.0"] - pub UNNECESSARY_FIRST_THEN_CHECK, - complexity, - "calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`" -} - -declare_clippy_lint! { - /// ### What it does - /// It detects useless calls to `str::as_bytes()` before calling `len()` or `is_empty()`. - /// - /// ### Why is this bad? - /// The `len()` and `is_empty()` methods are also directly available on strings, and they - /// return identical results. In particular, `len()` on a string returns the number of - /// bytes. - /// - /// ### Example - /// ``` - /// let len = "some string".as_bytes().len(); - /// let b = "some string".as_bytes().is_empty(); - /// ``` - /// Use instead: - /// ``` - /// let len = "some string".len(); - /// let b = "some string".is_empty(); - /// ``` - #[clippy::version = "1.84.0"] - pub NEEDLESS_AS_BYTES, - complexity, - "detect useless calls to `as_bytes()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `.map(…)`, followed by `.all(identity)` or `.any(identity)`. - /// - /// ### Why is this bad? - /// The `.all(…)` or `.any(…)` methods can be called directly in place of `.map(…)`. - /// - /// ### Example - /// ``` - /// # let mut v = [""]; - /// let e1 = v.iter().map(|s| s.is_empty()).all(|a| a); - /// let e2 = v.iter().map(|s| s.is_empty()).any(std::convert::identity); - /// ``` - /// Use instead: - /// ``` - /// # let mut v = [""]; - /// let e1 = v.iter().all(|s| s.is_empty()); - /// let e2 = v.iter().any(|s| s.is_empty()); - /// ``` - #[clippy::version = "1.84.0"] - pub MAP_ALL_ANY_IDENTITY, - complexity, - "combine `.map(_)` followed by `.all(identity)`/`.any(identity)` into a single call" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for `Iterator::map` over ranges without using the parameter which - /// could be more clearly expressed using `std::iter::repeat(...).take(...)` - /// or `std::iter::repeat_n`. - /// - /// ### Why is this bad? - /// - /// It expresses the intent more clearly to `take` the correct number of times - /// from a generating function than to apply a closure to each number in a - /// range only to discard them. - /// - /// ### Example - /// - /// ```no_run - /// let random_numbers : Vec<_> = (0..10).map(|_| { 3 + 1 }).collect(); - /// ``` - /// Use instead: - /// ```no_run - /// let f : Vec<_> = std::iter::repeat( 3 + 1 ).take(10).collect(); - /// ``` - /// - /// ### Known Issues - /// - /// This lint may suggest replacing a `Map` with a `Take`. - /// The former implements some traits that the latter does not, such as - /// `DoubleEndedIterator`. - #[clippy::version = "1.84.0"] - pub MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, - restriction, - "map of a trivial closure (not dependent on parameter) over a range" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for `Iterator::last` being called on a `DoubleEndedIterator`, which can be replaced - /// with `DoubleEndedIterator::next_back`. - /// - /// ### Why is this bad? - /// - /// `Iterator::last` is implemented by consuming the iterator, which is unnecessary if - /// the iterator is a `DoubleEndedIterator`. Since Rust traits do not allow specialization, - /// `Iterator::last` cannot be optimized for `DoubleEndedIterator`. - /// - /// ### Example - /// ```no_run - /// let last_arg = "echo hello world".split(' ').last(); - /// ``` - /// Use instead: - /// ```no_run - /// let last_arg = "echo hello world".split(' ').next_back(); - /// ``` - #[clippy::version = "1.86.0"] - pub DOUBLE_ENDED_ITERATOR_LAST, - perf, - "using `Iterator::last` on a `DoubleEndedIterator`" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for `NonZero*::new_unchecked()` being used in a `const` context. - /// - /// ### Why is this bad? - /// - /// Using `NonZero*::new_unchecked()` is an `unsafe` function and requires an `unsafe` context. When used in a - /// context evaluated at compilation time, `NonZero*::new().unwrap()` will provide the same result with identical - /// runtime performances while not requiring `unsafe`. - /// - /// ### Example - /// ```no_run - /// use std::num::NonZeroUsize; - /// const PLAYERS: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(3) }; - /// ``` - /// Use instead: - /// ```no_run - /// use std::num::NonZeroUsize; - /// const PLAYERS: NonZeroUsize = NonZeroUsize::new(3).unwrap(); - /// ``` - #[clippy::version = "1.86.0"] - pub USELESS_NONZERO_NEW_UNCHECKED, - complexity, - "using `NonZero::new_unchecked()` in a `const` context" -} - -declare_clippy_lint! { - /// ### What it does - /// - /// Checks for `repeat().take()` that can be replaced with `repeat_n()`. - /// - /// ### Why is this bad? - /// - /// Using `repeat_n()` is more concise and clearer. Also, `repeat_n()` is sometimes faster than `repeat().take()` when the type of the element is non-trivial to clone because the original value can be reused for the last `.next()` call rather than always cloning. - /// - /// ### Example - /// ```no_run - /// let _ = std::iter::repeat(10).take(3); - /// ``` - /// Use instead: - /// ```no_run - /// let _ = std::iter::repeat_n(10, 3); - /// ``` - #[clippy::version = "1.86.0"] - pub MANUAL_REPEAT_N, - style, - "detect `repeat().take()` that can be replaced with `repeat_n()`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for string slices immediately followed by `as_bytes`. - /// - /// ### Why is this bad? - /// It involves doing an unnecessary UTF-8 alignment check which is less efficient, and can cause a panic. - /// - /// ### Known problems - /// In some cases, the UTF-8 validation and potential panic from string slicing may be required for - /// the code's correctness. If you need to ensure the slice boundaries fall on valid UTF-8 character - /// boundaries, the original form (`s[1..5].as_bytes()`) should be preferred. - /// - /// ### Example - /// ```rust - /// let s = "Lorem ipsum"; - /// s[1..5].as_bytes(); - /// ``` - /// Use instead: - /// ```rust - /// let s = "Lorem ipsum"; - /// &s.as_bytes()[1..5]; - /// ``` - #[clippy::version = "1.86.0"] - pub SLICED_STRING_AS_BYTES, - perf, - "slicing a string and immediately calling as_bytes is less efficient and can lead to panics" -} - -declare_clippy_lint! { - /// ### What it does - /// Detect functions that end with `Option::and_then` or `Result::and_then`, and suggest using - /// the `?` operator instead. - /// - /// ### Why is this bad? - /// The `and_then` method is used to chain a computation that returns an `Option` or a `Result`. - /// This can be replaced with the `?` operator, which is more concise and idiomatic. - /// - /// ### Example - /// - /// ```no_run - /// fn test(opt: Option) -> Option { - /// opt.and_then(|n| { - /// if n > 1 { - /// Some(n + 1) - /// } else { - /// None - /// } - /// }) - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn test(opt: Option) -> Option { - /// let n = opt?; - /// if n > 1 { - /// Some(n + 1) - /// } else { - /// None - /// } - /// } - /// ``` - #[clippy::version = "1.86.0"] - pub RETURN_AND_THEN, - restriction, - "using `Option::and_then` or `Result::and_then` to chain a computation that returns an `Option` or a `Result`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `Read::bytes` on types which don't implement `BufRead`. - /// - /// ### Why is this bad? - /// The default implementation calls `read` for each byte, which can be very inefficient for data that’s not in memory, such as `File`. - /// - /// ### Example - /// ```no_run - /// use std::io::Read; - /// use std::fs::File; - /// let file = File::open("./bytes.txt").unwrap(); - /// file.bytes(); - /// ``` - /// Use instead: - /// ```no_run - /// use std::io::{BufReader, Read}; - /// use std::fs::File; - /// let file = BufReader::new(File::open("./bytes.txt").unwrap()); - /// file.bytes(); - /// ``` - #[clippy::version = "1.87.0"] - pub UNBUFFERED_BYTES, - perf, - "calling .bytes() is very inefficient when data is not in memory" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `iter().any()` on slices when it can be replaced with `contains()` and suggests doing so. - /// - /// ### Why is this bad? - /// `contains()` is more concise and idiomatic, while also being faster in some cases. - /// - /// ### Example - /// ```no_run - /// fn foo(values: &[u8]) -> bool { - /// values.iter().any(|&v| v == 10) - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn foo(values: &[u8]) -> bool { - /// values.contains(&10) - /// } - /// ``` - #[clippy::version = "1.87.0"] - pub MANUAL_CONTAINS, - perf, - "unnecessary `iter().any()` on slices that can be replaced with `contains()`" -} - -declare_clippy_lint! { - /// This lint warns on calling `io::Error::new(..)` with a kind of - /// `io::ErrorKind::Other`. - /// - /// ### Why is this bad? - /// Since Rust 1.74, there's the `io::Error::other(_)` shortcut. - /// - /// ### Example - /// ```no_run - /// use std::io; - /// let _ = io::Error::new(io::ErrorKind::Other, "bad".to_string()); - /// ``` - /// Use instead: - /// ```no_run - /// let _ = std::io::Error::other("bad".to_string()); - /// ``` - #[clippy::version = "1.87.0"] - pub IO_OTHER_ERROR, - style, - "calling `std::io::Error::new(std::io::ErrorKind::Other, _)`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `std::mem::swap` with temporary values. - /// - /// ### Why is this bad? - /// Storing a new value in place of a temporary value which will - /// be dropped right after the `swap` is an inefficient way of performing - /// an assignment. The same result can be achieved by using a regular - /// assignment. - /// - /// ### Examples - /// ```no_run - /// fn replace_string(s: &mut String) { - /// std::mem::swap(s, &mut String::from("replaced")); - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn replace_string(s: &mut String) { - /// *s = String::from("replaced"); - /// } - /// ``` - /// - /// Also, swapping two temporary values has no effect, as they will - /// both be dropped right after swapping them. This is likely an indication - /// of a bug. For example, the following code swaps the references to - /// the last element of the vectors, instead of swapping the elements - /// themselves: - /// - /// ```no_run - /// fn bug(v1: &mut [i32], v2: &mut [i32]) { - /// // Incorrect: swapping temporary references (`&mut &mut` passed to swap) - /// std::mem::swap(&mut v1.last_mut().unwrap(), &mut v2.last_mut().unwrap()); - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn correct(v1: &mut [i32], v2: &mut [i32]) { - /// std::mem::swap(v1.last_mut().unwrap(), v2.last_mut().unwrap()); - /// } - /// ``` - #[clippy::version = "1.88.0"] - pub SWAP_WITH_TEMPORARY, - complexity, - "detect swap with a temporary value" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for IP addresses that could be replaced with predefined constants such as - /// `Ipv4Addr::new(127, 0, 0, 1)` instead of using the appropriate constants. - /// - /// ### Why is this bad? - /// Using specific IP addresses like `127.0.0.1` or `::1` is less clear and less maintainable than using the - /// predefined constants `Ipv4Addr::LOCALHOST` or `Ipv6Addr::LOCALHOST`. These constants improve code - /// readability, make the intent explicit, and are less error-prone. - /// - /// ### Example - /// ```no_run - /// use std::net::{Ipv4Addr, Ipv6Addr}; - /// - /// // IPv4 loopback - /// let addr_v4 = Ipv4Addr::new(127, 0, 0, 1); - /// - /// // IPv6 loopback - /// let addr_v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); - /// ``` - /// Use instead: - /// ```no_run - /// use std::net::{Ipv4Addr, Ipv6Addr}; - /// - /// // IPv4 loopback - /// let addr_v4 = Ipv4Addr::LOCALHOST; - /// - /// // IPv6 loopback - /// let addr_v6 = Ipv6Addr::LOCALHOST; - /// ``` - #[clippy::version = "1.89.0"] - pub IP_CONSTANT, - pedantic, - "hardcoded localhost IP address" -} - -#[expect(clippy::struct_excessive_bools)] -pub struct Methods { - avoid_breaking_exported_api: bool, - msrv: Msrv, - allow_expect_in_tests: bool, - allow_unwrap_in_tests: bool, - allow_expect_in_consts: bool, - allow_unwrap_in_consts: bool, - allowed_dotfiles: FxHashSet<&'static str>, - format_args: FormatArgsStorage, -} - -impl Methods { - pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self { - let mut allowed_dotfiles: FxHashSet<_> = conf.allowed_dotfiles.iter().map(|s| &**s).collect(); - allowed_dotfiles.extend(DEFAULT_ALLOWED_DOTFILES); - - Self { - avoid_breaking_exported_api: conf.avoid_breaking_exported_api, - msrv: conf.msrv, - allow_expect_in_tests: conf.allow_expect_in_tests, - allow_unwrap_in_tests: conf.allow_unwrap_in_tests, - allow_expect_in_consts: conf.allow_expect_in_consts, - allow_unwrap_in_consts: conf.allow_unwrap_in_consts, - allowed_dotfiles, - format_args, - } - } -} - -impl_lint_pass!(Methods => [ - UNWRAP_USED, - EXPECT_USED, - SHOULD_IMPLEMENT_TRAIT, - WRONG_SELF_CONVENTION, - OK_EXPECT, - UNWRAP_OR_DEFAULT, - MAP_UNWRAP_OR, - RESULT_MAP_OR_INTO_OPTION, - OPTION_MAP_OR_NONE, - BIND_INSTEAD_OF_MAP, - OR_FUN_CALL, - OR_THEN_UNWRAP, - EXPECT_FUN_CALL, - CHARS_NEXT_CMP, - CHARS_LAST_CMP, - CLONE_ON_COPY, - CLONE_ON_REF_PTR, - COLLAPSIBLE_STR_REPLACE, - CONST_IS_EMPTY, - ITER_OVEREAGER_CLONED, - CLONED_INSTEAD_OF_COPIED, - FLAT_MAP_OPTION, - INEFFICIENT_TO_STRING, - NEW_RET_NO_SELF, - SINGLE_CHAR_ADD_STR, - SEARCH_IS_SOME, - FILTER_NEXT, - SKIP_WHILE_NEXT, - FILTER_MAP_IDENTITY, - MAP_IDENTITY, - MANUAL_FILTER_MAP, - MANUAL_FIND_MAP, - OPTION_FILTER_MAP, - FILTER_MAP_NEXT, - FLAT_MAP_IDENTITY, - MAP_FLATTEN, - ITERATOR_STEP_BY_ZERO, - ITER_NEXT_SLICE, - ITER_COUNT, - ITER_NTH, - ITER_NTH_ZERO, - BYTES_NTH, - ITER_SKIP_NEXT, - GET_UNWRAP, - GET_LAST_WITH_LEN, - STRING_EXTEND_CHARS, - ITER_CLONED_COLLECT, - ITER_WITH_DRAIN, - TYPE_ID_ON_BOX, - USELESS_ASREF, - UNNECESSARY_FOLD, - UNNECESSARY_FILTER_MAP, - UNNECESSARY_FIND_MAP, - INTO_ITER_ON_REF, - SUSPICIOUS_MAP, - UNINIT_ASSUMED_INIT, - MANUAL_SATURATING_ARITHMETIC, - ZST_OFFSET, - FILETYPE_IS_FILE, - OPTION_AS_REF_DEREF, - UNNECESSARY_LAZY_EVALUATIONS, - MAP_COLLECT_RESULT_UNIT, - FROM_ITER_INSTEAD_OF_COLLECT, - INSPECT_FOR_EACH, - IMPLICIT_CLONE, - SUSPICIOUS_TO_OWNED, - SUSPICIOUS_SPLITN, - MANUAL_STR_REPEAT, - EXTEND_WITH_DRAIN, - MANUAL_SPLIT_ONCE, - NEEDLESS_SPLITN, - UNNECESSARY_TO_OWNED, - UNNECESSARY_JOIN, - ERR_EXPECT, - NEEDLESS_OPTION_AS_DEREF, - IS_DIGIT_ASCII_RADIX, - NEEDLESS_OPTION_TAKE, - NO_EFFECT_REPLACE, - OBFUSCATED_IF_ELSE, - ITER_ON_SINGLE_ITEMS, - ITER_ON_EMPTY_COLLECTIONS, - NAIVE_BYTECOUNT, - BYTES_COUNT_TO_LEN, - CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, - GET_FIRST, - MANUAL_OK_OR, - MAP_CLONE, - MAP_ERR_IGNORE, - MUT_MUTEX_LOCK, - NONSENSICAL_OPEN_OPTIONS, - SUSPICIOUS_OPEN_OPTIONS, - PATH_BUF_PUSH_OVERWRITE, - RANGE_ZIP_WITH_LEN, - REPEAT_ONCE, - STABLE_SORT_PRIMITIVE, - UNIT_HASH, - READ_LINE_WITHOUT_TRIM, - UNNECESSARY_SORT_BY, - VEC_RESIZE_TO_ZERO, - VERBOSE_FILE_READS, - ITER_KV_MAP, - SEEK_FROM_CURRENT, - SEEK_TO_START_INSTEAD_OF_REWIND, - NEEDLESS_COLLECT, - SUSPICIOUS_COMMAND_ARG_SPACE, - CLEAR_WITH_DRAIN, - MANUAL_NEXT_BACK, - UNNECESSARY_LITERAL_UNWRAP, - DRAIN_COLLECT, - MANUAL_TRY_FOLD, - FORMAT_COLLECT, - STRING_LIT_CHARS_ANY, - ITER_SKIP_ZERO, - FILTER_MAP_BOOL_THEN, - READONLY_WRITE_LOCK, - ITER_OUT_OF_BOUNDS, - PATH_ENDS_WITH_EXT, - REDUNDANT_AS_STR, - WAKER_CLONE_WAKE, - UNNECESSARY_FALLIBLE_CONVERSIONS, - JOIN_ABSOLUTE_PATHS, - RESULT_FILTER_MAP, - ITER_FILTER_IS_SOME, - ITER_FILTER_IS_OK, - MANUAL_IS_VARIANT_AND, - STR_SPLIT_AT_NEWLINE, - OPTION_AS_REF_CLONED, - UNNECESSARY_RESULT_MAP_OR_ELSE, - MANUAL_C_STR_LITERALS, - UNNECESSARY_GET_THEN_CHECK, - UNNECESSARY_FIRST_THEN_CHECK, - NEEDLESS_CHARACTER_ITERATION, - MANUAL_INSPECT, - UNNECESSARY_MIN_OR_MAX, - NEEDLESS_AS_BYTES, - MAP_ALL_ANY_IDENTITY, - MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, - UNNECESSARY_MAP_OR, - DOUBLE_ENDED_ITERATOR_LAST, - USELESS_NONZERO_NEW_UNCHECKED, - MANUAL_REPEAT_N, - SLICED_STRING_AS_BYTES, - RETURN_AND_THEN, - UNBUFFERED_BYTES, - MANUAL_CONTAINS, - IO_OTHER_ERROR, - SWAP_WITH_TEMPORARY, - IP_CONSTANT, -]); - -/// Extracts a method call name, args, and `Span` of the method name. -/// This ensures that neither the receiver nor any of the arguments -/// come from expansion. -pub fn method_call<'tcx>(recv: &'tcx Expr<'tcx>) -> Option<(Symbol, &'tcx Expr<'tcx>, &'tcx [Expr<'tcx>], Span, Span)> { - if let ExprKind::MethodCall(path, receiver, args, call_span) = recv.kind - && !args.iter().any(|e| e.span.from_expansion()) - && !receiver.span.from_expansion() - { - Some((path.ident.name, receiver, args, path.ident.span, call_span)) - } else { - None - } -} - -impl<'tcx> LateLintPass<'tcx> for Methods { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if expr.span.from_expansion() { - return; - } - - self.check_methods(cx, expr); - - match expr.kind { - ExprKind::Call(func, args) => { - from_iter_instead_of_collect::check(cx, expr, args, func); - unnecessary_fallible_conversions::check_function(cx, expr, func); - manual_c_str_literals::check(cx, expr, func, args, self.msrv); - useless_nonzero_new_unchecked::check(cx, expr, func, args, self.msrv); - io_other_error::check(cx, expr, func, args, self.msrv); - swap_with_temporary::check(cx, expr, func, args); - ip_constant::check(cx, expr, func, args); - }, - ExprKind::MethodCall(method_call, receiver, args, _) => { - let method_span = method_call.ident.span; - or_fun_call::check(cx, expr, method_span, method_call.ident.name, receiver, args); - expect_fun_call::check( - cx, - &self.format_args, - expr, - method_span, - method_call.ident.name, - receiver, - args, - ); - clone_on_copy::check(cx, expr, method_call.ident.name, receiver, args); - clone_on_ref_ptr::check(cx, expr, method_call.ident.name, receiver, args); - inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args); - single_char_add_str::check(cx, expr, receiver, args); - into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, receiver); - unnecessary_to_owned::check(cx, expr, method_call.ident.name, receiver, args, self.msrv); - }, - ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => { - let mut info = BinaryExprInfo { - expr, - chain: lhs, - other: rhs, - eq: op.node == hir::BinOpKind::Eq, - }; - lint_binary_expr_with_method_call(cx, &mut info); - }, - _ => (), - } - } - - #[allow(clippy::too_many_lines)] - fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { - if impl_item.span.in_external_macro(cx.sess().source_map()) { - return; - } - let name = impl_item.ident.name; - let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id; - let item = cx.tcx.hir_expect_item(parent); - let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); - - let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })); - if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind { - let method_sig = cx.tcx.fn_sig(impl_item.owner_id).instantiate_identity(); - let method_sig = cx.tcx.instantiate_bound_regions_with_erased(method_sig); - let first_arg_ty_opt = method_sig.inputs().iter().next().copied(); - // if this impl block implements a trait, lint in trait definition instead - if !implements_trait && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) { - // check missing trait implementations - for method_config in &TRAIT_METHODS { - if name == method_config.method_name - && sig.decl.inputs.len() == method_config.param_count - && method_config.output_type.matches(&sig.decl.output) - // in case there is no first arg, since we already have checked the number of arguments - // it's should be always true - && first_arg_ty_opt.is_none_or(|first_arg_ty| method_config - .self_kind.matches(cx, self_ty, first_arg_ty) - ) - && fn_header_equals(method_config.fn_header, sig.header) - && method_config.lifetime_param_cond(impl_item) - { - span_lint_and_help( - cx, - SHOULD_IMPLEMENT_TRAIT, - impl_item.span, - format!( - "method `{}` can be confused for the standard trait method `{}::{}`", - method_config.method_name, method_config.trait_name, method_config.method_name - ), - None, - format!( - "consider implementing the trait `{}` or choosing a less ambiguous method name", - method_config.trait_name - ), - ); - } - } - } - - if sig.decl.implicit_self.has_implicit_self() - && !(self.avoid_breaking_exported_api - && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id)) - && let Some(first_arg) = iter_input_pats(sig.decl, cx.tcx.hir_body(id)).next() - && let Some(first_arg_ty) = first_arg_ty_opt - { - wrong_self_convention::check( - cx, - name, - self_ty, - first_arg_ty, - first_arg.pat.span, - implements_trait, - false, - ); - } - } - - // if this impl block implements a trait, lint in trait definition instead - if implements_trait { - return; - } - - if let hir::ImplItemKind::Fn(_, _) = impl_item.kind { - let ret_ty = return_ty(cx, impl_item.owner_id); - - if contains_ty_adt_constructor_opaque(cx, ret_ty, self_ty) { - return; - } - - if name == sym::new && ret_ty != self_ty { - span_lint( - cx, - NEW_RET_NO_SELF, - impl_item.span, - "methods called `new` usually return `Self`", - ); - } - } - } - - fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { - if item.span.in_external_macro(cx.tcx.sess.source_map()) { - return; - } - - if let TraitItemKind::Fn(ref sig, _) = item.kind - && sig.decl.implicit_self.has_implicit_self() - && let Some(first_arg_hir_ty) = sig.decl.inputs.first() - && let Some(&first_arg_ty) = cx - .tcx - .fn_sig(item.owner_id) - .instantiate_identity() - .inputs() - .skip_binder() - .first() - { - let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty(); - wrong_self_convention::check( - cx, - item.ident.name, - self_ty, - first_arg_ty, - first_arg_hir_ty.span, - false, - true, - ); - } - - if item.ident.name == sym::new - && let TraitItemKind::Fn(_, _) = item.kind - && let ret_ty = return_ty(cx, item.owner_id) - && let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty() - && !ret_ty.contains(self_ty) - { - span_lint( - cx, - NEW_RET_NO_SELF, - item.span, - "methods called `new` usually return `Self`", - ); - } - } -} - -impl Methods { - #[allow(clippy::too_many_lines)] - fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - // Handle method calls whose receiver and arguments may not come from expansion - if let Some((name, recv, args, span, call_span)) = method_call(expr) { - match (name, args) { - ( - sym::add | sym::offset | sym::sub | sym::wrapping_offset | sym::wrapping_add | sym::wrapping_sub, - [_arg], - ) => { - zst_offset::check(cx, expr, recv); - }, - (sym::all, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); - needless_character_iteration::check(cx, expr, recv, arg, true); - match method_call(recv) { - Some((sym::cloned, recv2, [], _, _)) => { - iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::NeedlessMove(arg), - false, - ); - }, - Some((sym::map, _, [map_arg], _, map_call_span)) => { - map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "all"); - }, - _ => {}, - } - }, - (sym::and_then, [arg]) => { - let biom_option_linted = bind_instead_of_map::check_and_then_some(cx, expr, recv, arg); - let biom_result_linted = bind_instead_of_map::check_and_then_ok(cx, expr, recv, arg); - if !biom_option_linted && !biom_result_linted { - let ule_and_linted = unnecessary_lazy_eval::check(cx, expr, recv, arg, "and"); - if !ule_and_linted { - return_and_then::check(cx, expr, recv, arg); - } - } - }, - (sym::any, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); - needless_character_iteration::check(cx, expr, recv, arg, false); - match method_call(recv) { - Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::NeedlessMove(arg), - false, - ), - Some((sym::chars, recv, _, _, _)) - if let ExprKind::Closure(arg) = arg.kind - && let body = cx.tcx.hir_body(arg.body) - && let [param] = body.params => - { - string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), self.msrv); - }, - Some((sym::map, _, [map_arg], _, map_call_span)) => { - map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "any"); - }, - Some((sym::iter, iter_recv, ..)) => { - manual_contains::check(cx, expr, iter_recv, arg); - }, - _ => {}, - } - }, - (sym::arg, [arg]) => { - suspicious_command_arg_space::check(cx, recv, arg, span); - }, - (sym::as_deref | sym::as_deref_mut, []) => { - needless_option_as_deref::check(cx, expr, recv, name); - }, - (sym::as_bytes, []) => { - if let Some((sym::as_str, recv, [], as_str_span, _)) = method_call(recv) { - redundant_as_str::check(cx, expr, recv, as_str_span, span); - } - sliced_string_as_bytes::check(cx, expr, recv); - }, - (sym::as_mut | sym::as_ref, []) => useless_asref::check(cx, expr, name, recv), - (sym::as_ptr, []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, self.msrv), - (sym::assume_init, []) => uninit_assumed_init::check(cx, expr, recv), - (sym::bytes, []) => unbuffered_bytes::check(cx, expr, recv), - (sym::cloned, []) => { - cloned_instead_of_copied::check(cx, expr, recv, span, self.msrv); - option_as_ref_cloned::check(cx, recv, span); - }, - (sym::collect, []) if is_trait_method(cx, expr, sym::Iterator) => { - needless_collect::check(cx, span, expr, recv, call_span); - match method_call(recv) { - Some((name @ (sym::cloned | sym::copied), recv2, [], _, _)) => { - iter_cloned_collect::check(cx, name, expr, recv2); - }, - Some((sym::map, m_recv, [m_arg], m_ident_span, _)) => { - map_collect_result_unit::check(cx, expr, m_recv, m_arg); - format_collect::check(cx, expr, m_arg, m_ident_span); - }, - Some((sym::take, take_self_arg, [take_arg], _, _)) => { - if self.msrv.meets(cx, msrvs::STR_REPEAT) { - manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg); - } - }, - Some((sym::drain, recv, args, ..)) => { - drain_collect::check(cx, args, expr, recv); - }, - _ => {}, - } - }, - (sym::count, []) if is_trait_method(cx, expr, sym::Iterator) => match method_call(recv) { - Some((sym::cloned, recv2, [], _, _)) => { - iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::RmCloned, false); - }, - Some((name2 @ (sym::into_iter | sym::iter | sym::iter_mut), recv2, [], _, _)) => { - iter_count::check(cx, expr, recv2, name2); - }, - Some((sym::map, _, [arg], _, _)) => suspicious_map::check(cx, expr, recv, arg), - Some((sym::filter, recv2, [arg], _, _)) => bytecount::check(cx, expr, recv2, arg), - Some((sym::bytes, recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2), - _ => {}, - }, - (sym::min | sym::max, [arg]) => { - unnecessary_min_or_max::check(cx, expr, name, recv, arg); - }, - (sym::drain, ..) => { - if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.parent_hir_node(expr.hir_id) - && matches!(kind, StmtKind::Semi(_)) - && args.len() <= 1 - { - clear_with_drain::check(cx, expr, recv, span, args.first()); - } else if let [arg] = args { - iter_with_drain::check(cx, expr, recv, span, arg); - } - }, - (sym::ends_with, [arg]) => { - if let ExprKind::MethodCall(.., span) = expr.kind { - case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg, self.msrv); - } - path_ends_with_ext::check(cx, recv, arg, expr, self.msrv, &self.allowed_dotfiles); - }, - (sym::expect, [_]) => { - match method_call(recv) { - Some((sym::ok, recv, [], _, _)) => ok_expect::check(cx, expr, recv), - Some((sym::err, recv, [], err_span, _)) => { - err_expect::check(cx, expr, recv, span, err_span, self.msrv); - }, - _ => {}, - } - unnecessary_literal_unwrap::check(cx, expr, recv, name, args); - }, - (sym::expect_err, [_]) | (sym::unwrap_err | sym::unwrap_unchecked | sym::unwrap_err_unchecked, []) => { - unnecessary_literal_unwrap::check(cx, expr, recv, name, args); - }, - (sym::extend, [arg]) => { - string_extend_chars::check(cx, expr, recv, arg); - extend_with_drain::check(cx, expr, recv, arg); - }, - (sym::filter, [arg]) => { - if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) { - // if `arg` has side-effect, the semantic will change - iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::FixClosure(name, arg), - false, - ); - } - if self.msrv.meets(cx, msrvs::ITER_FLATTEN) { - // use the sourcemap to get the span of the closure - iter_filter::check(cx, expr, arg, span); - } - }, - (sym::find, [arg]) => { - if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) { - // if `arg` has side-effect, the semantic will change - iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::FixClosure(name, arg), - false, - ); - } - }, - (sym::filter_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); - unnecessary_filter_map::check(cx, expr, arg, name); - filter_map_bool_then::check(cx, expr, arg, call_span); - filter_map_identity::check(cx, expr, arg, span); - }, - (sym::find_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); - unnecessary_filter_map::check(cx, expr, arg, name); - }, - (sym::flat_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); - flat_map_identity::check(cx, expr, arg, span); - flat_map_option::check(cx, expr, arg, span); - }, - (sym::flatten, []) => match method_call(recv) { - Some((sym::map, recv, [map_arg], map_span, _)) => { - map_flatten::check(cx, expr, recv, map_arg, map_span); - }, - Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::LaterCloned, - true, - ), - _ => {}, - }, - (sym::fold, [init, acc]) => { - manual_try_fold::check(cx, expr, init, acc, call_span, self.msrv); - unnecessary_fold::check(cx, expr, init, acc, span); - }, - (sym::for_each, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); - match method_call(recv) { - Some((sym::inspect, _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2), - Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::NeedlessMove(arg), - false, - ), - _ => {}, - } - }, - (sym::get, [arg]) => { - get_first::check(cx, expr, recv, arg); - get_last_with_len::check(cx, expr, recv, arg); - }, - (sym::get_or_insert_with, [arg]) => { - unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"); - }, - (sym::hash, [arg]) => { - unit_hash::check(cx, expr, recv, arg); - }, - (sym::is_empty, []) => { - match method_call(recv) { - Some((prev_method @ (sym::as_bytes | sym::bytes), prev_recv, [], _, _)) => { - needless_as_bytes::check(cx, prev_method, name, prev_recv, expr.span); - }, - Some((sym::as_str, recv, [], as_str_span, _)) => { - redundant_as_str::check(cx, expr, recv, as_str_span, span); - }, - _ => {}, - } - is_empty::check(cx, expr, recv); - }, - (sym::is_file, []) => filetype_is_file::check(cx, expr, recv), - (sym::is_digit, [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv), - (sym::is_none, []) => check_is_some_is_none(cx, expr, recv, call_span, false), - (sym::is_some, []) => check_is_some_is_none(cx, expr, recv, call_span, true), - (sym::iter | sym::iter_mut | sym::into_iter, []) => { - iter_on_single_or_empty_collections::check(cx, expr, name, recv); - }, - (sym::join, [join_arg]) => { - if let Some((sym::collect, _, _, span, _)) = method_call(recv) { - unnecessary_join::check(cx, expr, recv, join_arg, span); - } else { - join_absolute_paths::check(cx, recv, join_arg, expr.span); - } - }, - (sym::last, []) => { - if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) { - iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::LaterCloned, - false, - ); - } - double_ended_iterator_last::check(cx, expr, recv, call_span); - }, - (sym::len, []) => { - if let Some((prev_method @ (sym::as_bytes | sym::bytes), prev_recv, [], _, _)) = method_call(recv) { - needless_as_bytes::check(cx, prev_method, sym::len, prev_recv, expr.span); - } - }, - (sym::lock, []) => { - mut_mutex_lock::check(cx, expr, recv, span); - }, - (name @ (sym::map | sym::map_err), [m_arg]) => { - if name == sym::map { - unused_enumerate_index::check(cx, expr, recv, m_arg); - map_clone::check(cx, expr, recv, m_arg, self.msrv); - map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, self.msrv, span); - manual_is_variant_and::check_map(cx, expr); - match method_call(recv) { - Some((map_name @ (sym::iter | sym::into_iter), recv2, _, _, _)) => { - iter_kv_map::check(cx, map_name, expr, recv2, m_arg, self.msrv); - }, - Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::NeedlessMove(m_arg), - false, - ), - _ => {}, - } - } else { - map_err_ignore::check(cx, expr, m_arg); - } - if let Some((name, recv2, args, span2, _)) = method_call(recv) { - match (name, args) { - (sym::as_mut, []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, self.msrv), - (sym::as_ref, []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, self.msrv), - (sym::filter, [f_arg]) => { - filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false); - }, - (sym::find, [f_arg]) => { - filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, true); - }, - _ => {}, - } - } - map_identity::check(cx, expr, recv, m_arg, name, span); - manual_inspect::check(cx, expr, m_arg, name, span, self.msrv); - crate::useless_conversion::check_function_application(cx, expr, recv, m_arg); - }, - (sym::map_break | sym::map_continue, [m_arg]) => { - crate::useless_conversion::check_function_application(cx, expr, recv, m_arg); - }, - (sym::map_or, [def, map]) => { - option_map_or_none::check(cx, expr, recv, def, map); - manual_ok_or::check(cx, expr, recv, def, map); - unnecessary_map_or::check(cx, expr, recv, def, map, span, self.msrv); - }, - (sym::map_or_else, [def, map]) => { - result_map_or_else_none::check(cx, expr, recv, def, map); - unnecessary_result_map_or_else::check(cx, expr, recv, def, map); - }, - (sym::next, []) => { - if let Some((name2, recv2, args2, _, _)) = method_call(recv) { - match (name2, args2) { - (sym::cloned, []) => iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::LaterCloned, - false, - ), - (sym::filter, [arg]) => filter_next::check(cx, expr, recv2, arg), - (sym::filter_map, [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv), - (sym::iter, []) => iter_next_slice::check(cx, expr, recv2), - (sym::skip, [arg]) => iter_skip_next::check(cx, expr, recv2, arg), - (sym::skip_while, [_]) => skip_while_next::check(cx, expr), - (sym::rev, []) => manual_next_back::check(cx, expr, recv, recv2), - _ => {}, - } - } - }, - (sym::nth, [n_arg]) => match method_call(recv) { - Some((sym::bytes, recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg), - Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::LaterCloned, - false, - ), - Some((iter_method @ (sym::iter | sym::iter_mut), iter_recv, [], iter_span, _)) => { - if !iter_nth::check(cx, expr, iter_recv, iter_method, iter_span, span) { - iter_nth_zero::check(cx, expr, recv, n_arg); - } - }, - _ => iter_nth_zero::check(cx, expr, recv, n_arg), - }, - (sym::ok_or_else, [arg]) => { - unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"); - }, - (sym::open, [_]) => { - open_options::check(cx, expr, recv); - }, - (sym::or_else, [arg]) => { - if !bind_instead_of_map::check_or_else_err(cx, expr, recv, arg) { - unnecessary_lazy_eval::check(cx, expr, recv, arg, "or"); - } - }, - (sym::push, [arg]) => { - path_buf_push_overwrite::check(cx, expr, arg); - }, - (sym::read_to_end, [_]) => { - verbose_file_reads::check(cx, expr, recv, verbose_file_reads::READ_TO_END_MSG); - }, - (sym::read_to_string, [_]) => { - verbose_file_reads::check(cx, expr, recv, verbose_file_reads::READ_TO_STRING_MSG); - }, - (sym::read_line, [arg]) => { - read_line_without_trim::check(cx, expr, recv, arg); - }, - (sym::repeat, [arg]) => { - repeat_once::check(cx, expr, recv, arg); - }, - (name @ (sym::replace | sym::replacen), [arg1, arg2] | [arg1, arg2, _]) => { - no_effect_replace::check(cx, expr, arg1, arg2); - - // Check for repeated `str::replace` calls to perform `collapsible_str_replace` lint - if self.msrv.meets(cx, msrvs::PATTERN_TRAIT_CHAR_ARRAY) - && name == sym::replace - && let Some((sym::replace, ..)) = method_call(recv) - { - collapsible_str_replace::check(cx, expr, arg1, arg2); - } - }, - (sym::resize, [count_arg, default_arg]) => { - vec_resize_to_zero::check(cx, expr, count_arg, default_arg, span); - }, - (sym::seek, [arg]) => { - if self.msrv.meets(cx, msrvs::SEEK_FROM_CURRENT) { - seek_from_current::check(cx, expr, recv, arg); - } - if self.msrv.meets(cx, msrvs::SEEK_REWIND) { - seek_to_start_instead_of_rewind::check(cx, expr, recv, arg, span); - } - }, - (sym::skip, [arg]) => { - iter_skip_zero::check(cx, expr, arg); - iter_out_of_bounds::check_skip(cx, expr, recv, arg); - - if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) { - iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::LaterCloned, - false, - ); - } - }, - (sym::sort, []) => { - stable_sort_primitive::check(cx, expr, recv); - }, - (sym::sort_by, [arg]) => { - unnecessary_sort_by::check(cx, expr, recv, arg, false); - }, - (sym::sort_unstable_by, [arg]) => { - unnecessary_sort_by::check(cx, expr, recv, arg, true); - }, - (sym::split, [arg]) => { - str_split::check(cx, expr, recv, arg); - }, - (sym::splitn | sym::rsplitn, [count_arg, pat_arg]) => { - if let Some(Constant::Int(count)) = ConstEvalCtxt::new(cx).eval(count_arg) { - suspicious_splitn::check(cx, name, expr, recv, count); - str_splitn::check(cx, name, expr, recv, pat_arg, count, self.msrv); - } - }, - (sym::splitn_mut | sym::rsplitn_mut, [count_arg, _]) => { - if let Some(Constant::Int(count)) = ConstEvalCtxt::new(cx).eval(count_arg) { - suspicious_splitn::check(cx, name, expr, recv, count); - } - }, - (sym::step_by, [arg]) => iterator_step_by_zero::check(cx, expr, arg), - (sym::take, [arg]) => { - iter_out_of_bounds::check_take(cx, expr, recv, arg); - manual_repeat_n::check(cx, expr, recv, arg, self.msrv); - if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) { - iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::LaterCloned, - false, - ); - } - }, - (sym::take, []) => needless_option_take::check(cx, expr, recv), - (sym::then, [arg]) => { - if !self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) { - return; - } - unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some"); - }, - (sym::try_into, []) if is_trait_method(cx, expr, sym::TryInto) => { - unnecessary_fallible_conversions::check_method(cx, expr); - }, - (sym::to_owned, []) => { - if !suspicious_to_owned::check(cx, expr, recv) { - implicit_clone::check(cx, name, expr, recv); - } - }, - (sym::to_os_string | sym::to_path_buf | sym::to_vec, []) => { - implicit_clone::check(cx, name, expr, recv); - }, - (sym::type_id, []) => { - type_id_on_box::check(cx, recv, expr.span); - }, - (sym::unwrap, []) => { - match method_call(recv) { - Some((sym::get, recv, [get_arg], _, _)) => { - get_unwrap::check(cx, expr, recv, get_arg, false); - }, - Some((sym::get_mut, recv, [get_arg], _, _)) => { - get_unwrap::check(cx, expr, recv, get_arg, true); - }, - Some((sym::or, recv, [or_arg], or_span, _)) => { - or_then_unwrap::check(cx, expr, recv, or_arg, or_span); - }, - _ => {}, - } - unnecessary_literal_unwrap::check(cx, expr, recv, name, args); - }, - (sym::unwrap_or, [u_arg]) => { - match method_call(recv) { - Some((arith @ (sym::checked_add | sym::checked_sub | sym::checked_mul), lhs, [rhs], _, _)) => { - manual_saturating_arithmetic::check( - cx, - expr, - lhs, - rhs, - u_arg, - &arith.as_str()[const { "checked_".len() }..], - ); - }, - Some((sym::map, m_recv, [m_arg], span, _)) => { - option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv); - }, - Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => { - obfuscated_if_else::check(cx, expr, t_recv, t_arg, Some(u_arg), then_method, name); - }, - _ => {}, - } - unnecessary_literal_unwrap::check(cx, expr, recv, name, args); - }, - (sym::unwrap_or_default, []) => { - match method_call(recv) { - Some((sym::map, m_recv, [arg], span, _)) => { - manual_is_variant_and::check(cx, expr, m_recv, arg, span, self.msrv); - }, - Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => { - obfuscated_if_else::check( - cx, - expr, - t_recv, - t_arg, - None, - then_method, - sym::unwrap_or_default, - ); - }, - _ => {}, - } - unnecessary_literal_unwrap::check(cx, expr, recv, name, args); - }, - (sym::unwrap_or_else, [u_arg]) => { - match method_call(recv) { - Some((sym::map, recv, [map_arg], _, _)) - if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {}, - Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => { - obfuscated_if_else::check( - cx, - expr, - t_recv, - t_arg, - Some(u_arg), - then_method, - sym::unwrap_or_else, - ); - }, - _ => { - unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or"); - }, - } - unnecessary_literal_unwrap::check(cx, expr, recv, name, args); - }, - (sym::wake, []) => { - waker_clone_wake::check(cx, expr, recv); - }, - (sym::write, []) => { - readonly_write_lock::check(cx, expr, recv); - }, - (sym::zip, [arg]) => { - if let ExprKind::MethodCall(name, iter_recv, [], _) = recv.kind - && name.ident.name == sym::iter - { - range_zip_with_len::check(cx, expr, iter_recv, arg); - } - }, - _ => {}, - } - } - // Handle method calls whose receiver and arguments may come from expansion - if let ExprKind::MethodCall(path, recv, args, _call_span) = expr.kind { - match (path.ident.name, args) { - (sym::expect, [_]) if !matches!(method_call(recv), Some((sym::ok | sym::err, _, [], _, _))) => { - unwrap_expect_used::check( - cx, - expr, - recv, - false, - self.allow_expect_in_consts, - self.allow_expect_in_tests, - unwrap_expect_used::Variant::Expect, - ); - }, - (sym::expect_err, [_]) => { - unwrap_expect_used::check( - cx, - expr, - recv, - true, - self.allow_expect_in_consts, - self.allow_expect_in_tests, - unwrap_expect_used::Variant::Expect, - ); - }, - (sym::unwrap, []) => { - unwrap_expect_used::check( - cx, - expr, - recv, - false, - self.allow_unwrap_in_consts, - self.allow_unwrap_in_tests, - unwrap_expect_used::Variant::Unwrap, - ); - }, - (sym::unwrap_err, []) => { - unwrap_expect_used::check( - cx, - expr, - recv, - true, - self.allow_unwrap_in_consts, - self.allow_unwrap_in_tests, - unwrap_expect_used::Variant::Unwrap, - ); - }, - _ => {}, - } - } - } -} - -fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, call_span: Span, is_some: bool) { - match method_call(recv) { - Some((name @ (sym::find | sym::position | sym::rposition), f_recv, [arg], span, _)) => { - search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span); - }, - Some((sym::get, f_recv, [arg], _, _)) => { - unnecessary_get_then_check::check(cx, call_span, recv, f_recv, arg, is_some); - }, - Some((sym::first, f_recv, [], _, _)) => { - unnecessary_first_then_check::check(cx, call_span, recv, f_recv, is_some); - }, - _ => {}, - } -} - -/// Used for `lint_binary_expr_with_method_call`. -#[derive(Copy, Clone)] -struct BinaryExprInfo<'a> { - expr: &'a Expr<'a>, - chain: &'a Expr<'a>, - other: &'a Expr<'a>, - eq: bool, -} - -/// Checks for the `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints. -fn lint_binary_expr_with_method_call(cx: &LateContext<'_>, info: &mut BinaryExprInfo<'_>) { - macro_rules! lint_with_both_lhs_and_rhs { - ($func:expr, $cx:expr, $info:ident) => { - if !$func($cx, $info) { - ::std::mem::swap(&mut $info.chain, &mut $info.other); - if $func($cx, $info) { - return; - } - } - }; - } - - lint_with_both_lhs_and_rhs!(chars_next_cmp::check, cx, info); - lint_with_both_lhs_and_rhs!(chars_last_cmp::check, cx, info); - lint_with_both_lhs_and_rhs!(chars_next_cmp_with_unwrap::check, cx, info); - lint_with_both_lhs_and_rhs!(chars_last_cmp_with_unwrap::check, cx, info); -} - -const FN_HEADER: hir::FnHeader = hir::FnHeader { - safety: hir::HeaderSafety::Normal(hir::Safety::Safe), - constness: hir::Constness::NotConst, - asyncness: hir::IsAsync::NotAsync, - abi: ExternAbi::Rust, -}; - -struct ShouldImplTraitCase { - trait_name: &'static str, - method_name: Symbol, - param_count: usize, - fn_header: hir::FnHeader, - // implicit self kind expected (none, self, &self, ...) - self_kind: SelfKind, - // checks against the output type - output_type: OutType, - // certain methods with explicit lifetimes can't implement the equivalent trait method - lint_explicit_lifetime: bool, -} -impl ShouldImplTraitCase { - const fn new( - trait_name: &'static str, - method_name: Symbol, - param_count: usize, - fn_header: hir::FnHeader, - self_kind: SelfKind, - output_type: OutType, - lint_explicit_lifetime: bool, - ) -> ShouldImplTraitCase { - ShouldImplTraitCase { - trait_name, - method_name, - param_count, - fn_header, - self_kind, - output_type, - lint_explicit_lifetime, - } - } - - fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool { - self.lint_explicit_lifetime - || !impl_item.generics.params.iter().any(|p| { - matches!( - p.kind, - hir::GenericParamKind::Lifetime { - kind: hir::LifetimeParamKind::Explicit - } - ) - }) - } -} - -#[rustfmt::skip] -const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ - ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), - ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), - ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), - ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), - ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, FN_HEADER, SelfKind::Ref, OutType::Any, true), - ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, FN_HEADER, SelfKind::Ref, OutType::Any, true), - ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, FN_HEADER, SelfKind::No, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), - ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), - ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true), - ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true), - ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, FN_HEADER, SelfKind::No, OutType::Any, true), - ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, FN_HEADER, SelfKind::No, OutType::Any, true), - ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true), - ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true), - ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), - ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false), - ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), - ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), -]; - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum SelfKind { - Value, - Ref, - RefMut, - No, // When we want the first argument type to be different than `Self` -} - -impl SelfKind { - fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { - fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { - if ty == parent_ty { - true - } else if let Some(boxed_ty) = ty.boxed_ty() { - boxed_ty == parent_ty - } else if is_type_diagnostic_item(cx, ty, sym::Rc) || is_type_diagnostic_item(cx, ty, sym::Arc) { - if let ty::Adt(_, args) = ty.kind() { - args.types().next() == Some(parent_ty) - } else { - false - } - } else { - false - } - } - - fn matches_ref<'a>(cx: &LateContext<'a>, mutability: hir::Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { - if let ty::Ref(_, t, m) = *ty.kind() { - return m == mutability && t == parent_ty; - } - - let trait_sym = match mutability { - hir::Mutability::Not => sym::AsRef, - hir::Mutability::Mut => sym::AsMut, - }; - - let Some(trait_def_id) = cx.tcx.get_diagnostic_item(trait_sym) else { - return false; - }; - implements_trait(cx, ty, trait_def_id, &[parent_ty.into()]) - } - - fn matches_none<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { - !matches_value(cx, parent_ty, ty) - && !matches_ref(cx, hir::Mutability::Not, parent_ty, ty) - && !matches_ref(cx, hir::Mutability::Mut, parent_ty, ty) - } - - match self { - Self::Value => matches_value(cx, parent_ty, ty), - Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty), - Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty), - Self::No => matches_none(cx, parent_ty, ty), - } - } - - #[must_use] - fn description(self) -> &'static str { - match self { - Self::Value => "`self` by value", - Self::Ref => "`self` by reference", - Self::RefMut => "`self` by mutable reference", - Self::No => "no `self`", - } - } -} - -#[derive(Clone, Copy)] -enum OutType { - Unit, - Bool, - Any, - Ref, -} - -impl OutType { - fn matches(self, ty: &hir::FnRetTy<'_>) -> bool { - let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[])); - match (self, ty) { - (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true, - (Self::Unit, &hir::FnRetTy::Return(ty)) if is_unit(ty) => true, - (Self::Bool, &hir::FnRetTy::Return(ty)) if is_bool(ty) => true, - (Self::Any, &hir::FnRetTy::Return(ty)) if !is_unit(ty) => true, - (Self::Ref, &hir::FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Ref(_, _)), - _ => false, - } - } -} - -fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool { - expected.constness == actual.constness && expected.safety == actual.safety && expected.asyncness == actual.asyncness -} diff --git a/clippy_lints/src/methods/single_char_add_str.rs b/clippy_lints/src/methods/single_char_add_str.rs deleted file mode 100644 index ccdf5529d537..000000000000 --- a/clippy_lints/src/methods/single_char_add_str.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::methods::{single_char_insert_string, single_char_push_string}; -use rustc_hir as hir; -use rustc_lint::LateContext; -use rustc_span::sym; - -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - if cx.tcx.is_diagnostic_item(sym::string_push_str, fn_def_id) { - single_char_push_string::check(cx, expr, receiver, args); - } else if cx.tcx.is_diagnostic_item(sym::string_insert_str, fn_def_id) { - single_char_insert_string::check(cx, expr, receiver, args); - } - } -} diff --git a/clippy_lints/src/methods/single_char_insert_string.rs b/clippy_lints/src/methods/single_char_insert_string.rs deleted file mode 100644 index 4a1d25deade9..000000000000 --- a/clippy_lints/src/methods/single_char_insert_string.rs +++ /dev/null @@ -1,67 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; -use rustc_ast::BorrowKind; -use rustc_errors::Applicability; -use rustc_hir::{self as hir, ExprKind}; -use rustc_lint::LateContext; - -use super::SINGLE_CHAR_ADD_STR; - -/// lint for length-1 `str`s as argument for `insert_str` -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = str_literal_to_char_literal(cx, &args[1], &mut applicability, false) { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); - let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); - let sugg = format!("{base_string_snippet}.insert({pos_arg}, {extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `insert_str()` using a single-character string literal", - "consider using `insert` with a character literal", - sugg, - applicability, - ); - } - - if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[1].kind - && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind - && path_segment.ident.name == rustc_span::sym::to_string - && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) - { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let extension_string = - snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); - let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); - let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; - - let sugg = format!("{base_string_snippet}.insert({pos_arg}, {deref_string}{extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `insert_str()` using a single-character converted to string", - "consider using `insert` without `to_string()`", - sugg, - applicability, - ); - } -} - -fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - if cx.typeck_results().expr_ty(expr).is_ref() - && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() - && ty.is_char() - { - return true; - } - - false -} - -fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr).is_char() -} diff --git a/clippy_lints/src/methods/single_char_push_string.rs b/clippy_lints/src/methods/single_char_push_string.rs deleted file mode 100644 index bc271d593925..000000000000 --- a/clippy_lints/src/methods/single_char_push_string.rs +++ /dev/null @@ -1,65 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; -use rustc_ast::BorrowKind; -use rustc_errors::Applicability; -use rustc_hir::{self as hir, ExprKind}; -use rustc_lint::LateContext; - -use super::SINGLE_CHAR_ADD_STR; - -/// lint for length-1 `str`s as argument for `push_str` -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = str_literal_to_char_literal(cx, &args[0], &mut applicability, false) { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let sugg = format!("{base_string_snippet}.push({extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `push_str()` using a single-character string literal", - "consider using `push` with a character literal", - sugg, - applicability, - ); - } - - if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[0].kind - && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind - && path_segment.ident.name == rustc_span::sym::to_string - && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) - { - let base_string_snippet = - snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); - let extension_string = - snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); - let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; - - let sugg = format!("{base_string_snippet}.push({deref_string}{extension_string})"); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `push_str()` using a single-character converted to string", - "consider using `push` without `to_string()`", - sugg, - applicability, - ); - } -} - -fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - if cx.typeck_results().expr_ty(expr).is_ref() - && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() - && ty.is_char() - { - return true; - } - - false -} - -fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { - cx.typeck_results().expr_ty(expr).is_char() -} diff --git a/clippy_lints/src/methods/stable_sort_primitive.rs b/clippy_lints/src/methods/stable_sort_primitive.rs deleted file mode 100644 index aef14435d8af..000000000000 --- a/clippy_lints/src/methods/stable_sort_primitive.rs +++ /dev/null @@ -1,31 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::is_slice_of_primitives; -use clippy_utils::source::snippet_with_context; -use rustc_errors::Applicability; -use rustc_hir::Expr; -use rustc_lint::LateContext; - -use super::STABLE_SORT_PRIMITIVE; - -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { - if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) - && let Some(impl_id) = cx.tcx.impl_of_method(method_id) - && cx.tcx.type_of(impl_id).instantiate_identity().is_slice() - && let Some(slice_type) = is_slice_of_primitives(cx, recv) - { - span_lint_and_then( - cx, - STABLE_SORT_PRIMITIVE, - e.span, - format!("used `sort` on primitive type `{slice_type}`"), - |diag| { - let mut app = Applicability::MachineApplicable; - let recv_snip = snippet_with_context(cx, recv.span, e.span.ctxt(), "..", &mut app).0; - diag.span_suggestion(e.span, "try", format!("{recv_snip}.sort_unstable()"), app); - diag.note( - "an unstable sort typically performs faster without any observable difference for this data type", - ); - }, - ); - } -} diff --git a/clippy_lints/src/methods/uninit_assumed_init.rs b/clippy_lints/src/methods/uninit_assumed_init.rs deleted file mode 100644 index 6371fe644282..000000000000 --- a/clippy_lints/src/methods/uninit_assumed_init.rs +++ /dev/null @@ -1,23 +0,0 @@ -use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_path_diagnostic_item; -use clippy_utils::ty::is_uninit_value_valid_for_ty; -use rustc_hir as hir; -use rustc_lint::LateContext; -use rustc_span::sym; - -use super::UNINIT_ASSUMED_INIT; - -/// lint for `MaybeUninit::uninit().assume_init()` (we already have the latter) -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { - if let hir::ExprKind::Call(callee, []) = recv.kind - && is_path_diagnostic_item(cx, callee, sym::maybe_uninit_uninit) - && !is_uninit_value_valid_for_ty(cx, cx.typeck_results().expr_ty_adjusted(expr)) - { - span_lint( - cx, - UNINIT_ASSUMED_INIT, - expr.span, - "this call for this type may be undefined behavior", - ); - } -} diff --git a/clippy_lints/src/methods/unit_hash.rs b/clippy_lints/src/methods/unit_hash.rs deleted file mode 100644 index 3c7955bc4698..000000000000 --- a/clippy_lints/src/methods/unit_hash.rs +++ /dev/null @@ -1,29 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::is_trait_method; -use clippy_utils::source::snippet; -use rustc_errors::Applicability; -use rustc_hir::Expr; -use rustc_lint::LateContext; -use rustc_span::sym; - -use super::UNIT_HASH; - -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { - if is_trait_method(cx, expr, sym::Hash) && cx.typeck_results().expr_ty(recv).is_unit() { - span_lint_and_then( - cx, - UNIT_HASH, - expr.span, - "this call to `hash` on the unit type will do nothing", - |diag| { - diag.span_suggestion( - expr.span, - "remove the call to `hash` or consider using", - format!("0_u8.hash({})", snippet(cx, arg.span, ".."),), - Applicability::MaybeIncorrect, - ); - diag.note("the implementation of `Hash` for `()` is a no-op"); - }, - ); - } -} diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints/src/methods/unnecessary_iter_cloned.rs deleted file mode 100644 index 20cf35363d13..000000000000 --- a/clippy_lints/src/methods/unnecessary_iter_cloned.rs +++ /dev/null @@ -1,143 +0,0 @@ -use super::utils::clone_or_copy_needed; -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::higher::ForLoop; -use clippy_utils::source::SpanRangeExt; -use clippy_utils::ty::{get_iterator_item_ty, implements_trait}; -use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{can_mut_borrow_both, fn_def_id, get_parent_expr, path_to_local}; -use core::ops::ControlFlow; -use itertools::Itertools; -use rustc_errors::Applicability; -use rustc_hir::def_id::DefId; -use rustc_hir::{BindingMode, Expr, ExprKind, Node, PatKind}; -use rustc_lint::LateContext; -use rustc_span::{Symbol, sym}; - -use super::UNNECESSARY_TO_OWNED; - -pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool { - if let Some(parent) = get_parent_expr(cx, expr) - && let Some(callee_def_id) = fn_def_id(cx, parent) - && is_into_iter(cx, callee_def_id) - { - check_for_loop_iter(cx, parent, method_name, receiver, false) - } else { - false - } -} - -/// Checks whether `expr` is an iterator in a `for` loop and, if so, determines whether the -/// iterated-over items could be iterated over by reference. The reason why `check` above does not -/// include this code directly is so that it can be called from -/// `unnecessary_into_owned::check_into_iter_call_arg`. -pub fn check_for_loop_iter( - cx: &LateContext<'_>, - expr: &Expr<'_>, - method_name: Symbol, - receiver: &Expr<'_>, - cloned_before_iter: bool, -) -> bool { - if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent)) - && let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent) - && let (clone_or_copy_needed, references_to_binding) = clone_or_copy_needed(cx, pat, body) - && !clone_or_copy_needed - && let Some(receiver_snippet) = receiver.span.get_source_text(cx) - { - // Issue 12098 - // https://github.com/rust-lang/rust-clippy/issues/12098 - // if the assignee have `mut borrow` conflict with the iteratee - // the lint should not execute, former didn't consider the mut case - - // check whether `expr` is mutable - fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - if let Some(hir_id) = path_to_local(expr) - && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) - { - matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..)) - } else { - true - } - } - - fn is_caller_or_fields_change(cx: &LateContext<'_>, body: &Expr<'_>, caller: &Expr<'_>) -> bool { - let mut change = false; - if let ExprKind::Block(block, ..) = body.kind { - for_each_expr_without_closures(block, |e| { - match e.kind { - ExprKind::Assign(assignee, _, _) | ExprKind::AssignOp(_, assignee, _) => { - change |= !can_mut_borrow_both(cx, caller, assignee); - }, - _ => {}, - } - // the return value has no effect but the function need one return value - ControlFlow::<()>::Continue(()) - }); - } - change - } - - if let ExprKind::Call(_, [child, ..]) = expr.kind { - // filter first layer of iterator - let mut child = child; - // get inner real caller requests for clone - while let ExprKind::MethodCall(_, caller, _, _) = child.kind { - child = caller; - } - if is_mutable(cx, child) && is_caller_or_fields_change(cx, body, child) { - // skip lint - return true; - } - } - - // the lint should not be executed if no violation happens - let snippet = if let ExprKind::MethodCall(maybe_iter_method_name, collection, [], _) = receiver.kind - && maybe_iter_method_name.ident.name == sym::iter - && let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator) - && let receiver_ty = cx.typeck_results().expr_ty(receiver) - && implements_trait(cx, receiver_ty, iterator_trait_id, &[]) - && let Some(iter_item_ty) = get_iterator_item_ty(cx, receiver_ty) - && let Some(into_iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator) - && let collection_ty = cx.typeck_results().expr_ty(collection) - && implements_trait(cx, collection_ty, into_iterator_trait_id, &[]) - && let Some(into_iter_item_ty) = cx.get_associated_type(collection_ty, into_iterator_trait_id, sym::Item) - && iter_item_ty == into_iter_item_ty - && let Some(collection_snippet) = collection.span.get_source_text(cx) - { - collection_snippet - } else { - receiver_snippet - }; - span_lint_and_then( - cx, - UNNECESSARY_TO_OWNED, - expr.span, - format!("unnecessary use of `{method_name}`"), - |diag| { - // If `check_into_iter_call_arg` called `check_for_loop_iter` because a call to - // a `to_owned`-like function was removed, then the next suggestion may be - // incorrect. This is because the iterator that results from the call's removal - // could hold a reference to a resource that is used mutably. See - // https://github.com/rust-lang/rust-clippy/issues/8148. - let applicability = if cloned_before_iter { - Applicability::MaybeIncorrect - } else { - Applicability::MachineApplicable - }; - - let combined = references_to_binding - .into_iter() - .chain(vec![(expr.span, snippet.to_owned())]) - .collect_vec(); - - diag.multipart_suggestion("remove any references to the binding", combined, applicability); - }, - ); - return true; - } - false -} - -/// Returns true if the named method is `IntoIterator::into_iter`. -pub fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool { - Some(callee_def_id) == cx.tcx.lang_items().into_iter_fn() -} diff --git a/clippy_lints/src/methods/unnecessary_join.rs b/clippy_lints/src/methods/unnecessary_join.rs deleted file mode 100644 index efd1a718504c..000000000000 --- a/clippy_lints/src/methods/unnecessary_join.rs +++ /dev/null @@ -1,40 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_lang_item; -use rustc_ast::ast::LitKind; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem}; -use rustc_lint::LateContext; -use rustc_middle::ty; -use rustc_span::Span; - -use super::UNNECESSARY_JOIN; - -pub(super) fn check<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'tcx>, - join_self_arg: &'tcx Expr<'tcx>, - join_arg: &'tcx Expr<'tcx>, - span: Span, -) { - let applicability = Applicability::MachineApplicable; - let collect_output_adjusted_type = cx.typeck_results().expr_ty_adjusted(join_self_arg); - if let ty::Ref(_, ref_type, _) = collect_output_adjusted_type.kind() - // the turbofish for collect is ::> - && let ty::Slice(slice) = ref_type.kind() - && is_type_lang_item(cx, *slice, LangItem::String) - // the argument for join is "" - && let ExprKind::Lit(spanned) = &join_arg.kind - && let LitKind::Str(symbol, _) = spanned.node - && symbol.is_empty() - { - span_lint_and_sugg( - cx, - UNNECESSARY_JOIN, - span.with_hi(expr.span.hi()), - r#"called `.collect::>().join("")` on an iterator"#, - "consider using", - "collect::()".to_owned(), - applicability, - ); - } -} diff --git a/clippy_lints/src/methods/unused_enumerate_index.rs b/clippy_lints/src/methods/unused_enumerate_index.rs deleted file mode 100644 index af466fe091c2..000000000000 --- a/clippy_lints/src/methods/unused_enumerate_index.rs +++ /dev/null @@ -1,139 +0,0 @@ -use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::source::{SpanRangeExt, snippet}; -use clippy_utils::{expr_or_init, is_trait_method, pat_is_wild}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, FnDecl, PatKind, TyKind}; -use rustc_lint::LateContext; -use rustc_middle::ty::AdtDef; -use rustc_span::{Span, sym}; - -use crate::loops::UNUSED_ENUMERATE_INDEX; - -/// Check for the `UNUSED_ENUMERATE_INDEX` lint outside of loops. -/// -/// The lint is declared in `clippy_lints/src/loops/mod.rs`. There, the following pattern is -/// checked: -/// ```ignore -/// for (_, x) in some_iter.enumerate() { -/// // Index is ignored -/// } -/// ``` -/// -/// This `check` function checks for chained method calls constructs where we can detect that the -/// index is unused. Currently, this checks only for the following patterns: -/// ```ignore -/// some_iter.enumerate().map_function(|(_, x)| ..) -/// let x = some_iter.enumerate(); -/// x.map_function(|(_, x)| ..) -/// ``` -/// where `map_function` is one of `all`, `any`, `filter_map`, `find_map`, `flat_map`, `for_each` or -/// `map`. -/// -/// # Preconditions -/// This function must be called not on the `enumerate` call expression itself, but on any of the -/// map functions listed above. It will ensure that `recv` is a `std::iter::Enumerate` instance and -/// that the method call is one of the `std::iter::Iterator` trait. -/// -/// * `call_expr`: The map function call expression -/// * `recv`: The receiver of the call -/// * `closure_arg`: The argument to the map function call containing the closure/function to apply -pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { - let recv_ty = cx.typeck_results().expr_ty(recv); - if let Some(recv_ty_defid) = recv_ty.ty_adt_def().map(AdtDef::did) - // If we call a method on a `std::iter::Enumerate` instance - && cx.tcx.is_diagnostic_item(sym::Enumerate, recv_ty_defid) - // If we are calling a method of the `Iterator` trait - && is_trait_method(cx, call_expr, sym::Iterator) - // And the map argument is a closure - && let ExprKind::Closure(closure) = closure_arg.kind - && let closure_body = cx.tcx.hir_body(closure.body) - // And that closure has one argument ... - && let [closure_param] = closure_body.params - // .. which is a tuple of 2 elements - && let PatKind::Tuple([index, elem], ..) = closure_param.pat.kind - // And that the first element (the index) is either `_` or unused in the body - && pat_is_wild(cx, &index.kind, closure_body) - // Try to find the initializer for `recv`. This is needed in case `recv` is a local_binding. In the - // first example below, `expr_or_init` would return `recv`. - // ``` - // iter.enumerate().map(|(_, x)| x) - // ^^^^^^^^^^^^^^^^ `recv`, a call to `std::iter::Iterator::enumerate` - // - // let binding = iter.enumerate(); - // ^^^^^^^^^^^^^^^^ `recv_init_expr` - // binding.map(|(_, x)| x) - // ^^^^^^^ `recv`, not a call to `std::iter::Iterator::enumerate` - // ``` - && let recv_init_expr = expr_or_init(cx, recv) - // Make sure the initializer is a method call. It may be that the `Enumerate` comes from something - // that we cannot control. - // This would for instance happen with: - // ``` - // external_lib::some_function_returning_enumerate().map(|(_, x)| x) - // ``` - && let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = recv_init_expr.kind - && let Some(enumerate_defid) = cx.typeck_results().type_dependent_def_id(recv_init_expr.hir_id) - // Make sure the method call is `std::iter::Iterator::enumerate`. - && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_defid) - { - // Check if the tuple type was explicit. It may be the type system _needs_ the type of the element - // that would be explicitly in the closure. - let new_closure_param = match find_elem_explicit_type_span(closure.fn_decl) { - // We have an explicit type. Get its snippet, that of the binding name, and do `binding: ty`. - // Fallback to `..` if we fail getting either snippet. - Some(ty_span) => elem - .span - .get_source_text(cx) - .and_then(|binding_name| { - ty_span - .get_source_text(cx) - .map(|ty_name| format!("{binding_name}: {ty_name}")) - }) - .unwrap_or_else(|| "..".to_string()), - // Otherwise, we have no explicit type. We can replace with the binding name of the element. - None => snippet(cx, elem.span, "..").into_owned(), - }; - - // Suggest removing the tuple from the closure and the preceding call to `enumerate`, whose span we - // can get from the `MethodCall`. - span_lint_hir_and_then( - cx, - UNUSED_ENUMERATE_INDEX, - recv_init_expr.hir_id, - enumerate_span, - "you seem to use `.enumerate()` and immediately discard the index", - |diag| { - diag.multipart_suggestion( - "remove the `.enumerate()` call", - vec![ - (closure_param.span, new_closure_param), - ( - enumerate_span.with_lo(enumerate_recv.span.source_callsite().hi()), - String::new(), - ), - ], - Applicability::MachineApplicable, - ); - }, - ); - } -} - -/// Find the span of the explicit type of the element. -/// -/// # Returns -/// If the tuple argument: -/// * Has no explicit type, returns `None` -/// * Has an explicit tuple type with an implicit element type (`(usize, _)`), returns `None` -/// * Has an explicit tuple type with an explicit element type (`(_, i32)`), returns the span for -/// the element type. -fn find_elem_explicit_type_span(fn_decl: &FnDecl<'_>) -> Option { - if let [tuple_ty] = fn_decl.inputs - && let TyKind::Tup([_idx_ty, elem_ty]) = tuple_ty.kind - && !matches!(elem_ty.kind, TyKind::Err(..) | TyKind::Infer(())) - { - Some(elem_ty.span) - } else { - None - } -} diff --git a/clippy_lints/src/methods/unwrap_expect_used.rs b/clippy_lints/src/methods/unwrap_expect_used.rs deleted file mode 100644 index 027215e3b4d7..000000000000 --- a/clippy_lints/src/methods/unwrap_expect_used.rs +++ /dev/null @@ -1,88 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::{is_never_like, is_type_diagnostic_item}; -use clippy_utils::{is_in_test, is_inside_always_const_context, is_lint_allowed}; -use rustc_hir::Expr; -use rustc_lint::{LateContext, Lint}; -use rustc_middle::ty; -use rustc_span::sym; - -use super::{EXPECT_USED, UNWRAP_USED}; - -#[derive(Clone, Copy, Eq, PartialEq)] -pub(super) enum Variant { - Unwrap, - Expect, -} - -impl Variant { - fn method_name(self, is_err: bool) -> &'static str { - match (self, is_err) { - (Variant::Unwrap, true) => "unwrap_err", - (Variant::Unwrap, false) => "unwrap", - (Variant::Expect, true) => "expect_err", - (Variant::Expect, false) => "expect", - } - } - - fn lint(self) -> &'static Lint { - match self { - Variant::Unwrap => UNWRAP_USED, - Variant::Expect => EXPECT_USED, - } - } -} - -/// Lint usage of `unwrap` or `unwrap_err` for `Result` and `unwrap()` for `Option` (and their -/// `expect` counterparts). -pub(super) fn check( - cx: &LateContext<'_>, - expr: &Expr<'_>, - recv: &Expr<'_>, - is_err: bool, - allow_unwrap_in_consts: bool, - allow_unwrap_in_tests: bool, - variant: Variant, -) { - let ty = cx.typeck_results().expr_ty(recv).peel_refs(); - - let (kind, none_value, none_prefix) = if is_type_diagnostic_item(cx, ty, sym::Option) && !is_err { - ("an `Option`", "None", "") - } else if is_type_diagnostic_item(cx, ty, sym::Result) - && let ty::Adt(_, substs) = ty.kind() - && let Some(t_or_e_ty) = substs[usize::from(!is_err)].as_type() - { - if is_never_like(t_or_e_ty) { - return; - } - - ("a `Result`", if is_err { "Ok" } else { "Err" }, "an ") - } else { - return; - }; - - let method_suffix = if is_err { "_err" } else { "" }; - - if allow_unwrap_in_tests && is_in_test(cx.tcx, expr.hir_id) { - return; - } - - if allow_unwrap_in_consts && is_inside_always_const_context(cx.tcx, expr.hir_id) { - return; - } - - span_lint_and_then( - cx, - variant.lint(), - expr.span, - format!("used `{}()` on {kind} value", variant.method_name(is_err)), - |diag| { - diag.note(format!("if this value is {none_prefix}`{none_value}`, it will panic")); - - if variant == Variant::Unwrap && is_lint_allowed(cx, EXPECT_USED, expr.hir_id) { - diag.help(format!( - "consider using `expect{method_suffix}()` to provide a better panic message" - )); - } - }, - ); -} diff --git a/clippy_lints/src/methods/zst_offset.rs b/clippy_lints/src/methods/zst_offset.rs deleted file mode 100644 index 102fa7bc8953..000000000000 --- a/clippy_lints/src/methods/zst_offset.rs +++ /dev/null @@ -1,15 +0,0 @@ -use clippy_utils::diagnostics::span_lint; -use rustc_hir as hir; -use rustc_lint::LateContext; -use rustc_middle::ty; - -use super::ZST_OFFSET; - -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { - if let ty::RawPtr(ty, _) = cx.typeck_results().expr_ty(recv).kind() - && let Ok(layout) = cx.tcx.layout_of(cx.typing_env().as_query_input(*ty)) - && layout.is_zst() - { - span_lint(cx, ZST_OFFSET, expr.span, "offset calculation on zero-sized value"); - } -} diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 380ddea4e1e8..e5b20c0e0a13 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -176,6 +176,33 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { } }, + ExprKind::MethodCall(path, recv, [arg], _) => { + if matches!( + path.ident.name, + sym::map | sym::map_err | sym::map_break | sym::map_continue + ) && has_eligible_receiver(cx, recv, e) + && (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From)) + && let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind() + && let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice() + && same_type_and_consts(from_ty, to_ty) + { + span_lint_and_then( + cx, + USELESS_CONVERSION, + e.span.with_lo(recv.span.hi()), + format!("useless conversion to the same type: `{from_ty}`"), + |diag| { + diag.suggest_remove_item( + cx, + e.span.with_lo(recv.span.hi()), + "consider removing", + Applicability::MachineApplicable, + ); + }, + ); + } + }, + ExprKind::MethodCall(name, recv, [], _) => { if is_trait_method(cx, e, sym::Into) && name.ident.name == sym::into { let a = cx.typeck_results().expr_ty(e); @@ -412,32 +439,6 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { } } -/// Check if `arg` is a `Into::into` or `From::from` applied to `receiver` to give `expr`, through a -/// higher-order mapping function. -pub fn check_function_application(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { - if has_eligible_receiver(cx, recv, expr) - && (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From)) - && let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind() - && let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice() - && same_type_and_consts(from_ty, to_ty) - { - span_lint_and_then( - cx, - USELESS_CONVERSION, - expr.span.with_lo(recv.span.hi()), - format!("useless conversion to the same type: `{from_ty}`"), - |diag| { - diag.suggest_remove_item( - cx, - expr.span.with_lo(recv.span.hi()), - "consider removing", - Applicability::MachineApplicable, - ); - }, - ); - } -} - fn has_eligible_receiver(cx: &LateContext<'_>, recv: &Expr<'_>, expr: &Expr<'_>) -> bool { if is_inherent_method_call(cx, expr) { matches!( diff --git a/clippy_lints_internal/src/lib.rs b/clippy_lints_internal/src/lib.rs index 43cde86504f5..c19fba229aaa 100644 --- a/clippy_lints_internal/src/lib.rs +++ b/clippy_lints_internal/src/lib.rs @@ -50,7 +50,6 @@ static LINTS: &[&Lint] = &[ derive_deserialize_allowing_unknown::DERIVE_DESERIALIZE_ALLOWING_UNKNOWN, lint_without_lint_pass::DEFAULT_LINT, lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE, - lint_without_lint_pass::LINT_WITHOUT_LINT_PASS, lint_without_lint_pass::MISSING_CLIPPY_VERSION_ATTRIBUTE, msrv_attr_impl::MISSING_MSRV_ATTR_IMPL, outer_expn_data_pass::OUTER_EXPN_EXPN_DATA, @@ -69,7 +68,7 @@ pub fn register_lints(store: &mut LintStore) { store.register_late_pass(|_| Box::new(collapsible_calls::CollapsibleCalls)); store.register_late_pass(|_| Box::new(derive_deserialize_allowing_unknown::DeriveDeserializeAllowingUnknown)); store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::::default()); + store.register_late_pass(|_| Box::new(lint_without_lint_pass::LintWithoutLintPass)); store.register_late_pass(|_| Box::new(unnecessary_def_path::UnnecessaryDefPath)); store.register_late_pass(|_| Box::new(outer_expn_data_pass::OuterExpnDataPass)); store.register_late_pass(|_| Box::new(msrv_attr_impl::MsrvAttrImpl)); diff --git a/clippy_lints_internal/src/lint_without_lint_pass.rs b/clippy_lints_internal/src/lint_without_lint_pass.rs index 45a866030b2d..3b782c28e92a 100644 --- a/clippy_lints_internal/src/lint_without_lint_pass.rs +++ b/clippy_lints_internal/src/lint_without_lint_pass.rs @@ -1,49 +1,14 @@ use crate::internal_paths; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; -use clippy_utils::is_lint_allowed; -use clippy_utils::macros::root_macro_call_first_node; use rustc_ast::ast::LitKind; -use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::hir_id::CRATE_HIR_ID; -use rustc_hir::intravisit::Visitor; -use rustc_hir::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind}; +use rustc_hir::{ExprKind, Item, MutTy, Mutability, TyKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::hir::nested_filter; -use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Spanned; +use rustc_span::sym; use rustc_span::symbol::Symbol; -use rustc_span::{Span, sym}; - -declare_tool_lint! { - /// ### What it does - /// Ensures every lint is associated to a `LintPass`. - /// - /// ### Why is this bad? - /// The compiler only knows lints via a `LintPass`. Without - /// putting a lint to a `LintPass::lint_vec()`'s return, the compiler will not - /// know the name of the lint. - /// - /// ### Known problems - /// Only checks for lints associated using the `declare_lint_pass!` and - /// `impl_lint_pass!` macros. - /// - /// ### Example - /// ```rust,ignore - /// declare_lint! { pub LINT_1, ... } - /// declare_lint! { pub LINT_2, ... } - /// declare_lint! { pub FORGOTTEN_LINT, ... } - /// // ... - /// declare_lint_pass!(Pass => [LINT_1, LINT_2]); - /// // missing FORGOTTEN_LINT - /// ``` - pub clippy::LINT_WITHOUT_LINT_PASS, - Warn, - "declaring a lint without associating it in a LintPass", - report_in_external_macro: true - -} declare_tool_lint! { /// ### What it does @@ -90,109 +55,47 @@ declare_tool_lint! { report_in_external_macro: true } -#[derive(Clone, Debug, Default)] -pub struct LintWithoutLintPass { - declared_lints: FxIndexMap, - registered_lints: FxIndexSet, -} - -impl_lint_pass!(LintWithoutLintPass => [ +declare_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<'_>) { - if let hir::ItemKind::Static(Mutability::Not, ident, ty, body_id) = item.kind { - if is_lint_ref_type(cx, ty) { - check_invalid_clippy_version_attribute(cx, item); + if let hir::ItemKind::Static(Mutability::Not, ident, ty, body_id) = item.kind + && is_lint_ref_type(cx, ty) + { + check_invalid_clippy_version_attribute(cx, item); - let expr = &cx.tcx.hir_body(body_id).value; - let fields = if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind - && let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind - { - struct_fields - } else { - return; - }; + let expr = &cx.tcx.hir_body(body_id).value; + let fields = if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind + && let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind + { + struct_fields + } else { + return; + }; - let field = fields - .iter() - .find(|f| f.ident.as_str() == "desc") - .expect("lints must have a description field"); + let field = fields + .iter() + .find(|f| f.ident.as_str() == "desc") + .expect("lints must have a description field"); - if let ExprKind::Lit(Spanned { - node: LitKind::Str(sym, _), - .. - }) = field.expr.kind - { - let sym_str = sym.as_str(); - if sym_str == "default lint description" { - span_lint( - cx, - DEFAULT_LINT, - item.span, - format!("the lint `{}` has the default lint description", ident.name), - ); - } - self.declared_lints.insert(ident.name, item.span); - } - } - } else if let Some(macro_call) = root_macro_call_first_node(cx, item) { - if !matches!( - cx.tcx.item_name(macro_call.def_id).as_str(), - "impl_lint_pass" | "declare_lint_pass" - ) { - return; - } - if let hir::ItemKind::Impl(hir::Impl { - of_trait: None, - items: impl_item_refs, + if let ExprKind::Lit(Spanned { + node: LitKind::Str(sym, _), .. - }) = item.kind + }) = field.expr.kind { - let mut collector = LintCollector { - output: &mut self.registered_lints, - cx, - }; - let body = cx.tcx.hir_body_owned_by( - impl_item_refs - .iter() - .find(|iiref| iiref.ident.as_str() == "lint_vec") - .expect("LintPass needs to implement lint_vec") - .id - .owner_id - .def_id, - ); - collector.visit_expr(body.value); - } - } - } - - fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { - if is_lint_allowed(cx, LINT_WITHOUT_LINT_PASS, CRATE_HIR_ID) { - return; - } - - for (lint_name, &lint_span) in &self.declared_lints { - // When using the `declare_tool_lint!` macro, the original `lint_span`'s - // file points to "". - // `compiletest-rs` thinks that's an error in a different file and - // just ignores it. This causes the test in compile-fail/lint_pass - // not able to capture the error. - // Therefore, we need to climb the macro expansion tree and find the - // actual span that invoked `declare_tool_lint!`: - let lint_span = lint_span.ctxt().outer_expn_data().call_site; - - if !self.registered_lints.contains(lint_name) { - span_lint( - cx, - LINT_WITHOUT_LINT_PASS, - lint_span, - format!("the lint `{lint_name}` is not added to any `LintPass`"), - ); + let sym_str = sym.as_str(); + if sym_str == "default lint description" { + span_lint( + cx, + DEFAULT_LINT, + item.span, + format!("the lint `{}` has the default lint description", ident.name), + ); + } } } } @@ -261,22 +164,3 @@ pub(super) fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item< } }) } - -struct LintCollector<'a, 'tcx> { - output: &'a mut FxIndexSet, - cx: &'a LateContext<'tcx>, -} - -impl<'tcx> Visitor<'tcx> for LintCollector<'_, 'tcx> { - type NestedFilter = nested_filter::All; - - fn visit_path(&mut self, path: &Path<'_>, _: HirId) { - if path.segments.len() == 1 { - self.output.insert(path.segments[0].ident.name); - } - } - - fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { - self.cx.tcx - } -} diff --git a/clippy_lints_methods/Cargo.toml b/clippy_lints_methods/Cargo.toml new file mode 100644 index 000000000000..31301d9f16b6 --- /dev/null +++ b/clippy_lints_methods/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "clippy_lints_methods" +version = "0.1.90" +description = "A bunch of helpful lints to avoid common pitfalls in Rust" +repository = "https://github.com/rust-lang/rust-clippy" +readme = "README.md" +license = "MIT OR Apache-2.0" +keywords = ["clippy", "lint", "plugin"] +edition = "2024" + +[dependencies] +clippy_config = { path = "../clippy_config" } +clippy_utils = { path = "../clippy_utils" } +declare_clippy_lint = { path = "../declare_clippy_lint" } +itertools = "0.12" + +[package.metadata.rust-analyzer] +# This crate uses #[feature(rustc_private)] +rustc_private = true diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints_methods/src/bind_instead_of_map.rs similarity index 84% rename from clippy_lints/src/methods/bind_instead_of_map.rs rename to clippy_lints_methods/src/bind_instead_of_map.rs index f8520c23ea50..21b1d66932d4 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints_methods/src/bind_instead_of_map.rs @@ -1,4 +1,4 @@ -use super::{BIND_INSTEAD_OF_MAP, contains_return}; +use super::contains_return; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::peel_blocks; use clippy_utils::source::{snippet, snippet_with_context}; @@ -10,6 +10,38 @@ use rustc_hir::{LangItem, QPath}; use rustc_lint::LateContext; use rustc_span::Span; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` + /// or `_.or_else(|x| Err(y))`. + /// + /// ### Why is this bad? + /// This can be written more concisely as `_.map(|x| y)` or `_.map_err(|x| y)`. + /// + /// ### Example + /// ```no_run + /// # fn opt() -> Option<&'static str> { Some("42") } + /// # fn res() -> Result<&'static str, &'static str> { Ok("42") } + /// let _ = opt().and_then(|s| Some(s.len())); + /// let _ = res().and_then(|s| if s.len() == 42 { Ok(10) } else { Ok(20) }); + /// let _ = res().or_else(|s| if s.len() == 42 { Err(10) } else { Err(20) }); + /// ``` + /// + /// The correct use would be: + /// + /// ```no_run + /// # fn opt() -> Option<&'static str> { Some("42") } + /// # fn res() -> Result<&'static str, &'static str> { Ok("42") } + /// let _ = opt().map(|s| s.len()); + /// 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)`" +} + pub(super) fn check_and_then_some( cx: &LateContext<'_>, expr: &hir::Expr<'_>, diff --git a/clippy_lints/src/methods/bytecount.rs b/clippy_lints_methods/src/bytecount.rs similarity index 70% rename from clippy_lints/src/methods/bytecount.rs rename to clippy_lints_methods/src/bytecount.rs index 0498f317442a..7b5b46ba6651 100644 --- a/clippy_lints/src/methods/bytecount.rs +++ b/clippy_lints_methods/src/bytecount.rs @@ -9,7 +9,36 @@ use rustc_lint::LateContext; use rustc_middle::ty::{self, UintTy}; use rustc_span::sym; -use super::NAIVE_BYTECOUNT; +declare_clippy_lint! { + /// ### What it does + /// Checks for naive byte counts + /// + /// ### Why is this bad? + /// The [`bytecount`](https://crates.io/crates/bytecount) + /// crate has methods to count your bytes faster, especially for large slices. + /// + /// ### Known problems + /// If you have predominantly small slices, the + /// `bytecount::count(..)` method may actually be slower. However, if you can + /// ensure that less than 2³²-1 matches arise, the `naive_count_32(..)` can be + /// faster in those cases. + /// + /// ### Example + /// ```no_run + /// # let vec = vec![1_u8]; + /// let count = vec.iter().filter(|x| **x == 0u8).count(); + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// # let vec = vec![1_u8]; + /// let count = bytecount::count(&vec, 0u8); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub NAIVE_BYTECOUNT, + pedantic, + "use of naive `.filter(|&x| x == y).count()` to count byte values" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/bytes_count_to_len.rs b/clippy_lints_methods/src/bytes_count_to_len.rs similarity index 64% rename from clippy_lints/src/methods/bytes_count_to_len.rs rename to clippy_lints_methods/src/bytes_count_to_len.rs index a9f6a41c2357..2b01b4abeefe 100644 --- a/clippy_lints/src/methods/bytes_count_to_len.rs +++ b/clippy_lints_methods/src/bytes_count_to_len.rs @@ -5,7 +5,30 @@ use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; -use super::BYTES_COUNT_TO_LEN; +declare_clippy_lint! { + /// ### What it does + /// It checks for `str::bytes().count()` and suggests replacing it with + /// `str::len()`. + /// + /// ### Why is this bad? + /// `str::bytes().count()` is longer and may not be as performant as using + /// `str::len()`. + /// + /// ### Example + /// ```no_run + /// "hello".bytes().count(); + /// String::from("hello").bytes().count(); + /// ``` + /// Use instead: + /// ```no_run + /// "hello".len(); + /// String::from("hello").len(); + /// ``` + #[clippy::version = "1.62.0"] + pub BYTES_COUNT_TO_LEN, + complexity, + "Using `bytes().count()` when `len()` performs the same functionality" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/bytes_nth.rs b/clippy_lints_methods/src/bytes_nth.rs similarity index 74% rename from clippy_lints/src/methods/bytes_nth.rs rename to clippy_lints_methods/src/bytes_nth.rs index 02fc09170e59..10fa38004f6d 100644 --- a/clippy_lints/src/methods/bytes_nth.rs +++ b/clippy_lints_methods/src/bytes_nth.rs @@ -6,9 +6,30 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, LangItem}; use rustc_lint::LateContext; -use crate::methods::method_call; +use crate::method_call; -use super::BYTES_NTH; +declare_clippy_lint! { + /// ### What it does + /// Checks for the use of `.bytes().nth()`. + /// + /// ### Why is this bad? + /// `.as_bytes().get()` is more efficient and more + /// readable. + /// + /// ### Example + /// ```no_run + /// "Hello".bytes().nth(3); + /// ``` + /// + /// Use instead: + /// ```no_run + /// "Hello".as_bytes().get(3); + /// ``` + #[clippy::version = "1.52.0"] + pub BYTES_NTH, + style, + "replace `.bytes().nth()` with `.as_bytes().get()`" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, n_arg: &'tcx Expr<'tcx>) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); diff --git a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs b/clippy_lints_methods/src/case_sensitive_file_extension_comparisons.rs similarity index 78% rename from clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs rename to clippy_lints_methods/src/case_sensitive_file_extension_comparisons.rs index 292fa08b5984..01100a119c27 100644 --- a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs +++ b/clippy_lints_methods/src/case_sensitive_file_extension_comparisons.rs @@ -10,7 +10,33 @@ use rustc_lint::LateContext; use rustc_span::Span; use rustc_span::source_map::Spanned; -use super::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `ends_with` with possible file extensions + /// and suggests to use a case-insensitive approach instead. + /// + /// ### Why is this bad? + /// `ends_with` is case-sensitive and may not detect files with a valid extension. + /// + /// ### Example + /// ```no_run + /// fn is_rust_file(filename: &str) -> bool { + /// filename.ends_with(".rs") + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn is_rust_file(filename: &str) -> bool { + /// let filename = std::path::Path::new(filename); + /// filename.extension() + /// .map_or(false, |ext| ext.eq_ignore_ascii_case("rs")) + /// } + /// ``` + #[clippy::version = "1.51.0"] + pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + pedantic, + "Checks for calls to ends_with with case-sensitive file extensions" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/chars_cmp.rs b/clippy_lints_methods/src/chars_cmp.rs new file mode 100644 index 000000000000..456853ae5576 --- /dev/null +++ b/clippy_lints_methods/src/chars_cmp.rs @@ -0,0 +1,181 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{method_chain_args, path_def_id, sym}; +use rustc_errors::Applicability; +use rustc_lint::{LateContext, Lint}; +use rustc_middle::ty; +use rustc_span::Symbol; +use {rustc_ast as ast, rustc_hir as hir}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.chars().next()` on a `str` to check + /// if it starts with a given char. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.starts_with(_)`. + /// + /// ### Example + /// ```no_run + /// let name = "foo"; + /// if name.chars().next() == Some('_') {}; + /// ``` + /// + /// Use instead: + /// ```no_run + /// 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" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.chars().last()` or + /// `_.chars().next_back()` on a `str` to check if it ends with a given char. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.ends_with(_)`. + /// + /// ### Example + /// ```no_run + /// # let name = "_"; + /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-'); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let name = "_"; + /// 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" +} + +/// Checks for the `CHARS_NEXT_CMP` lint. +pub(super) fn check_next(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { + check(cx, info, &[sym::chars, sym::next], CHARS_NEXT_CMP, "starts_with") +} + +/// Checks for the `CHARS_NEXT_CMP` lint with `unwrap()`. +pub(super) fn check_next_unwrap(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { + check_unwrap( + cx, + info, + &[sym::chars, sym::next, sym::unwrap], + CHARS_NEXT_CMP, + "starts_with", + ) +} + +/// Checks for the `CHARS_LAST_CMP` lint. +pub(super) fn check_last(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { + if check(cx, info, &[sym::chars, sym::last], CHARS_LAST_CMP, "ends_with") { + true + } else { + check(cx, info, &[sym::chars, sym::next_back], CHARS_LAST_CMP, "ends_with") + } +} + +/// Checks for the `CHARS_LAST_CMP` lint with `unwrap()`. +pub(super) fn check_last_unwrap(cx: &LateContext<'_>, info: &crate::BinaryExprInfo<'_>) -> bool { + if check_unwrap( + cx, + info, + &[sym::chars, sym::last, sym::unwrap], + CHARS_LAST_CMP, + "ends_with", + ) { + true + } else { + check_unwrap( + cx, + info, + &[sym::chars, sym::next_back, sym::unwrap], + CHARS_LAST_CMP, + "ends_with", + ) + } +} + +/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints. +fn check( + cx: &LateContext<'_>, + info: &crate::BinaryExprInfo<'_>, + chain_methods: &[Symbol], + lint: &'static Lint, + suggest: &str, +) -> bool { + if let Some(args) = method_chain_args(info.chain, chain_methods) + && let hir::ExprKind::Call(fun, [arg_char]) = info.other.kind + && let Some(id) = path_def_id(cx, fun).map(|ctor_id| cx.tcx.parent(ctor_id)) + && Some(id) == cx.tcx.lang_items().option_some_variant() + { + let mut applicability = Applicability::MachineApplicable; + let self_ty = cx.typeck_results().expr_ty_adjusted(args[0].0).peel_refs(); + + if *self_ty.kind() != ty::Str { + return false; + } + + span_lint_and_sugg( + cx, + lint, + info.expr.span, + format!("you should use the `{suggest}` method"), + "like this", + format!( + "{}{}.{suggest}({})", + if info.eq { "" } else { "!" }, + snippet_with_applicability(cx, args[0].0.span, "..", &mut applicability), + snippet_with_applicability(cx, arg_char.span, "..", &mut applicability) + ), + applicability, + ); + + return true; + } + + false +} + +/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints with `unwrap()`. +fn check_unwrap( + cx: &LateContext<'_>, + info: &crate::BinaryExprInfo<'_>, + chain_methods: &[Symbol], + lint: &'static Lint, + suggest: &str, +) -> bool { + if let Some(args) = method_chain_args(info.chain, chain_methods) + && let hir::ExprKind::Lit(lit) = info.other.kind + && let ast::LitKind::Char(c) = lit.node + { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + lint, + info.expr.span, + format!("you should use the `{suggest}` method"), + "like this", + format!( + "{}{}.{suggest}('{}')", + if info.eq { "" } else { "!" }, + snippet_with_applicability(cx, args[0].0.span, "..", &mut applicability), + c.escape_default() + ), + applicability, + ); + + true + } else { + false + } +} diff --git a/clippy_lints/src/methods/clear_with_drain.rs b/clippy_lints_methods/src/clear_with_drain.rs similarity index 76% rename from clippy_lints/src/methods/clear_with_drain.rs rename to clippy_lints_methods/src/clear_with_drain.rs index 6e24cabca8bb..9f4230022dee 100644 --- a/clippy_lints/src/methods/clear_with_drain.rs +++ b/clippy_lints_methods/src/clear_with_drain.rs @@ -7,7 +7,30 @@ use rustc_lint::LateContext; use rustc_span::Span; use rustc_span::symbol::sym; -use super::CLEAR_WITH_DRAIN; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.drain(..)` for the sole purpose of clearing a container. + /// + /// ### Why is this bad? + /// This creates an unnecessary iterator that is dropped immediately. + /// + /// Calling `.clear()` also makes the intent clearer. + /// + /// ### Example + /// ```no_run + /// let mut v = vec![1, 2, 3]; + /// v.drain(..); + /// ``` + /// Use instead: + /// ```no_run + /// let mut v = vec![1, 2, 3]; + /// v.clear(); + /// ``` + #[clippy::version = "1.70.0"] + pub CLEAR_WITH_DRAIN, + nursery, + "calling `drain` in order to `clear` a container" +} // Add `String` here when it is added to diagnostic items const ACCEPTABLE_TYPES_WITH_ARG: [rustc_span::Symbol; 2] = [sym::Vec, sym::VecDeque]; diff --git a/clippy_lints/src/methods/clone_on_copy.rs b/clippy_lints_methods/src/clone_on_copy.rs similarity index 88% rename from clippy_lints/src/methods/clone_on_copy.rs rename to clippy_lints_methods/src/clone_on_copy.rs index 2ecf3eb89798..ae912c4bdbca 100644 --- a/clippy_lints/src/methods/clone_on_copy.rs +++ b/clippy_lints_methods/src/clone_on_copy.rs @@ -9,7 +9,23 @@ use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_middle::ty::{self}; use rustc_span::symbol::{Symbol, sym}; -use super::CLONE_ON_COPY; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.clone()` on a `Copy` type. + /// + /// ### Why is this bad? + /// The only reason `Copy` types implement `Clone` is for + /// generics, not for using the `clone` method on a concrete type. + /// + /// ### Example + /// ```no_run + /// 42u64.clone(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CLONE_ON_COPY, + complexity, + "using `clone` on a `Copy` type" +} /// Checks for the `CLONE_ON_COPY` lint. #[allow(clippy::too_many_lines)] diff --git a/clippy_lints/src/methods/clone_on_ref_ptr.rs b/clippy_lints_methods/src/clone_on_ref_ptr.rs similarity index 63% rename from clippy_lints/src/methods/clone_on_ref_ptr.rs rename to clippy_lints_methods/src/clone_on_ref_ptr.rs index 96e2de0dc1cb..c207ce193b4d 100644 --- a/clippy_lints/src/methods/clone_on_ref_ptr.rs +++ b/clippy_lints_methods/src/clone_on_ref_ptr.rs @@ -6,7 +6,36 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::symbol::{Symbol, sym}; -use super::CLONE_ON_REF_PTR; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.clone()` on a ref-counted pointer, + /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified + /// function syntax instead (e.g., `Rc::clone(foo)`). + /// + /// ### Why restrict this? + /// Calling `.clone()` on an `Rc`, `Arc`, or `Weak` + /// can obscure the fact that only the pointer is being cloned, not the underlying + /// data. + /// + /// ### Example + /// ```no_run + /// # use std::rc::Rc; + /// let x = Rc::new(1); + /// + /// x.clone(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::rc::Rc; + /// # let x = Rc::new(1); + /// Rc::clone(&x); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CLONE_ON_REF_PTR, + restriction, + "using `clone` on a ref-counted pointer" +} pub(super) fn check( cx: &LateContext<'_>, diff --git a/clippy_lints/src/methods/cloned_instead_of_copied.rs b/clippy_lints_methods/src/cloned_instead_of_copied.rs similarity index 69% rename from clippy_lints/src/methods/cloned_instead_of_copied.rs rename to clippy_lints_methods/src/cloned_instead_of_copied.rs index f50fb627b89a..6a309b6dd966 100644 --- a/clippy_lints/src/methods/cloned_instead_of_copied.rs +++ b/clippy_lints_methods/src/cloned_instead_of_copied.rs @@ -8,7 +8,28 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Span, sym}; -use super::CLONED_INSTEAD_OF_COPIED; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `cloned()` on an `Iterator` or `Option` where + /// `copied()` could be used instead. + /// + /// ### Why is this bad? + /// `copied()` is better because it guarantees that the type being cloned + /// implements `Copy`. + /// + /// ### Example + /// ```no_run + /// [1, 2, 3].iter().cloned(); + /// ``` + /// Use instead: + /// ```no_run + /// [1, 2, 3].iter().copied(); + /// ``` + #[clippy::version = "1.53.0"] + pub CLONED_INSTEAD_OF_COPIED, + pedantic, + "used `cloned` where `copied` could be used instead" +} pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, msrv: Msrv) { let recv_ty = cx.typeck_results().expr_ty_adjusted(recv); diff --git a/clippy_lints/src/methods/collapsible_str_replace.rs b/clippy_lints_methods/src/collapsible_str_replace.rs similarity index 80% rename from clippy_lints/src/methods/collapsible_str_replace.rs rename to clippy_lints_methods/src/collapsible_str_replace.rs index 6d0b944df55d..e057290ad8b0 100644 --- a/clippy_lints/src/methods/collapsible_str_replace.rs +++ b/clippy_lints_methods/src/collapsible_str_replace.rs @@ -1,3 +1,4 @@ +use super::method_call; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::visitors::for_each_expr_without_closures; @@ -8,7 +9,31 @@ use rustc_hir as hir; use rustc_lint::LateContext; use std::collections::VecDeque; -use super::{COLLAPSIBLE_STR_REPLACE, method_call}; +declare_clippy_lint! { + /// ### What it does + /// Checks for consecutive calls to `str::replace` (2 or more) + /// that can be collapsed into a single call. + /// + /// ### Why is this bad? + /// Consecutive `str::replace` calls scan the string multiple times + /// with repetitive code. + /// + /// ### Example + /// ```no_run + /// let hello = "hesuo worpd" + /// .replace('s', "l") + /// .replace("u", "l") + /// .replace('p', "l"); + /// ``` + /// Use instead: + /// ```no_run + /// let hello = "hesuo worpd".replace(['s', 'u', 'p'], "l"); + /// ``` + #[clippy::version = "1.65.0"] + pub COLLAPSIBLE_STR_REPLACE, + perf, + "collapse consecutive calls to str::replace (2 or more) into a single call" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/declared_lints.rs b/clippy_lints_methods/src/declared_lints.rs new file mode 100644 index 000000000000..a16b547841db --- /dev/null +++ b/clippy_lints_methods/src/declared_lints.rs @@ -0,0 +1,155 @@ +// This file was generated by `cargo dev update_lints`. +// Use that command to update this file and do not edit by hand. +// Manual edits will be overwritten. + +pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ + crate::NEW_RET_NO_SELF_INFO, + crate::SHOULD_IMPLEMENT_TRAIT_INFO, + crate::bind_instead_of_map::BIND_INSTEAD_OF_MAP_INFO, + crate::bytecount::NAIVE_BYTECOUNT_INFO, + crate::bytes_count_to_len::BYTES_COUNT_TO_LEN_INFO, + crate::bytes_nth::BYTES_NTH_INFO, + crate::case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS_INFO, + crate::chars_cmp::CHARS_LAST_CMP_INFO, + crate::chars_cmp::CHARS_NEXT_CMP_INFO, + crate::clear_with_drain::CLEAR_WITH_DRAIN_INFO, + crate::clone_on_copy::CLONE_ON_COPY_INFO, + crate::clone_on_ref_ptr::CLONE_ON_REF_PTR_INFO, + crate::cloned_instead_of_copied::CLONED_INSTEAD_OF_COPIED_INFO, + crate::collapsible_str_replace::COLLAPSIBLE_STR_REPLACE_INFO, + crate::double_ended_iterator_last::DOUBLE_ENDED_ITERATOR_LAST_INFO, + crate::drain_collect::DRAIN_COLLECT_INFO, + crate::err_expect::ERR_EXPECT_INFO, + crate::expect_fun_call::EXPECT_FUN_CALL_INFO, + crate::extend_with_drain::EXTEND_WITH_DRAIN_INFO, + crate::filetype_is_file::FILETYPE_IS_FILE_INFO, + crate::filter_map::MANUAL_FILTER_MAP_INFO, + crate::filter_map::MANUAL_FIND_MAP_INFO, + crate::filter_map::OPTION_FILTER_MAP_INFO, + crate::filter_map::RESULT_FILTER_MAP_INFO, + crate::filter_map_bool_then::FILTER_MAP_BOOL_THEN_INFO, + crate::filter_map_identity::FILTER_MAP_IDENTITY_INFO, + crate::filter_map_next::FILTER_MAP_NEXT_INFO, + crate::filter_next::FILTER_NEXT_INFO, + crate::flat_map_identity::FLAT_MAP_IDENTITY_INFO, + crate::flat_map_option::FLAT_MAP_OPTION_INFO, + crate::format_collect::FORMAT_COLLECT_INFO, + crate::from_iter_instead_of_collect::FROM_ITER_INSTEAD_OF_COLLECT_INFO, + crate::get_first::GET_FIRST_INFO, + crate::get_last_with_len::GET_LAST_WITH_LEN_INFO, + crate::get_unwrap::GET_UNWRAP_INFO, + crate::implicit_clone::IMPLICIT_CLONE_INFO, + crate::inefficient_to_string::INEFFICIENT_TO_STRING_INFO, + crate::inspect_for_each::INSPECT_FOR_EACH_INFO, + crate::into_iter_on_ref::INTO_ITER_ON_REF_INFO, + crate::io_other_error::IO_OTHER_ERROR_INFO, + crate::ip_constant::IP_CONSTANT_INFO, + crate::is_digit_ascii_radix::IS_DIGIT_ASCII_RADIX_INFO, + crate::is_empty::CONST_IS_EMPTY_INFO, + crate::iter_cloned_collect::ITER_CLONED_COLLECT_INFO, + crate::iter_count::ITER_COUNT_INFO, + crate::iter_filter::ITER_FILTER_IS_OK_INFO, + crate::iter_filter::ITER_FILTER_IS_SOME_INFO, + crate::iter_kv_map::ITER_KV_MAP_INFO, + crate::iter_next_slice::ITER_NEXT_SLICE_INFO, + crate::iter_nth::ITER_NTH_INFO, + crate::iter_nth_zero::ITER_NTH_ZERO_INFO, + crate::iter_on_single_or_empty_collections::ITER_ON_EMPTY_COLLECTIONS_INFO, + crate::iter_on_single_or_empty_collections::ITER_ON_SINGLE_ITEMS_INFO, + crate::iter_out_of_bounds::ITER_OUT_OF_BOUNDS_INFO, + crate::iter_overeager_cloned::ITER_OVEREAGER_CLONED_INFO, + crate::iter_overeager_cloned::REDUNDANT_ITER_CLONED_INFO, + crate::iter_skip_next::ITER_SKIP_NEXT_INFO, + crate::iter_skip_zero::ITER_SKIP_ZERO_INFO, + crate::iter_with_drain::ITER_WITH_DRAIN_INFO, + crate::iterator_step_by_zero::ITERATOR_STEP_BY_ZERO_INFO, + crate::join_absolute_paths::JOIN_ABSOLUTE_PATHS_INFO, + crate::manual_c_str_literals::MANUAL_C_STR_LITERALS_INFO, + crate::manual_contains::MANUAL_CONTAINS_INFO, + crate::manual_inspect::MANUAL_INSPECT_INFO, + crate::manual_is_variant_and::MANUAL_IS_VARIANT_AND_INFO, + crate::manual_next_back::MANUAL_NEXT_BACK_INFO, + crate::manual_ok_or::MANUAL_OK_OR_INFO, + crate::manual_repeat_n::MANUAL_REPEAT_N_INFO, + crate::manual_saturating_arithmetic::MANUAL_SATURATING_ARITHMETIC_INFO, + crate::manual_str_repeat::MANUAL_STR_REPEAT_INFO, + crate::manual_try_fold::MANUAL_TRY_FOLD_INFO, + crate::map_all_any_identity::MAP_ALL_ANY_IDENTITY_INFO, + crate::map_clone::MAP_CLONE_INFO, + crate::map_collect_result_unit::MAP_COLLECT_RESULT_UNIT_INFO, + crate::map_err_ignore::MAP_ERR_IGNORE_INFO, + crate::map_flatten::MAP_FLATTEN_INFO, + crate::map_identity::MAP_IDENTITY_INFO, + crate::map_unwrap_or::MAP_UNWRAP_OR_INFO, + crate::map_with_unused_argument_over_ranges::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO, + crate::mut_mutex_lock::MUT_MUTEX_LOCK_INFO, + crate::needless_as_bytes::NEEDLESS_AS_BYTES_INFO, + crate::needless_character_iteration::NEEDLESS_CHARACTER_ITERATION_INFO, + crate::needless_collect::NEEDLESS_COLLECT_INFO, + crate::needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF_INFO, + crate::needless_option_take::NEEDLESS_OPTION_TAKE_INFO, + crate::no_effect_replace::NO_EFFECT_REPLACE_INFO, + crate::obfuscated_if_else::OBFUSCATED_IF_ELSE_INFO, + crate::ok_expect::OK_EXPECT_INFO, + crate::open_options::NONSENSICAL_OPEN_OPTIONS_INFO, + crate::open_options::SUSPICIOUS_OPEN_OPTIONS_INFO, + crate::option_as_ref_cloned::OPTION_AS_REF_CLONED_INFO, + crate::option_as_ref_deref::OPTION_AS_REF_DEREF_INFO, + crate::option_map_or_none::OPTION_MAP_OR_NONE_INFO, + crate::option_map_or_none::RESULT_MAP_OR_INTO_OPTION_INFO, + crate::or_fun_call::OR_FUN_CALL_INFO, + crate::or_fun_call::UNWRAP_OR_DEFAULT_INFO, + crate::or_then_unwrap::OR_THEN_UNWRAP_INFO, + crate::path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE_INFO, + crate::path_ends_with_ext::PATH_ENDS_WITH_EXT_INFO, + crate::range_zip_with_len::RANGE_ZIP_WITH_LEN_INFO, + crate::read_line_without_trim::READ_LINE_WITHOUT_TRIM_INFO, + crate::readonly_write_lock::READONLY_WRITE_LOCK_INFO, + crate::redundant_as_str::REDUNDANT_AS_STR_INFO, + crate::repeat_once::REPEAT_ONCE_INFO, + crate::return_and_then::RETURN_AND_THEN_INFO, + crate::search_is_some::SEARCH_IS_SOME_INFO, + crate::seek_from_current::SEEK_FROM_CURRENT_INFO, + crate::seek_to_start_instead_of_rewind::SEEK_TO_START_INSTEAD_OF_REWIND_INFO, + crate::single_char_add_str::SINGLE_CHAR_ADD_STR_INFO, + crate::skip_while_next::SKIP_WHILE_NEXT_INFO, + crate::sliced_string_as_bytes::SLICED_STRING_AS_BYTES_INFO, + crate::stable_sort_primitive::STABLE_SORT_PRIMITIVE_INFO, + crate::str_split::STR_SPLIT_AT_NEWLINE_INFO, + crate::str_splitn::MANUAL_SPLIT_ONCE_INFO, + crate::str_splitn::NEEDLESS_SPLITN_INFO, + crate::string_extend_chars::STRING_EXTEND_CHARS_INFO, + crate::string_lit_chars_any::STRING_LIT_CHARS_ANY_INFO, + crate::suspicious_command_arg_space::SUSPICIOUS_COMMAND_ARG_SPACE_INFO, + crate::suspicious_map::SUSPICIOUS_MAP_INFO, + crate::suspicious_splitn::SUSPICIOUS_SPLITN_INFO, + crate::suspicious_to_owned::SUSPICIOUS_TO_OWNED_INFO, + crate::swap_with_temporary::SWAP_WITH_TEMPORARY_INFO, + crate::type_id_on_box::TYPE_ID_ON_BOX_INFO, + crate::unbuffered_bytes::UNBUFFERED_BYTES_INFO, + crate::uninit_assumed_init::UNINIT_ASSUMED_INIT_INFO, + crate::unit_hash::UNIT_HASH_INFO, + crate::unnecessary_fallible_conversions::UNNECESSARY_FALLIBLE_CONVERSIONS_INFO, + crate::unnecessary_filter_map::UNNECESSARY_FILTER_MAP_INFO, + crate::unnecessary_filter_map::UNNECESSARY_FIND_MAP_INFO, + crate::unnecessary_first_then_check::UNNECESSARY_FIRST_THEN_CHECK_INFO, + crate::unnecessary_fold::UNNECESSARY_FOLD_INFO, + crate::unnecessary_get_then_check::UNNECESSARY_GET_THEN_CHECK_INFO, + crate::unnecessary_join::UNNECESSARY_JOIN_INFO, + crate::unnecessary_lazy_eval::UNNECESSARY_LAZY_EVALUATIONS_INFO, + crate::unnecessary_literal_unwrap::UNNECESSARY_LITERAL_UNWRAP_INFO, + crate::unnecessary_map_or::UNNECESSARY_MAP_OR_INFO, + crate::unnecessary_min_or_max::UNNECESSARY_MIN_OR_MAX_INFO, + crate::unnecessary_result_map_or_else::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO, + crate::unnecessary_sort_by::UNNECESSARY_SORT_BY_INFO, + crate::unnecessary_to_owned::UNNECESSARY_TO_OWNED_INFO, + crate::unwrap_expect_used::EXPECT_USED_INFO, + crate::unwrap_expect_used::UNWRAP_USED_INFO, + crate::useless_asref::USELESS_ASREF_INFO, + crate::useless_nonzero_new_unchecked::USELESS_NONZERO_NEW_UNCHECKED_INFO, + crate::vec_resize_to_zero::VEC_RESIZE_TO_ZERO_INFO, + crate::verbose_file_reads::VERBOSE_FILE_READS_INFO, + crate::waker_clone_wake::WAKER_CLONE_WAKE_INFO, + crate::wrong_self_convention::WRONG_SELF_CONVENTION_INFO, + crate::zst_offset::ZST_OFFSET_INFO, +]; diff --git a/clippy_lints/src/methods/double_ended_iterator_last.rs b/clippy_lints_methods/src/double_ended_iterator_last.rs similarity index 80% rename from clippy_lints/src/methods/double_ended_iterator_last.rs rename to clippy_lints_methods/src/double_ended_iterator_last.rs index 6d841853fbe5..ca7bbf56a3a9 100644 --- a/clippy_lints/src/methods/double_ended_iterator_last.rs +++ b/clippy_lints_methods/src/double_ended_iterator_last.rs @@ -7,7 +7,31 @@ use rustc_lint::LateContext; use rustc_middle::ty::Instance; use rustc_span::Span; -use super::DOUBLE_ENDED_ITERATOR_LAST; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `Iterator::last` being called on a `DoubleEndedIterator`, which can be replaced + /// with `DoubleEndedIterator::next_back`. + /// + /// ### Why is this bad? + /// + /// `Iterator::last` is implemented by consuming the iterator, which is unnecessary if + /// the iterator is a `DoubleEndedIterator`. Since Rust traits do not allow specialization, + /// `Iterator::last` cannot be optimized for `DoubleEndedIterator`. + /// + /// ### Example + /// ```no_run + /// let last_arg = "echo hello world".split(' ').last(); + /// ``` + /// Use instead: + /// ```no_run + /// let last_arg = "echo hello world".split(' ').next_back(); + /// ``` + #[clippy::version = "1.86.0"] + pub DOUBLE_ENDED_ITERATOR_LAST, + perf, + "using `Iterator::last` on a `DoubleEndedIterator`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Expr<'_>, call_span: Span) { let typeck = cx.typeck_results(); diff --git a/clippy_lints/src/methods/drain_collect.rs b/clippy_lints_methods/src/drain_collect.rs similarity index 72% rename from clippy_lints/src/methods/drain_collect.rs rename to clippy_lints_methods/src/drain_collect.rs index cbf713a3b17c..3255d700383f 100644 --- a/clippy_lints/src/methods/drain_collect.rs +++ b/clippy_lints_methods/src/drain_collect.rs @@ -1,4 +1,3 @@ -use crate::methods::DRAIN_COLLECT; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::ty::is_type_lang_item; @@ -10,6 +9,42 @@ use rustc_middle::ty; use rustc_middle::ty::Ty; use rustc_span::{Symbol, sym}; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `.drain()` that clear the collection, immediately followed by a call to `.collect()`. + /// + /// > "Collection" in this context refers to any type with a `drain` method: + /// > `Vec`, `VecDeque`, `BinaryHeap`, `HashSet`,`HashMap`, `String` + /// + /// ### Why is this bad? + /// Using `mem::take` is faster as it avoids the allocation. + /// When using `mem::take`, the old collection is replaced with an empty one and ownership of + /// the old collection is returned. + /// + /// ### Known issues + /// `mem::take(&mut vec)` is almost equivalent to `vec.drain(..).collect()`, except that + /// it also moves the **capacity**. The user might have explicitly written it this way + /// to keep the capacity on the original `Vec`. + /// + /// ### Example + /// ```no_run + /// fn remove_all(v: &mut Vec) -> Vec { + /// v.drain(..).collect() + /// } + /// ``` + /// Use instead: + /// ```no_run + /// use std::mem; + /// fn remove_all(v: &mut Vec) -> Vec { + /// mem::take(v) + /// } + /// ``` + #[clippy::version = "1.72.0"] + pub DRAIN_COLLECT, + perf, + "calling `.drain(..).collect()` to move all elements into a new collection" +} + /// Checks if both types match the given diagnostic item, e.g.: /// /// `vec![1,2].drain(..).collect::>()` diff --git a/clippy_lints/src/methods/err_expect.rs b/clippy_lints_methods/src/err_expect.rs similarity index 68% rename from clippy_lints/src/methods/err_expect.rs rename to clippy_lints_methods/src/err_expect.rs index 91ddaca07d8b..d89924cee4ac 100644 --- a/clippy_lints/src/methods/err_expect.rs +++ b/clippy_lints_methods/src/err_expect.rs @@ -1,4 +1,3 @@ -use super::ERR_EXPECT; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::ty::{has_debug_impl, is_type_diagnostic_item}; @@ -8,6 +7,29 @@ use rustc_middle::ty; use rustc_middle::ty::Ty; use rustc_span::{Span, sym}; +declare_clippy_lint! { + /// ### What it does + /// Checks for `.err().expect()` calls on the `Result` type. + /// + /// ### Why is this bad? + /// `.expect_err()` can be called directly to avoid the extra type conversion from `err()`. + /// + /// ### Example + /// ```should_panic + /// let x: Result = Ok(10); + /// x.err().expect("Testing err().expect()"); + /// ``` + /// Use instead: + /// ```should_panic + /// let x: Result = Ok(10); + /// x.expect_err("Testing expect_err"); + /// ``` + #[clippy::version = "1.62.0"] + pub ERR_EXPECT, + style, + r#"using `.err().expect("")` when `.expect_err("")` can be used"# +} + pub(super) fn check( cx: &LateContext<'_>, _expr: &rustc_hir::Expr<'_>, diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints_methods/src/expect_fun_call.rs similarity index 84% rename from clippy_lints/src/methods/expect_fun_call.rs rename to clippy_lints_methods/src/expect_fun_call.rs index 82e5a6d5a412..ec0c9a45eb8f 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints_methods/src/expect_fun_call.rs @@ -10,7 +10,43 @@ use rustc_span::symbol::sym; use rustc_span::{Span, Symbol}; use std::borrow::Cow; -use super::EXPECT_FUN_CALL; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`, + /// etc., and suggests to use `unwrap_or_else` instead + /// + /// ### Why is this bad? + /// The function will always be called. + /// + /// ### Known problems + /// If the function has side-effects, not calling it will + /// change the semantics of the program, but you shouldn't rely on that anyway. + /// + /// ### Example + /// ```no_run + /// # let foo = Some(String::new()); + /// # let err_code = "418"; + /// # let err_msg = "I'm a teapot"; + /// foo.expect(&format!("Err {}: {}", err_code, err_msg)); + /// + /// // or + /// + /// # let foo = Some(String::new()); + /// foo.expect(format!("Err {}: {}", err_code, err_msg).as_str()); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let foo = Some(String::new()); + /// # let err_code = "418"; + /// # 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" +} /// Checks for the `EXPECT_FUN_CALL` lint. #[allow(clippy::too_many_lines)] diff --git a/clippy_lints/src/methods/extend_with_drain.rs b/clippy_lints_methods/src/extend_with_drain.rs similarity index 71% rename from clippy_lints/src/methods/extend_with_drain.rs rename to clippy_lints_methods/src/extend_with_drain.rs index db60061904f6..b7490260660d 100644 --- a/clippy_lints/src/methods/extend_with_drain.rs +++ b/clippy_lints_methods/src/extend_with_drain.rs @@ -6,7 +6,33 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem}; use rustc_lint::LateContext; -use super::EXTEND_WITH_DRAIN; +declare_clippy_lint! { + /// ### What it does + /// Checks for occurrences where one vector gets extended instead of append + /// + /// ### Why is this bad? + /// Using `append` instead of `extend` is more concise and faster + /// + /// ### Example + /// ```no_run + /// let mut a = vec![1, 2, 3]; + /// let mut b = vec![4, 5, 6]; + /// + /// a.extend(b.drain(..)); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let mut a = vec![1, 2, 3]; + /// let mut b = vec![4, 5, 6]; + /// + /// 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 vector to another" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); diff --git a/clippy_lints_methods/src/filetype_is_file.rs b/clippy_lints_methods/src/filetype_is_file.rs new file mode 100644 index 000000000000..20a88cd6b50f --- /dev/null +++ b/clippy_lints_methods/src/filetype_is_file.rs @@ -0,0 +1,81 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::get_parent_expr; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::{Span, sym}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `FileType::is_file()`. + /// + /// ### Why restrict this? + /// When people testing a file type with `FileType::is_file` + /// they are testing whether a path is something they can get bytes from. But + /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover + /// symlink in windows. Using `!FileType::is_dir()` is a better way to that intention. + /// + /// ### Example + /// ```no_run + /// # || { + /// let metadata = std::fs::metadata("foo.txt")?; + /// let filetype = metadata.file_type(); + /// + /// if filetype.is_file() { + /// // read file + /// } + /// # Ok::<_, std::io::Error>(()) + /// # }; + /// ``` + /// + /// should be written as: + /// + /// ```no_run + /// # || { + /// let metadata = std::fs::metadata("foo.txt")?; + /// let filetype = metadata.file_type(); + /// + /// if !filetype.is_dir() { + /// // read file + /// } + /// # 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" +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { + let ty = cx.typeck_results().expr_ty(recv); + + if !is_type_diagnostic_item(cx, ty, sym::FileType) { + return; + } + + let span: Span; + let verb: &str; + let lint_unary: &str; + let help_unary: &str; + if let Some(parent) = get_parent_expr(cx, expr) + && let hir::ExprKind::Unary(op, _) = parent.kind + && op == hir::UnOp::Not + { + lint_unary = "!"; + verb = "denies"; + help_unary = ""; + span = parent.span; + } else { + lint_unary = ""; + verb = "covers"; + help_unary = "!"; + span = expr.span; + } + let lint_msg = format!("`{lint_unary}FileType::is_file()` only {verb} regular files"); + + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then(cx, FILETYPE_IS_FILE, span, lint_msg, |diag| { + diag.help(format!("use `{help_unary}FileType::is_dir()` instead")); + }); +} diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints_methods/src/filter_map.rs similarity index 85% rename from clippy_lints/src/methods/filter_map.rs rename to clippy_lints_methods/src/filter_map.rs index 4dd54cf19745..d246a55ce098 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints_methods/src/filter_map.rs @@ -13,7 +13,103 @@ use rustc_middle::ty::adjustment::Adjust; use rustc_span::Span; use rustc_span::symbol::{Ident, Symbol}; -use super::{MANUAL_FILTER_MAP, MANUAL_FIND_MAP, OPTION_FILTER_MAP, RESULT_FILTER_MAP}; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.filter(_).map(_)` that can be written more simply + /// as `filter_map(_)`. + /// + /// ### Why is this bad? + /// Redundant code in the `filter` and `map` operations is poor style and + /// less performant. + /// + /// ### Example + /// ```no_run + /// (0_i32..10) + /// .filter(|n| n.checked_add(1).is_some()) + /// .map(|n| n.checked_add(1).unwrap()); + /// ``` + /// + /// Use instead: + /// ```no_run + /// (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(_)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.find(_).map(_)` that can be written more simply + /// as `find_map(_)`. + /// + /// ### Why is this bad? + /// Redundant code in the `find` and `map` operations is poor style and + /// less performant. + /// + /// ### Example + /// ```no_run + /// (0_i32..10) + /// .find(|n| n.checked_add(1).is_some()) + /// .map(|n| n.checked_add(1).unwrap()); + /// ``` + /// + /// Use instead: + /// ```no_run + /// (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(_)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for iterators of `Option`s using `.filter(Option::is_some).map(Option::unwrap)` that may + /// be replaced with a `.flatten()` call. + /// + /// ### Why is this bad? + /// `Option` is like a collection of 0-1 things, so `flatten` + /// automatically does this without suspicious-looking `unwrap` calls. + /// + /// ### Example + /// ```no_run + /// let _ = std::iter::empty::>().filter(Option::is_some).map(Option::unwrap); + /// ``` + /// Use instead: + /// ```no_run + /// 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" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for iterators of `Result`s using `.filter(Result::is_ok).map(Result::unwrap)` that may + /// be replaced with a `.flatten()` call. + /// + /// ### Why is this bad? + /// `Result` implements `IntoIterator`. This means that `Result` can be flattened + /// automatically without suspicious-looking `unwrap` calls. + /// + /// ### Example + /// ```no_run + /// let _ = std::iter::empty::>().filter(Result::is_ok).map(Result::unwrap); + /// ``` + /// Use instead: + /// ```no_run + /// let _ = std::iter::empty::>().flatten(); + /// ``` + #[clippy::version = "1.77.0"] + pub RESULT_FILTER_MAP, + complexity, + "filtering `Result` for `Ok` then force-unwrapping, which can be one type-safe operation" +} fn is_method(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol) -> bool { match &expr.kind { diff --git a/clippy_lints/src/methods/filter_map_bool_then.rs b/clippy_lints_methods/src/filter_map_bool_then.rs similarity index 80% rename from clippy_lints/src/methods/filter_map_bool_then.rs rename to clippy_lints_methods/src/filter_map_bool_then.rs index 965993808f6b..e25375abb500 100644 --- a/clippy_lints/src/methods/filter_map_bool_then.rs +++ b/clippy_lints_methods/src/filter_map_bool_then.rs @@ -1,4 +1,3 @@ -use super::FILTER_MAP_BOOL_THEN; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::is_copy; @@ -14,6 +13,37 @@ use rustc_middle::ty::Binder; use rustc_middle::ty::adjustment::Adjust; use rustc_span::{Span, sym}; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `bool::then` in `Iterator::filter_map`. + /// + /// ### Why is this bad? + /// This can be written with `filter` then `map` instead, which would reduce nesting and + /// separates the filtering from the transformation phase. This comes with no cost to + /// performance and is just cleaner. + /// + /// ### Limitations + /// Does not lint `bool::then_some`, as it eagerly evaluates its arguments rather than lazily. + /// This can create differing behavior, so better safe than sorry. + /// + /// ### Example + /// ```no_run + /// # fn really_expensive_fn(i: i32) -> i32 { i } + /// # let v = vec![]; + /// _ = v.into_iter().filter_map(|i| (i % 2 == 0).then(|| really_expensive_fn(i))); + /// ``` + /// Use instead: + /// ```no_run + /// # fn really_expensive_fn(i: i32) -> i32 { i } + /// # let v = vec![]; + /// _ = v.into_iter().filter(|i| i % 2 == 0).map(|i| really_expensive_fn(i)); + /// ``` + #[clippy::version = "1.73.0"] + pub FILTER_MAP_BOOL_THEN, + style, + "checks for usage of `bool::then` in `Iterator::filter_map`" +} + pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &Expr<'_>, call_span: Span) { if !expr.span.in_external_macro(cx.sess().source_map()) && is_trait_method(cx, expr, sym::Iterator) diff --git a/clippy_lints/src/methods/filter_map_identity.rs b/clippy_lints_methods/src/filter_map_identity.rs similarity index 70% rename from clippy_lints/src/methods/filter_map_identity.rs rename to clippy_lints_methods/src/filter_map_identity.rs index b04d761d4860..f70cdf157bdd 100644 --- a/clippy_lints/src/methods/filter_map_identity.rs +++ b/clippy_lints_methods/src/filter_map_identity.rs @@ -6,7 +6,28 @@ use rustc_hir::ExprKind; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::FILTER_MAP_IDENTITY; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `filter_map(|x| x)`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely by using `flatten`. + /// + /// ### Example + /// ```no_run + /// # let iter = vec![Some(1)].into_iter(); + /// iter.filter_map(|x| x); + /// ``` + /// Use instead: + /// ```no_run + /// # 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" +} fn is_identity(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { if is_expr_untyped_identity_function(cx, expr) { diff --git a/clippy_lints/src/methods/filter_map_next.rs b/clippy_lints_methods/src/filter_map_next.rs similarity index 66% rename from clippy_lints/src/methods/filter_map_next.rs rename to clippy_lints_methods/src/filter_map_next.rs index 9f3c346042ff..8536b756c45e 100644 --- a/clippy_lints/src/methods/filter_map_next.rs +++ b/clippy_lints_methods/src/filter_map_next.rs @@ -7,7 +7,28 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::FILTER_MAP_NEXT; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.filter_map(_).next()`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.find_map(_)`. + /// + /// ### Example + /// ```no_run + /// (0..3).filter_map(|x| if x == 2 { Some(x) } else { None }).next(); + /// ``` + /// Can be written as + /// + /// ```no_run + /// (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" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/filter_next.rs b/clippy_lints_methods/src/filter_next.rs similarity index 81% rename from clippy_lints/src/methods/filter_next.rs rename to clippy_lints_methods/src/filter_next.rs index 6c1a14fc8829..a79204e1f67a 100644 --- a/clippy_lints/src/methods/filter_next.rs +++ b/clippy_lints_methods/src/filter_next.rs @@ -7,7 +7,30 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::FILTER_NEXT; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.filter(_).next()`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.find(_)`. + /// + /// ### Example + /// ```no_run + /// # let vec = vec![1]; + /// vec.iter().filter(|x| **x == 0).next(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # 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)`" +} fn path_to_local(expr: &hir::Expr<'_>) -> Option { match expr.kind { diff --git a/clippy_lints/src/methods/flat_map_identity.rs b/clippy_lints_methods/src/flat_map_identity.rs similarity index 59% rename from clippy_lints/src/methods/flat_map_identity.rs rename to clippy_lints_methods/src/flat_map_identity.rs index 0c2ecfbc8ffd..ed4b6e57e72c 100644 --- a/clippy_lints/src/methods/flat_map_identity.rs +++ b/clippy_lints_methods/src/flat_map_identity.rs @@ -5,7 +5,28 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::FLAT_MAP_IDENTITY; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `flat_map(|x| x)`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely by using `flatten`. + /// + /// ### Example + /// ```no_run + /// # let iter = vec![vec![0]].into_iter(); + /// iter.flat_map(|x| x); + /// ``` + /// Can be written as + /// ```no_run + /// # 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" +} /// lint use of `flat_map` for `Iterators` where `flatten` would be sufficient pub(super) fn check<'tcx>( diff --git a/clippy_lints_methods/src/flat_map_option.rs b/clippy_lints_methods/src/flat_map_option.rs new file mode 100644 index 000000000000..92d66ef2eb38 --- /dev/null +++ b/clippy_lints_methods/src/flat_map_option.rs @@ -0,0 +1,59 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_trait_method; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::{Span, sym}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `Iterator::flat_map()` where `filter_map()` could be + /// used instead. + /// + /// ### Why is this bad? + /// `filter_map()` is known to always produce 0 or 1 output items per input item, + /// rather than however many the inner iterator type produces. + /// Therefore, it maintains the upper bound in `Iterator::size_hint()`, + /// and communicates to the reader that the input items are not being expanded into + /// multiple output items without their having to notice that the mapping function + /// returns an `Option`. + /// + /// ### Example + /// ```no_run + /// let nums: Vec = ["1", "2", "whee!"].iter().flat_map(|x| x.parse().ok()).collect(); + /// ``` + /// Use instead: + /// ```no_run + /// 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" +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, span: Span) { + if !is_trait_method(cx, expr, sym::Iterator) { + return; + } + let arg_ty = cx.typeck_results().expr_ty_adjusted(arg); + let sig = match arg_ty.kind() { + ty::Closure(_, args) => args.as_closure().sig(), + _ if arg_ty.is_fn() => arg_ty.fn_sig(cx.tcx), + _ => return, + }; + if !is_type_diagnostic_item(cx, sig.output().skip_binder(), sym::Option) { + return; + } + span_lint_and_sugg( + cx, + FLAT_MAP_OPTION, + span, + "used `flat_map` where `filter_map` could be used instead", + "try", + "filter_map".into(), + Applicability::MachineApplicable, + ); +} diff --git a/clippy_lints/src/methods/format_collect.rs b/clippy_lints_methods/src/format_collect.rs similarity index 56% rename from clippy_lints/src/methods/format_collect.rs rename to clippy_lints_methods/src/format_collect.rs index 1b28596d50da..394effa05967 100644 --- a/clippy_lints/src/methods/format_collect.rs +++ b/clippy_lints_methods/src/format_collect.rs @@ -1,4 +1,3 @@ -use super::FORMAT_COLLECT; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{is_format_macro, root_macro_call_first_node}; use clippy_utils::ty::is_type_lang_item; @@ -6,6 +5,41 @@ use rustc_hir::{Expr, ExprKind, LangItem}; use rustc_lint::LateContext; use rustc_span::Span; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.map(|_| format!(..)).collect::()`. + /// + /// ### Why is this bad? + /// This allocates a new string for every element in the iterator. + /// This can be done more efficiently by creating the `String` once and appending to it in `Iterator::fold`, + /// using either the `write!` macro which supports exactly the same syntax as the `format!` macro, + /// or concatenating with `+` in case the iterator yields `&str`/`String`. + /// + /// Note also that `write!`-ing into a `String` can never fail, despite the return type of `write!` being `std::fmt::Result`, + /// so it can be safely ignored or unwrapped. + /// + /// ### Example + /// ```no_run + /// fn hex_encode(bytes: &[u8]) -> String { + /// bytes.iter().map(|b| format!("{b:02X}")).collect() + /// } + /// ``` + /// Use instead: + /// ```no_run + /// use std::fmt::Write; + /// fn hex_encode(bytes: &[u8]) -> String { + /// bytes.iter().fold(String::new(), |mut output, b| { + /// let _ = write!(output, "{b:02X}"); + /// output + /// }) + /// } + /// ``` + #[clippy::version = "1.73.0"] + pub FORMAT_COLLECT, + pedantic, + "`format!`ing every element in a collection, then collecting the strings into a new `String`" +} + /// Same as `peel_blocks` but only actually considers blocks that are not from an expansion. /// This is needed because always calling `peel_blocks` would otherwise remove parts of the /// `format!` macro, which would cause `root_macro_call_first_node` to return `None`. diff --git a/clippy_lints/src/methods/from_iter_instead_of_collect.rs b/clippy_lints_methods/src/from_iter_instead_of_collect.rs similarity index 70% rename from clippy_lints/src/methods/from_iter_instead_of_collect.rs rename to clippy_lints_methods/src/from_iter_instead_of_collect.rs index 045363058d19..f47b848fa7a4 100644 --- a/clippy_lints/src/methods/from_iter_instead_of_collect.rs +++ b/clippy_lints_methods/src/from_iter_instead_of_collect.rs @@ -11,7 +11,46 @@ use rustc_lint::LateContext; use rustc_middle::ty::GenericParamDefKind; use rustc_span::sym; -use super::FROM_ITER_INSTEAD_OF_COLLECT; +declare_clippy_lint! { + /// ### What it does + /// Checks for `from_iter()` function calls on types that implement the `FromIterator` + /// trait. + /// + /// ### Why is this bad? + /// If it's needed to create a collection from the contents of an iterator, the `Iterator::collect(_)` + /// method is preferred. However, when it's needed to specify the container type, + /// `Vec::from_iter(_)` can be more readable than using a turbofish (e.g. `_.collect::>()`). See + /// [FromIterator documentation](https://doc.rust-lang.org/std/iter/trait.FromIterator.html) + /// + /// ### Example + /// ```no_run + /// let five_fives = std::iter::repeat(5).take(5); + /// + /// let v = Vec::from_iter(five_fives); + /// + /// assert_eq!(v, vec![5, 5, 5, 5, 5]); + /// ``` + /// Use instead: + /// ```no_run + /// let five_fives = std::iter::repeat(5).take(5); + /// + /// let v: Vec = five_fives.collect(); + /// + /// assert_eq!(v, vec![5, 5, 5, 5, 5]); + /// ``` + /// but prefer to use + /// ```no_run + /// let numbers: Vec = FromIterator::from_iter(1..=5); + /// ``` + /// instead of + /// ```no_run + /// let numbers = (1..=5).collect::>(); + /// ``` + #[clippy::version = "1.49.0"] + pub FROM_ITER_INSTEAD_OF_COLLECT, + pedantic, + "use `.collect()` instead of `::from_iter()`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>], func: &Expr<'_>) { if is_path_diagnostic_item(cx, func, sym::from_iter_fn) diff --git a/clippy_lints/src/methods/get_first.rs b/clippy_lints_methods/src/get_first.rs similarity index 73% rename from clippy_lints/src/methods/get_first.rs rename to clippy_lints_methods/src/get_first.rs index f4465e654c2e..de723684b845 100644 --- a/clippy_lints/src/methods/get_first.rs +++ b/clippy_lints_methods/src/get_first.rs @@ -9,7 +9,31 @@ use rustc_lint::LateContext; use rustc_span::source_map::Spanned; use rustc_span::sym; -use super::GET_FIRST; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `x.get(0)` instead of + /// `x.first()` or `x.front()`. + /// + /// ### Why is this bad? + /// Using `x.first()` for `Vec`s and slices or `x.front()` + /// for `VecDeque`s is easier to read and has the same result. + /// + /// ### Example + /// ```no_run + /// let x = vec![2, 3, 5]; + /// let first_element = x.get(0); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let x = vec![2, 3, 5]; + /// let first_element = x.first(); + /// ``` + #[clippy::version = "1.63.0"] + pub GET_FIRST, + style, + "Using `x.get(0)` when `x.first()` or `x.front()` is simpler" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/get_last_with_len.rs b/clippy_lints_methods/src/get_last_with_len.rs similarity index 63% rename from clippy_lints/src/methods/get_last_with_len.rs rename to clippy_lints_methods/src/get_last_with_len.rs index 5f6fb4c821d5..f60e9cdc734e 100644 --- a/clippy_lints/src/methods/get_last_with_len.rs +++ b/clippy_lints_methods/src/get_last_with_len.rs @@ -8,7 +8,38 @@ use rustc_middle::ty; use rustc_span::source_map::Spanned; use rustc_span::sym; -use super::GET_LAST_WITH_LEN; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `x.get(x.len() - 1)` instead of + /// `x.last()`. + /// + /// ### Why is this bad? + /// Using `x.last()` is easier to read and has the same + /// result. + /// + /// Note that using `x[x.len() - 1]` is semantically different from + /// `x.last()`. Indexing into the array will panic on out-of-bounds + /// accesses, while `x.get()` and `x.last()` will return `None`. + /// + /// There is another lint (get_unwrap) that covers the case of using + /// `x.get(index).unwrap()` instead of `x[index]`. + /// + /// ### Example + /// ```no_run + /// let x = vec![2, 3, 5]; + /// let last_element = x.get(x.len() - 1); + /// ``` + /// + /// Use instead: + /// ```no_run + /// 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" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { // Argument to "get" is a subtraction diff --git a/clippy_lints/src/methods/get_unwrap.rs b/clippy_lints_methods/src/get_unwrap.rs similarity index 67% rename from clippy_lints/src/methods/get_unwrap.rs rename to clippy_lints_methods/src/get_unwrap.rs index 9daad1a8a949..fa6fef516443 100644 --- a/clippy_lints/src/methods/get_unwrap.rs +++ b/clippy_lints_methods/src/get_unwrap.rs @@ -8,7 +8,42 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::GET_UNWRAP; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.get().unwrap()` (or + /// `.get_mut().unwrap`) on a standard library type which implements `Index` + /// + /// ### Why restrict this? + /// Using the Index trait (`[]`) is more clear and more + /// concise. + /// + /// ### Known problems + /// Not a replacement for error handling: Using either + /// `.unwrap()` or the Index trait (`[]`) carries the risk of causing a `panic` + /// if the value being accessed is `None`. If the use of `.get().unwrap()` is a + /// temporary placeholder for dealing with the `Option` type, then this does + /// not mitigate the need for error handling. If there is a chance that `.get()` + /// will be `None` in your program, then it is advisable that the `None` case + /// is handled in a future refactor instead of using `.unwrap()` or the Index + /// trait. + /// + /// ### Example + /// ```no_run + /// let mut some_vec = vec![0, 1, 2, 3]; + /// let last = some_vec.get(3).unwrap(); + /// *some_vec.get_mut(0).unwrap() = 1; + /// ``` + /// The correct use would be: + /// ```no_run + /// let mut some_vec = vec![0, 1, 2, 3]; + /// 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" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints_methods/src/implicit_clone.rs similarity index 76% rename from clippy_lints/src/methods/implicit_clone.rs rename to clippy_lints_methods/src/implicit_clone.rs index 9724463f0c08..5d2cff99cfba 100644 --- a/clippy_lints/src/methods/implicit_clone.rs +++ b/clippy_lints_methods/src/implicit_clone.rs @@ -7,7 +7,31 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::Symbol; -use super::IMPLICIT_CLONE; +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of `_.to_owned()`, `vec.to_vec()`, or similar when calling `_.clone()` would be clearer. + /// + /// ### Why is this bad? + /// These methods do the same thing as `_.clone()` but may be confusing as + /// to why we are calling `to_vec` on something that is already a `Vec` or calling `to_owned` on something that is already owned. + /// + /// ### Example + /// ```no_run + /// let a = vec![1, 2, 3]; + /// let b = a.to_vec(); + /// let c = a.to_owned(); + /// ``` + /// Use instead: + /// ```no_run + /// let a = vec![1, 2, 3]; + /// 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" +} pub fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) diff --git a/clippy_lints/src/methods/inefficient_to_string.rs b/clippy_lints_methods/src/inefficient_to_string.rs similarity index 75% rename from clippy_lints/src/methods/inefficient_to_string.rs rename to clippy_lints_methods/src/inefficient_to_string.rs index 4ed7de81ea3d..9c666d8b9dbf 100644 --- a/clippy_lints/src/methods/inefficient_to_string.rs +++ b/clippy_lints_methods/src/inefficient_to_string.rs @@ -7,7 +7,29 @@ use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::{Symbol, sym}; -use super::INEFFICIENT_TO_STRING; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.to_string()` on an `&&T` where + /// `T` implements `ToString` directly (like `&&str` or `&&String`). + /// + /// ### Why is this bad? + /// This bypasses the specialized implementation of + /// `ToString` and instead goes through the more expensive string formatting + /// facilities. + /// + /// ### Example + /// ```no_run + /// // Generic implementation for `T: Display` is used (slow) + /// ["foo", "bar"].iter().map(|s| s.to_string()); + /// + /// // 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`" +} /// Checks for the `INEFFICIENT_TO_STRING` lint pub fn check( diff --git a/clippy_lints_methods/src/inspect_for_each.rs b/clippy_lints_methods/src/inspect_for_each.rs new file mode 100644 index 000000000000..504904b4344d --- /dev/null +++ b/clippy_lints_methods/src/inspect_for_each.rs @@ -0,0 +1,51 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_trait_method; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::{Span, sym}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `inspect().for_each()`. + /// + /// ### Why is this bad? + /// It is the same as performing the computation + /// inside `inspect` at the beginning of the closure in `for_each`. + /// + /// ### Example + /// ```no_run + /// [1,2,3,4,5].iter() + /// .inspect(|&x| println!("inspect the number: {}", x)) + /// .for_each(|&x| { + /// assert!(x >= 0); + /// }); + /// ``` + /// Can be written as + /// ```no_run + /// [1,2,3,4,5].iter() + /// .for_each(|&x| { + /// println!("inspect the number: {}", x); + /// assert!(x >= 0); + /// }); + /// ``` + #[clippy::version = "1.51.0"] + pub INSPECT_FOR_EACH, + complexity, + "using `.inspect().for_each()`, which can be replaced with `.for_each()`" +} + +/// lint use of `inspect().for_each()` for `Iterators` +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, inspect_span: Span) { + if is_trait_method(cx, expr, sym::Iterator) { + let msg = "called `inspect(..).for_each(..)` on an `Iterator`"; + let hint = "move the code from `inspect(..)` to `for_each(..)` and remove the `inspect(..)`"; + span_lint_and_help( + cx, + INSPECT_FOR_EACH, + inspect_span.with_hi(expr.span.hi()), + msg, + None, + hint, + ); + } +} diff --git a/clippy_lints/src/methods/into_iter_on_ref.rs b/clippy_lints_methods/src/into_iter_on_ref.rs similarity index 67% rename from clippy_lints/src/methods/into_iter_on_ref.rs rename to clippy_lints_methods/src/into_iter_on_ref.rs index bedeb63367d0..ec22c7aca761 100644 --- a/clippy_lints/src/methods/into_iter_on_ref.rs +++ b/clippy_lints_methods/src/into_iter_on_ref.rs @@ -8,7 +8,32 @@ use rustc_middle::ty::{self, Ty}; use rustc_span::Span; use rustc_span::symbol::{Symbol, sym}; -use super::INTO_ITER_ON_REF; +declare_clippy_lint! { + /// ### What it does + /// Checks for `into_iter` calls on references which should be replaced by `iter` + /// or `iter_mut`. + /// + /// ### Why is this bad? + /// Readability. Calling `into_iter` on a reference will not move out its + /// content into the resulting iterator, which is confusing. It is better just call `iter` or + /// `iter_mut` directly. + /// + /// ### Example + /// ```no_run + /// # let vec = vec![3, 4, 5]; + /// (&vec).into_iter(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let vec = vec![3, 4, 5]; + /// (&vec).iter(); + /// ``` + #[clippy::version = "1.32.0"] + pub INTO_ITER_ON_REF, + style, + "using `.into_iter()` on a reference" +} pub(super) fn check( cx: &LateContext<'_>, diff --git a/clippy_lints/src/methods/io_other_error.rs b/clippy_lints_methods/src/io_other_error.rs similarity index 70% rename from clippy_lints/src/methods/io_other_error.rs rename to clippy_lints_methods/src/io_other_error.rs index 9276261606e1..18482fdbc4b6 100644 --- a/clippy_lints/src/methods/io_other_error.rs +++ b/clippy_lints_methods/src/io_other_error.rs @@ -5,6 +5,28 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; +declare_clippy_lint! { + /// This lint warns on calling `io::Error::new(..)` with a kind of + /// `io::ErrorKind::Other`. + /// + /// ### Why is this bad? + /// Since Rust 1.74, there's the `io::Error::other(_)` shortcut. + /// + /// ### Example + /// ```no_run + /// use std::io; + /// let _ = io::Error::new(io::ErrorKind::Other, "bad".to_string()); + /// ``` + /// Use instead: + /// ```no_run + /// let _ = std::io::Error::other("bad".to_string()); + /// ``` + #[clippy::version = "1.87.0"] + pub IO_OTHER_ERROR, + style, + "calling `std::io::Error::new(std::io::ErrorKind::Other, _)`" +} + pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args: &[Expr<'_>], msrv: Msrv) { if let [error_kind, error] = args && !expr.span.from_expansion() @@ -19,7 +41,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args { span_lint_and_then( cx, - super::IO_OTHER_ERROR, + IO_OTHER_ERROR, expr.span, "this can be `std::io::Error::other(_)`", |diag| { diff --git a/clippy_lints/src/methods/ip_constant.rs b/clippy_lints_methods/src/ip_constant.rs similarity index 59% rename from clippy_lints/src/methods/ip_constant.rs rename to clippy_lints_methods/src/ip_constant.rs index 83803fba6a13..608a9787749b 100644 --- a/clippy_lints/src/methods/ip_constant.rs +++ b/clippy_lints_methods/src/ip_constant.rs @@ -6,7 +6,41 @@ use rustc_lint::LateContext; use rustc_span::sym; use smallvec::SmallVec; -use super::IP_CONSTANT; +declare_clippy_lint! { + /// ### What it does + /// Checks for IP addresses that could be replaced with predefined constants such as + /// `Ipv4Addr::new(127, 0, 0, 1)` instead of using the appropriate constants. + /// + /// ### Why is this bad? + /// Using specific IP addresses like `127.0.0.1` or `::1` is less clear and less maintainable than using the + /// predefined constants `Ipv4Addr::LOCALHOST` or `Ipv6Addr::LOCALHOST`. These constants improve code + /// readability, make the intent explicit, and are less error-prone. + /// + /// ### Example + /// ```no_run + /// use std::net::{Ipv4Addr, Ipv6Addr}; + /// + /// // IPv4 loopback + /// let addr_v4 = Ipv4Addr::new(127, 0, 0, 1); + /// + /// // IPv6 loopback + /// let addr_v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); + /// ``` + /// Use instead: + /// ```no_run + /// use std::net::{Ipv4Addr, Ipv6Addr}; + /// + /// // IPv4 loopback + /// let addr_v4 = Ipv4Addr::LOCALHOST; + /// + /// // IPv6 loopback + /// let addr_v6 = Ipv6Addr::LOCALHOST; + /// ``` + #[clippy::version = "1.89.0"] + pub IP_CONSTANT, + pedantic, + "hardcoded localhost IP address" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>]) { if let ExprKind::Path(QPath::TypeRelative( diff --git a/clippy_lints/src/methods/is_digit_ascii_radix.rs b/clippy_lints_methods/src/is_digit_ascii_radix.rs similarity index 60% rename from clippy_lints/src/methods/is_digit_ascii_radix.rs rename to clippy_lints_methods/src/is_digit_ascii_radix.rs index 9c32e9ac539d..fd9cd51ed61e 100644 --- a/clippy_lints/src/methods/is_digit_ascii_radix.rs +++ b/clippy_lints_methods/src/is_digit_ascii_radix.rs @@ -1,4 +1,3 @@ -use super::IS_DIGIT_ASCII_RADIX; use clippy_utils::consts::{ConstEvalCtxt, FullInt}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; @@ -7,6 +6,33 @@ use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; +declare_clippy_lint! { + /// ### What it does + /// Finds usages of [`char::is_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that + /// can be replaced with [`is_ascii_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or + /// [`is_ascii_hexdigit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit). + /// + /// ### Why is this bad? + /// `is_digit(..)` is slower and requires specifying the radix. + /// + /// ### Example + /// ```no_run + /// let c: char = '6'; + /// c.is_digit(10); + /// c.is_digit(16); + /// ``` + /// Use instead: + /// ```no_run + /// let c: char = '6'; + /// c.is_ascii_digit(); + /// c.is_ascii_hexdigit(); + /// ``` + #[clippy::version = "1.62.0"] + pub IS_DIGIT_ASCII_RADIX, + style, + "use of `char::is_digit(..)` with literal radix of 10 or 16" +} + pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, diff --git a/clippy_lints/src/methods/is_empty.rs b/clippy_lints_methods/src/is_empty.rs similarity index 73% rename from clippy_lints/src/methods/is_empty.rs rename to clippy_lints_methods/src/is_empty.rs index 545bef1a4c5b..1ea3814c88f1 100644 --- a/clippy_lints/src/methods/is_empty.rs +++ b/clippy_lints_methods/src/is_empty.rs @@ -6,7 +6,31 @@ use rustc_hir::{Expr, HirId}; use rustc_lint::{LateContext, LintContext}; use rustc_span::sym; -use super::CONST_IS_EMPTY; +declare_clippy_lint! { + /// ### What it does + /// It identifies calls to `.is_empty()` on constant values. + /// + /// ### Why is this bad? + /// String literals and constant values are known at compile time. Checking if they + /// are empty will always return the same value. This might not be the intention of + /// the expression. + /// + /// ### Example + /// ```no_run + /// let value = ""; + /// if value.is_empty() { + /// println!("the string is empty"); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// println!("the string is empty"); + /// ``` + #[clippy::version = "1.79.0"] + pub CONST_IS_EMPTY, + suspicious, + "is_empty() called on strings known at compile time" +} /// Expression whose initialization depend on a constant conditioned by a `#[cfg(…)]` directive will /// not trigger the lint. diff --git a/clippy_lints/src/methods/iter_cloned_collect.rs b/clippy_lints_methods/src/iter_cloned_collect.rs similarity index 66% rename from clippy_lints/src/methods/iter_cloned_collect.rs rename to clippy_lints_methods/src/iter_cloned_collect.rs index b4ab313fe98d..8f301054def7 100644 --- a/clippy_lints/src/methods/iter_cloned_collect.rs +++ b/clippy_lints_methods/src/iter_cloned_collect.rs @@ -1,4 +1,4 @@ -use crate::methods::utils::derefs_to_slice; +use crate::utils::derefs_to_slice; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::ty::{get_iterator_item_ty, is_type_diagnostic_item}; use rustc_errors::Applicability; @@ -7,7 +7,29 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Symbol, sym}; -use super::ITER_CLONED_COLLECT; +declare_clippy_lint! { + /// ### What it does + /// Checks for the use of `.cloned().collect()` on slice to + /// create a `Vec`. + /// + /// ### Why is this bad? + /// `.to_vec()` is clearer + /// + /// ### Example + /// ```no_run + /// let s = [1, 2, 3, 4, 5]; + /// let s2: Vec = s[..].iter().cloned().collect(); + /// ``` + /// The better use would be: + /// ```no_run + /// 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`" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/iter_count.rs b/clippy_lints_methods/src/iter_count.rs similarity index 71% rename from clippy_lints/src/methods/iter_count.rs rename to clippy_lints_methods/src/iter_count.rs index 6b64cc8b50ae..42d88c4c5a5d 100644 --- a/clippy_lints/src/methods/iter_count.rs +++ b/clippy_lints_methods/src/iter_count.rs @@ -7,7 +7,34 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::{Symbol, sym}; -use super::ITER_COUNT; +declare_clippy_lint! { + /// ### What it does + /// Checks for the use of `.iter().count()`. + /// + /// ### Why is this bad? + /// `.len()` is more efficient and more + /// readable. + /// + /// ### Example + /// ```no_run + /// let some_vec = vec![0, 1, 2, 3]; + /// + /// some_vec.iter().count(); + /// &some_vec[..].iter().count(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let some_vec = vec![0, 1, 2, 3]; + /// + /// some_vec.len(); + /// &some_vec[..].len(); + /// ``` + #[clippy::version = "1.52.0"] + pub ITER_COUNT, + complexity, + "replace `.iter().count()` with `.len()`" +} pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, iter_method: Symbol) { let ty = cx.typeck_results().expr_ty(recv); diff --git a/clippy_lints/src/methods/iter_filter.rs b/clippy_lints_methods/src/iter_filter.rs similarity index 82% rename from clippy_lints/src/methods/iter_filter.rs rename to clippy_lints_methods/src/iter_filter.rs index adeff375c8aa..7a209e1571d7 100644 --- a/clippy_lints/src/methods/iter_filter.rs +++ b/clippy_lints_methods/src/iter_filter.rs @@ -1,18 +1,63 @@ -use clippy_utils::ty::get_iterator_item_ty; -use hir::ExprKind; -use rustc_lint::{LateContext, LintContext}; - -use super::{ITER_FILTER_IS_OK, ITER_FILTER_IS_SOME}; - use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{indent_of, reindent_multiline}; +use clippy_utils::ty::get_iterator_item_ty; use clippy_utils::{get_parent_expr, is_trait_method, peel_blocks, span_contains_comment, sym}; +use hir::ExprKind; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::QPath; +use rustc_lint::{LateContext, LintContext}; use rustc_span::Span; use rustc_span::symbol::{Ident, Symbol}; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.filter(Option::is_some)` that may be replaced with a `.flatten()` call. + /// This lint will require additional changes to the follow-up calls as it affects the type. + /// + /// ### Why is this bad? + /// This pattern is often followed by manual unwrapping of the `Option`. The simplification + /// results in more readable and succinct code without the need for manual unwrapping. + /// + /// ### Example + /// ```no_run + /// vec![Some(1)].into_iter().filter(Option::is_some); + /// + /// ``` + /// Use instead: + /// ```no_run + /// vec![Some(1)].into_iter().flatten(); + /// ``` + #[clippy::version = "1.77.0"] + pub ITER_FILTER_IS_SOME, + pedantic, + "filtering an iterator over `Option`s for `Some` can be achieved with `flatten`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.filter(Result::is_ok)` that may be replaced with a `.flatten()` call. + /// This lint will require additional changes to the follow-up calls as it affects the type. + /// + /// ### Why is this bad? + /// This pattern is often followed by manual unwrapping of `Result`. The simplification + /// results in more readable and succinct code without the need for manual unwrapping. + /// + /// ### Example + /// ```no_run + /// vec![Ok::(1)].into_iter().filter(Result::is_ok); + /// + /// ``` + /// Use instead: + /// ```no_run + /// vec![Ok::(1)].into_iter().flatten(); + /// ``` + #[clippy::version = "1.77.0"] + pub ITER_FILTER_IS_OK, + pedantic, + "filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`" +} + /// /// Returns true if the expression is a method call to `method_name` /// e.g. `a.method_name()` or `Option::method_name`. diff --git a/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints_methods/src/iter_kv_map.rs similarity index 76% rename from clippy_lints/src/methods/iter_kv_map.rs rename to clippy_lints_methods/src/iter_kv_map.rs index cbb1b450e60f..6bf52b05ec54 100644 --- a/clippy_lints/src/methods/iter_kv_map.rs +++ b/clippy_lints_methods/src/iter_kv_map.rs @@ -1,4 +1,3 @@ -use super::ITER_KV_MAP; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; @@ -8,6 +7,37 @@ use rustc_hir::{Body, Expr, ExprKind, PatKind}; use rustc_lint::LateContext; use rustc_span::Symbol; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for iterating a map (`HashMap` or `BTreeMap`) and + /// ignoring either the keys or values. + /// + /// ### Why is this bad? + /// + /// Readability. There are `keys` and `values` methods that + /// can be used to express that we only need the keys or the values. + /// + /// ### Example + /// + /// ```no_run + /// # use std::collections::HashMap; + /// let map: HashMap = HashMap::new(); + /// let values = map.iter().map(|(_, value)| value).collect::>(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::collections::HashMap; + /// let map: HashMap = HashMap::new(); + /// let values = map.values().collect::>(); + /// ``` + #[clippy::version = "1.66.0"] + pub ITER_KV_MAP, + complexity, + "iterating on map using `iter` when `keys` or `values` would do" +} + /// lint use of: /// /// - `hashmap.iter().map(|(_, v)| v)` diff --git a/clippy_lints/src/methods/iter_next_slice.rs b/clippy_lints_methods/src/iter_next_slice.rs similarity index 81% rename from clippy_lints/src/methods/iter_next_slice.rs rename to clippy_lints_methods/src/iter_next_slice.rs index fd4650e1e45f..95e8ec2c9f6f 100644 --- a/clippy_lints/src/methods/iter_next_slice.rs +++ b/clippy_lints_methods/src/iter_next_slice.rs @@ -10,7 +10,32 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::symbol::sym; -use super::ITER_NEXT_SLICE; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `iter().next()` on a Slice or an Array + /// + /// ### Why is this bad? + /// These can be shortened into `.get()` + /// + /// ### Example + /// ```no_run + /// # let a = [1, 2, 3]; + /// # let b = vec![1, 2, 3]; + /// a[2..].iter().next(); + /// b.iter().next(); + /// ``` + /// should be written as: + /// ```no_run + /// # let a = [1, 2, 3]; + /// # let b = vec![1, 2, 3]; + /// 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()`" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, caller_expr: &'tcx hir::Expr<'_>) { // Skip lint if the `iter().next()` expression is a for loop argument, diff --git a/clippy_lints/src/methods/iter_nth.rs b/clippy_lints_methods/src/iter_nth.rs similarity index 61% rename from clippy_lints/src/methods/iter_nth.rs rename to clippy_lints_methods/src/iter_nth.rs index 1fdbd81bf240..706f8e750fc7 100644 --- a/clippy_lints/src/methods/iter_nth.rs +++ b/clippy_lints_methods/src/iter_nth.rs @@ -6,7 +6,31 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::{Span, Symbol}; -use super::ITER_NTH; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.iter().nth()`/`.iter_mut().nth()` on standard library types that have + /// equivalent `.get()`/`.get_mut()` methods. + /// + /// ### Why is this bad? + /// `.get()` and `.get_mut()` are equivalent but more concise. + /// + /// ### Example + /// ```no_run + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.iter().nth(3); + /// let bad_slice = &some_vec[..].iter().nth(3); + /// ``` + /// The correct use would be: + /// ```no_run + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.get(3); + /// let bad_slice = &some_vec[..].get(3); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub ITER_NTH, + style, + "using `.iter().nth()` on a standard library type with O(1) element access" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/iter_nth_zero.rs b/clippy_lints_methods/src/iter_nth_zero.rs similarity index 61% rename from clippy_lints/src/methods/iter_nth_zero.rs rename to clippy_lints_methods/src/iter_nth_zero.rs index 4bdf589f4876..622f0249fe58 100644 --- a/clippy_lints/src/methods/iter_nth_zero.rs +++ b/clippy_lints_methods/src/iter_nth_zero.rs @@ -8,7 +8,35 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::ITER_NTH_ZERO; +declare_clippy_lint! { + /// ### What it does + /// Checks for the use of `iter.nth(0)`. + /// + /// ### Why is this bad? + /// `iter.next()` is equivalent to + /// `iter.nth(0)`, as they both consume the next element, + /// but is more readable. + /// + /// ### Example + /// ```no_run + /// # use std::collections::HashSet; + /// # let mut s = HashSet::new(); + /// # s.insert(1); + /// let x = s.iter().nth(0); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::collections::HashSet; + /// # let mut s = HashSet::new(); + /// # 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()`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) { if let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir_get_parent_item(expr.hir_id)) diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints_methods/src/iter_on_single_or_empty_collections.rs similarity index 72% rename from clippy_lints/src/methods/iter_on_single_or_empty_collections.rs rename to clippy_lints_methods/src/iter_on_single_or_empty_collections.rs index c0366765234f..3f7332040f22 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints_methods/src/iter_on_single_or_empty_collections.rs @@ -12,7 +12,68 @@ use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::LateContext; use rustc_span::Symbol; -use super::{ITER_ON_EMPTY_COLLECTIONS, ITER_ON_SINGLE_ITEMS}; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for calls to `iter`, `iter_mut` or `into_iter` on collections containing a single item + /// + /// ### Why is this bad? + /// + /// It is simpler to use the once function from the standard library: + /// + /// ### Example + /// + /// ```no_run + /// let a = [123].iter(); + /// let b = Some(123).into_iter(); + /// ``` + /// Use instead: + /// ```no_run + /// use std::iter; + /// let a = iter::once(&123); + /// let b = iter::once(123); + /// ``` + /// + /// ### Known problems + /// + /// The type of the resulting iterator might become incompatible with its usage + #[clippy::version = "1.65.0"] + pub ITER_ON_SINGLE_ITEMS, + nursery, + "Iterator for array of length 1" +} + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for calls to `iter`, `iter_mut` or `into_iter` on empty collections + /// + /// ### Why is this bad? + /// + /// It is simpler to use the empty function from the standard library: + /// + /// ### Example + /// + /// ```no_run + /// use std::{slice, option}; + /// let a: slice::Iter = [].iter(); + /// let f: option::IntoIter = None.into_iter(); + /// ``` + /// Use instead: + /// ```no_run + /// use std::iter; + /// let a: iter::Empty = iter::empty(); + /// let b: iter::Empty = iter::empty(); + /// ``` + /// + /// ### Known problems + /// + /// The type of the resulting iterator might become incompatible with its usage + #[clippy::version = "1.65.0"] + pub ITER_ON_EMPTY_COLLECTIONS, + nursery, + "Iterator for empty array" +} enum IterType { Iter, diff --git a/clippy_lints/src/methods/iter_out_of_bounds.rs b/clippy_lints_methods/src/iter_out_of_bounds.rs similarity index 79% rename from clippy_lints/src/methods/iter_out_of_bounds.rs rename to clippy_lints_methods/src/iter_out_of_bounds.rs index 9a62b719a8fb..9a91a710d92d 100644 --- a/clippy_lints/src/methods/iter_out_of_bounds.rs +++ b/clippy_lints_methods/src/iter_out_of_bounds.rs @@ -7,7 +7,29 @@ use rustc_lint::LateContext; use rustc_middle::ty::{self}; use rustc_span::sym; -use super::ITER_OUT_OF_BOUNDS; +declare_clippy_lint! { + /// ### What it does + /// Looks for iterator combinator calls such as `.take(x)` or `.skip(x)` + /// where `x` is greater than the amount of items that an iterator will produce. + /// + /// ### Why is this bad? + /// Taking or skipping more items than there are in an iterator either creates an iterator + /// with all items from the original iterator or an iterator with no items at all. + /// This is most likely not what the user intended to do. + /// + /// ### Example + /// ```no_run + /// for _ in [1, 2, 3].iter().take(4) {} + /// ``` + /// Use instead: + /// ```no_run + /// for _ in [1, 2, 3].iter() {} + /// ``` + #[clippy::version = "1.74.0"] + pub ITER_OUT_OF_BOUNDS, + suspicious, + "calls to `.take()` or `.skip()` that are out of bounds" +} fn expr_as_u128(cx: &LateContext<'_>, e: &Expr<'_>) -> Option { if let ExprKind::Lit(lit) = expr_or_init(cx, e).kind diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints_methods/src/iter_overeager_cloned.rs similarity index 75% rename from clippy_lints/src/methods/iter_overeager_cloned.rs rename to clippy_lints_methods/src/iter_overeager_cloned.rs index f5fe4316eb0d..09c2f00869b5 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints_methods/src/iter_overeager_cloned.rs @@ -10,8 +10,61 @@ use rustc_middle::mir::{FakeReadCause, Mutability}; use rustc_middle::ty::{self, BorrowKind}; use rustc_span::{Symbol, sym}; -use super::ITER_OVEREAGER_CLONED; -use crate::redundant_clone::REDUNDANT_CLONE; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.cloned().()` where call to `.cloned()` can be postponed. + /// + /// ### Why is this bad? + /// It's often inefficient to clone all elements of an iterator, when eventually, only some + /// of them will be consumed. + /// + /// ### Known Problems + /// This `lint` removes the side of effect of cloning items in the iterator. + /// A code that relies on that side-effect could fail. + /// + /// ### Examples + /// ```no_run + /// # let vec = vec!["string".to_string()]; + /// vec.iter().cloned().take(10); + /// vec.iter().cloned().last(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let vec = vec!["string".to_string()]; + /// vec.iter().take(10).cloned(); + /// vec.iter().last().cloned(); + /// ``` + #[clippy::version = "1.60.0"] + pub ITER_OVEREAGER_CLONED, + perf, + "using `cloned()` early with `Iterator::iter()` can lead to some performance inefficiencies" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `Iterator::cloned` where the original value could be used + /// instead. + /// + /// ### Why is this bad? + /// It is not always possible for the compiler to eliminate useless allocations and + /// deallocations generated by redundant `clone()`s. + /// + /// ### Example + /// ```no_run + /// let x = vec![String::new()]; + /// let _ = x.iter().cloned().map(|x| x.len()); + /// ``` + /// Use instead: + /// ```no_run + /// let x = vec![String::new()]; + /// let _ = x.iter().map(|x| x.len()); + /// ``` + #[clippy::version = "1.90.0"] + pub REDUNDANT_ITER_CLONED, + perf, + "detects redundant calls to `Iterator::cloned`" +} #[derive(Clone, Copy)] pub(super) enum Op<'a> { @@ -96,7 +149,7 @@ pub(super) fn check<'tcx>( } let (lint, msg, trailing_clone) = match op { - Op::RmCloned | Op::NeedlessMove(_) => (REDUNDANT_CLONE, "unneeded cloning of iterator items", ""), + Op::RmCloned | Op::NeedlessMove(_) => (REDUNDANT_ITER_CLONED, "unneeded cloning of iterator items", ""), Op::LaterCloned | Op::FixClosure(_, _) => ( ITER_OVEREAGER_CLONED, "unnecessarily eager cloning of iterator items", diff --git a/clippy_lints/src/methods/iter_skip_next.rs b/clippy_lints_methods/src/iter_skip_next.rs similarity index 69% rename from clippy_lints/src/methods/iter_skip_next.rs rename to clippy_lints_methods/src/iter_skip_next.rs index fedb7c22eded..aaedb99552a6 100644 --- a/clippy_lints/src/methods/iter_skip_next.rs +++ b/clippy_lints_methods/src/iter_skip_next.rs @@ -7,7 +7,30 @@ use rustc_hir::{BindingMode, Node, PatKind}; use rustc_lint::LateContext; use rustc_span::sym; -use super::ITER_SKIP_NEXT; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.skip(x).next()` on iterators. + /// + /// ### Why is this bad? + /// `.nth(x)` is cleaner + /// + /// ### Example + /// ```no_run + /// let some_vec = vec![0, 1, 2, 3]; + /// let bad_vec = some_vec.iter().skip(3).next(); + /// let bad_slice = &some_vec[..].iter().skip(3).next(); + /// ``` + /// The correct use would be: + /// ```no_run + /// let some_vec = vec![0, 1, 2, 3]; + /// 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" +} pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) { // lint if caller of skip is an Iterator diff --git a/clippy_lints/src/methods/iter_skip_zero.rs b/clippy_lints_methods/src/iter_skip_zero.rs similarity index 65% rename from clippy_lints/src/methods/iter_skip_zero.rs rename to clippy_lints_methods/src/iter_skip_zero.rs index 39e440e784f6..770dd443b719 100644 --- a/clippy_lints/src/methods/iter_skip_zero.rs +++ b/clippy_lints_methods/src/iter_skip_zero.rs @@ -6,7 +6,26 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::sym; -use super::ITER_SKIP_ZERO; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.skip(0)` on iterators. + /// + /// ### Why is this bad? + /// This was likely intended to be `.skip(1)` to skip the first element, as `.skip(0)` does + /// nothing. If not, the call should be removed. + /// + /// ### Example + /// ```no_run + /// let v = vec![1, 2, 3]; + /// let x = v.iter().skip(0).collect::>(); + /// let y = v.iter().collect::>(); + /// assert_eq!(x, y); + /// ``` + #[clippy::version = "1.73.0"] + pub ITER_SKIP_ZERO, + correctness, + "disallows `.skip(0)`" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg_expr: &Expr<'_>) { if !expr.span.from_expansion() diff --git a/clippy_lints/src/methods/iter_with_drain.rs b/clippy_lints_methods/src/iter_with_drain.rs similarity index 58% rename from clippy_lints/src/methods/iter_with_drain.rs rename to clippy_lints_methods/src/iter_with_drain.rs index aa45969c8982..18c8d9e4b188 100644 --- a/clippy_lints/src/methods/iter_with_drain.rs +++ b/clippy_lints_methods/src/iter_with_drain.rs @@ -6,7 +6,30 @@ use rustc_lint::LateContext; use rustc_span::Span; use rustc_span::symbol::sym; -use super::ITER_WITH_DRAIN; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.drain(..)` on `Vec` and `VecDeque` for iteration. + /// + /// ### Why is this bad? + /// `.into_iter()` is simpler with better performance. + /// + /// ### Example + /// ```no_run + /// # use std::collections::HashSet; + /// let mut foo = vec![0, 1, 2, 3]; + /// let bar: HashSet = foo.drain(..).collect(); + /// ``` + /// Use instead: + /// ```no_run + /// # use std::collections::HashSet; + /// let foo = vec![0, 1, 2, 3]; + /// let bar: HashSet = foo.into_iter().collect(); + /// ``` + #[clippy::version = "1.61.0"] + pub ITER_WITH_DRAIN, + nursery, + "replace `.drain(..)` with `.into_iter()`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) { if !matches!(recv.kind, ExprKind::Field(..)) diff --git a/clippy_lints/src/methods/iterator_step_by_zero.rs b/clippy_lints_methods/src/iterator_step_by_zero.rs similarity index 52% rename from clippy_lints/src/methods/iterator_step_by_zero.rs rename to clippy_lints_methods/src/iterator_step_by_zero.rs index 90d5d9df55ee..7f72a3d8341f 100644 --- a/clippy_lints/src/methods/iterator_step_by_zero.rs +++ b/clippy_lints_methods/src/iterator_step_by_zero.rs @@ -5,7 +5,25 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::ITERATOR_STEP_BY_ZERO; +declare_clippy_lint! { + /// ### What it does + /// Checks for calling `.step_by(0)` on iterators which panics. + /// + /// ### Why is this bad? + /// This very much looks like an oversight. Use `panic!()` instead if you + /// actually intend to panic. + /// + /// ### Example + /// ```rust,should_panic + /// for x in (0..100).step_by(0) { + /// //.. + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub ITERATOR_STEP_BY_ZERO, + correctness, + "using `Iterator::step_by(0)`, which will panic at runtime" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) { if is_trait_method(cx, expr, sym::Iterator) diff --git a/clippy_lints/src/methods/join_absolute_paths.rs b/clippy_lints_methods/src/join_absolute_paths.rs similarity index 58% rename from clippy_lints/src/methods/join_absolute_paths.rs rename to clippy_lints_methods/src/join_absolute_paths.rs index 2ad070793cbb..57092542f586 100644 --- a/clippy_lints/src/methods/join_absolute_paths.rs +++ b/clippy_lints_methods/src/join_absolute_paths.rs @@ -9,7 +9,45 @@ use rustc_lint::LateContext; use rustc_span::Span; use rustc_span::symbol::sym; -use super::JOIN_ABSOLUTE_PATHS; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `Path::join` that start with a path separator (`\\` or `/`). + /// + /// ### Why is this bad? + /// If the argument to `Path::join` starts with a separator, it will overwrite + /// the original path. If this is intentional, prefer using `Path::new` instead. + /// + /// Note the behavior is platform dependent. A leading `\\` will be accepted + /// on unix systems as part of the file name + /// + /// See [`Path::join`](https://doc.rust-lang.org/std/path/struct.Path.html#method.join) + /// + /// ### Example + /// ```rust + /// # use std::path::{Path, PathBuf}; + /// let path = Path::new("/bin"); + /// let joined_path = path.join("/sh"); + /// assert_eq!(joined_path, PathBuf::from("/sh")); + /// ``` + /// + /// Use instead; + /// ```rust + /// # use std::path::{Path, PathBuf}; + /// let path = Path::new("/bin"); + /// + /// // If this was unintentional, remove the leading separator + /// let joined_path = path.join("sh"); + /// assert_eq!(joined_path, PathBuf::from("/bin/sh")); + /// + /// // If this was intentional, create a new path instead + /// let new = Path::new("/sh"); + /// assert_eq!(new, PathBuf::from("/sh")); + /// ``` + #[clippy::version = "1.76.0"] + pub JOIN_ABSOLUTE_PATHS, + suspicious, + "calls to `Path::join` which will overwrite the original path" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, join_arg: &'tcx Expr<'tcx>, expr_span: Span) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); diff --git a/clippy_lints_methods/src/lib.rs b/clippy_lints_methods/src/lib.rs new file mode 100644 index 000000000000..a9308a469dd0 --- /dev/null +++ b/clippy_lints_methods/src/lib.rs @@ -0,0 +1,1537 @@ +#![feature( + if_let_guard, + macro_metavar_expr_concat, + never_type, + rustc_private, + unwrap_infallible +)] +#![allow( + clippy::missing_docs_in_private_items, + clippy::must_use_candidate, + rustc::diagnostic_outside_of_impl, + rustc::untranslatable_diagnostic, + clippy::literal_string_with_formatting_args +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + rust_2018_idioms, + unused_lifetimes, + unused_qualifications, + rustc::internal +)] + +extern crate rustc_abi; +extern crate rustc_ast; +extern crate rustc_data_structures; +extern crate rustc_errors; +extern crate rustc_hir; +extern crate rustc_hir_typeck; +extern crate rustc_infer; +extern crate rustc_lint; +extern crate rustc_middle; +extern crate rustc_session; +extern crate rustc_span; +extern crate rustc_trait_selection; +extern crate smallvec; + +#[macro_use] +extern crate declare_clippy_lint; + +pub mod declared_lints; + +mod option_map_unwrap_or; +mod result_map_or_else_none; +mod utils; + +// begin lints modules, do not remove this comment, it's used in `update_lints` +mod bind_instead_of_map; +mod bytecount; +mod bytes_count_to_len; +mod bytes_nth; +mod case_sensitive_file_extension_comparisons; +mod chars_cmp; +mod clear_with_drain; +mod clone_on_copy; +mod clone_on_ref_ptr; +mod cloned_instead_of_copied; +mod collapsible_str_replace; +mod double_ended_iterator_last; +mod drain_collect; +mod err_expect; +mod expect_fun_call; +mod extend_with_drain; +mod filetype_is_file; +mod filter_map; +mod filter_map_bool_then; +mod filter_map_identity; +mod filter_map_next; +mod filter_next; +mod flat_map_identity; +mod flat_map_option; +mod format_collect; +mod from_iter_instead_of_collect; +mod get_first; +mod get_last_with_len; +mod get_unwrap; +mod implicit_clone; +mod inefficient_to_string; +mod inspect_for_each; +mod into_iter_on_ref; +mod io_other_error; +mod ip_constant; +mod is_digit_ascii_radix; +mod is_empty; +mod iter_cloned_collect; +mod iter_count; +mod iter_filter; +mod iter_kv_map; +mod iter_next_slice; +mod iter_nth; +mod iter_nth_zero; +mod iter_on_single_or_empty_collections; +mod iter_out_of_bounds; +mod iter_overeager_cloned; +mod iter_skip_next; +mod iter_skip_zero; +mod iter_with_drain; +mod iterator_step_by_zero; +mod join_absolute_paths; +mod manual_c_str_literals; +mod manual_contains; +mod manual_inspect; +mod manual_is_variant_and; +mod manual_next_back; +mod manual_ok_or; +mod manual_repeat_n; +mod manual_saturating_arithmetic; +mod manual_str_repeat; +mod manual_try_fold; +mod map_all_any_identity; +mod map_clone; +mod map_collect_result_unit; +mod map_err_ignore; +mod map_flatten; +mod map_identity; +mod map_unwrap_or; +mod map_with_unused_argument_over_ranges; +mod mut_mutex_lock; +mod needless_as_bytes; +mod needless_character_iteration; +mod needless_collect; +mod needless_option_as_deref; +mod needless_option_take; +mod no_effect_replace; +mod obfuscated_if_else; +mod ok_expect; +mod open_options; +mod option_as_ref_cloned; +mod option_as_ref_deref; +mod option_map_or_none; +mod or_fun_call; +mod or_then_unwrap; +mod path_buf_push_overwrite; +mod path_ends_with_ext; +mod range_zip_with_len; +mod read_line_without_trim; +mod readonly_write_lock; +mod redundant_as_str; +mod repeat_once; +mod return_and_then; +mod search_is_some; +mod seek_from_current; +mod seek_to_start_instead_of_rewind; +mod single_char_add_str; +mod skip_while_next; +mod sliced_string_as_bytes; +mod stable_sort_primitive; +mod str_split; +mod str_splitn; +mod string_extend_chars; +mod string_lit_chars_any; +mod suspicious_command_arg_space; +mod suspicious_map; +mod suspicious_splitn; +mod suspicious_to_owned; +mod swap_with_temporary; +mod type_id_on_box; +mod unbuffered_bytes; +mod uninit_assumed_init; +mod unit_hash; +mod unnecessary_fallible_conversions; +mod unnecessary_filter_map; +mod unnecessary_first_then_check; +mod unnecessary_fold; +mod unnecessary_get_then_check; +mod unnecessary_join; +mod unnecessary_lazy_eval; +mod unnecessary_literal_unwrap; +mod unnecessary_map_or; +mod unnecessary_min_or_max; +mod unnecessary_result_map_or_else; +mod unnecessary_sort_by; +mod unnecessary_to_owned; +mod unwrap_expect_used; +mod useless_asref; +mod useless_nonzero_new_unchecked; +mod vec_resize_to_zero; +mod verbose_file_reads; +mod waker_clone_wake; +mod wrong_self_convention; +mod zst_offset; +// end lints modules, do not remove this comment, it's used in `update_lints` + +use clippy_config::Conf; +use clippy_utils::consts::{ConstEvalCtxt, Constant}; +use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; +use clippy_utils::macros::FormatArgsStorage; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item}; +use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty, sym}; +pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES; +use rustc_abi::ExternAbi; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir as hir; +use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::ty::{self, TraitRef, Ty}; +use rustc_session::impl_lint_pass; +use rustc_span::{Span, Symbol, kw}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for methods that should live in a trait + /// implementation of a `std` trait (see [llogiq's blog + /// post](http://llogiq.github.io/2015/07/30/traits.html) for further + /// information) instead of an inherent implementation. + /// + /// ### Why is this bad? + /// Implementing the traits improve ergonomics for users of + /// the code, often with very little cost. Also people seeing a `mul(...)` + /// method + /// may expect `*` to work equally, so you should have good reason to disappoint + /// them. + /// + /// ### Example + /// ```no_run + /// struct X; + /// impl X { + /// fn add(&self, other: &X) -> X { + /// // .. + /// # X + /// } + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub SHOULD_IMPLEMENT_TRAIT, + style, + "defining a method that should be implementing a std trait" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `new` not returning a type that contains `Self`. + /// + /// ### Why is this bad? + /// As a convention, `new` methods are used to make a new + /// instance of a type. + /// + /// ### Example + /// In an impl block: + /// ```no_run + /// # struct Foo; + /// # struct NotAFoo; + /// impl Foo { + /// fn new() -> NotAFoo { + /// # NotAFoo + /// } + /// } + /// ``` + /// + /// ```no_run + /// # struct Foo; + /// struct Bar(Foo); + /// impl Foo { + /// // Bad. The type name must contain `Self` + /// fn new() -> Bar { + /// # Bar(Foo) + /// } + /// } + /// ``` + /// + /// ```no_run + /// # struct Foo; + /// # struct FooError; + /// impl Foo { + /// // Good. Return type contains `Self` + /// fn new() -> Result { + /// # Ok(Foo) + /// } + /// } + /// ``` + /// + /// Or in a trait definition: + /// ```no_run + /// pub trait Trait { + /// // Bad. The type name must contain `Self` + /// fn new(); + /// } + /// ``` + /// + /// ```no_run + /// pub trait Trait { + /// // Good. Return type contains `Self` + /// fn new() -> Self; + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub NEW_RET_NO_SELF, + style, + "not returning type containing `Self` in a `new` method" +} + +#[expect(clippy::struct_excessive_bools)] +struct Methods { + avoid_breaking_exported_api: bool, + msrv: Msrv, + allow_expect_in_tests: bool, + allow_unwrap_in_tests: bool, + allow_expect_in_consts: bool, + allow_unwrap_in_consts: bool, + allowed_dotfiles: FxHashSet<&'static str>, + format_args: FormatArgsStorage, +} + +impl Methods { + fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self { + let mut allowed_dotfiles: FxHashSet<_> = conf.allowed_dotfiles.iter().map(|s| &**s).collect(); + allowed_dotfiles.extend(DEFAULT_ALLOWED_DOTFILES); + + Self { + avoid_breaking_exported_api: conf.avoid_breaking_exported_api, + msrv: conf.msrv, + allow_expect_in_tests: conf.allow_expect_in_tests, + allow_unwrap_in_tests: conf.allow_unwrap_in_tests, + allow_expect_in_consts: conf.allow_expect_in_consts, + allow_unwrap_in_consts: conf.allow_unwrap_in_consts, + allowed_dotfiles, + format_args, + } + } +} + +impl_lint_pass!(Methods => [ + unwrap_expect_used::UNWRAP_USED, + unwrap_expect_used::EXPECT_USED, + SHOULD_IMPLEMENT_TRAIT, + wrong_self_convention::WRONG_SELF_CONVENTION, + ok_expect::OK_EXPECT, + or_fun_call::UNWRAP_OR_DEFAULT, + map_unwrap_or::MAP_UNWRAP_OR, + option_map_or_none::RESULT_MAP_OR_INTO_OPTION, + option_map_or_none::OPTION_MAP_OR_NONE, + bind_instead_of_map::BIND_INSTEAD_OF_MAP, + or_fun_call::OR_FUN_CALL, + or_then_unwrap::OR_THEN_UNWRAP, + expect_fun_call::EXPECT_FUN_CALL, + chars_cmp::CHARS_NEXT_CMP, + chars_cmp::CHARS_LAST_CMP, + clone_on_copy::CLONE_ON_COPY, + clone_on_ref_ptr::CLONE_ON_REF_PTR, + collapsible_str_replace::COLLAPSIBLE_STR_REPLACE, + is_empty::CONST_IS_EMPTY, + iter_overeager_cloned::ITER_OVEREAGER_CLONED, + cloned_instead_of_copied::CLONED_INSTEAD_OF_COPIED, + flat_map_option::FLAT_MAP_OPTION, + inefficient_to_string::INEFFICIENT_TO_STRING, + NEW_RET_NO_SELF, + single_char_add_str::SINGLE_CHAR_ADD_STR, + search_is_some::SEARCH_IS_SOME, + filter_next::FILTER_NEXT, + skip_while_next::SKIP_WHILE_NEXT, + filter_map_identity::FILTER_MAP_IDENTITY, + map_identity::MAP_IDENTITY, + filter_map::MANUAL_FILTER_MAP, + filter_map::MANUAL_FIND_MAP, + filter_map::OPTION_FILTER_MAP, + filter_map_next::FILTER_MAP_NEXT, + flat_map_identity::FLAT_MAP_IDENTITY, + map_flatten::MAP_FLATTEN, + iterator_step_by_zero::ITERATOR_STEP_BY_ZERO, + iter_next_slice::ITER_NEXT_SLICE, + iter_count::ITER_COUNT, + iter_nth::ITER_NTH, + iter_nth_zero::ITER_NTH_ZERO, + bytes_nth::BYTES_NTH, + iter_skip_next::ITER_SKIP_NEXT, + get_unwrap::GET_UNWRAP, + get_last_with_len::GET_LAST_WITH_LEN, + string_extend_chars::STRING_EXTEND_CHARS, + iter_cloned_collect::ITER_CLONED_COLLECT, + iter_with_drain::ITER_WITH_DRAIN, + type_id_on_box::TYPE_ID_ON_BOX, + useless_asref::USELESS_ASREF, + unnecessary_fold::UNNECESSARY_FOLD, + unnecessary_filter_map::UNNECESSARY_FILTER_MAP, + unnecessary_filter_map::UNNECESSARY_FIND_MAP, + into_iter_on_ref::INTO_ITER_ON_REF, + suspicious_map::SUSPICIOUS_MAP, + uninit_assumed_init::UNINIT_ASSUMED_INIT, + manual_saturating_arithmetic::MANUAL_SATURATING_ARITHMETIC, + zst_offset::ZST_OFFSET, + filetype_is_file::FILETYPE_IS_FILE, + option_as_ref_deref::OPTION_AS_REF_DEREF, + unnecessary_lazy_eval::UNNECESSARY_LAZY_EVALUATIONS, + map_collect_result_unit::MAP_COLLECT_RESULT_UNIT, + from_iter_instead_of_collect::FROM_ITER_INSTEAD_OF_COLLECT, + inspect_for_each::INSPECT_FOR_EACH, + implicit_clone::IMPLICIT_CLONE, + suspicious_to_owned::SUSPICIOUS_TO_OWNED, + suspicious_splitn::SUSPICIOUS_SPLITN, + manual_str_repeat::MANUAL_STR_REPEAT, + extend_with_drain::EXTEND_WITH_DRAIN, + str_splitn::MANUAL_SPLIT_ONCE, + str_splitn::NEEDLESS_SPLITN, + unnecessary_to_owned::UNNECESSARY_TO_OWNED, + unnecessary_join::UNNECESSARY_JOIN, + err_expect::ERR_EXPECT, + needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF, + is_digit_ascii_radix::IS_DIGIT_ASCII_RADIX, + needless_option_take::NEEDLESS_OPTION_TAKE, + no_effect_replace::NO_EFFECT_REPLACE, + obfuscated_if_else::OBFUSCATED_IF_ELSE, + iter_on_single_or_empty_collections::ITER_ON_SINGLE_ITEMS, + iter_on_single_or_empty_collections::ITER_ON_EMPTY_COLLECTIONS, + bytecount::NAIVE_BYTECOUNT, + bytes_count_to_len::BYTES_COUNT_TO_LEN, + case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + get_first::GET_FIRST, + manual_ok_or::MANUAL_OK_OR, + map_clone::MAP_CLONE, + map_err_ignore::MAP_ERR_IGNORE, + mut_mutex_lock::MUT_MUTEX_LOCK, + open_options::NONSENSICAL_OPEN_OPTIONS, + open_options::SUSPICIOUS_OPEN_OPTIONS, + path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE, + range_zip_with_len::RANGE_ZIP_WITH_LEN, + repeat_once::REPEAT_ONCE, + stable_sort_primitive::STABLE_SORT_PRIMITIVE, + unit_hash::UNIT_HASH, + read_line_without_trim::READ_LINE_WITHOUT_TRIM, + unnecessary_sort_by::UNNECESSARY_SORT_BY, + vec_resize_to_zero::VEC_RESIZE_TO_ZERO, + verbose_file_reads::VERBOSE_FILE_READS, + iter_kv_map::ITER_KV_MAP, + seek_from_current::SEEK_FROM_CURRENT, + seek_to_start_instead_of_rewind::SEEK_TO_START_INSTEAD_OF_REWIND, + needless_collect::NEEDLESS_COLLECT, + suspicious_command_arg_space::SUSPICIOUS_COMMAND_ARG_SPACE, + clear_with_drain::CLEAR_WITH_DRAIN, + manual_next_back::MANUAL_NEXT_BACK, + unnecessary_literal_unwrap::UNNECESSARY_LITERAL_UNWRAP, + drain_collect::DRAIN_COLLECT, + manual_try_fold::MANUAL_TRY_FOLD, + format_collect::FORMAT_COLLECT, + string_lit_chars_any::STRING_LIT_CHARS_ANY, + iter_skip_zero::ITER_SKIP_ZERO, + filter_map_bool_then::FILTER_MAP_BOOL_THEN, + readonly_write_lock::READONLY_WRITE_LOCK, + iter_out_of_bounds::ITER_OUT_OF_BOUNDS, + path_ends_with_ext::PATH_ENDS_WITH_EXT, + redundant_as_str::REDUNDANT_AS_STR, + waker_clone_wake::WAKER_CLONE_WAKE, + unnecessary_fallible_conversions::UNNECESSARY_FALLIBLE_CONVERSIONS, + join_absolute_paths::JOIN_ABSOLUTE_PATHS, + filter_map::RESULT_FILTER_MAP, + iter_filter::ITER_FILTER_IS_SOME, + iter_filter::ITER_FILTER_IS_OK, + manual_is_variant_and::MANUAL_IS_VARIANT_AND, + str_split::STR_SPLIT_AT_NEWLINE, + option_as_ref_cloned::OPTION_AS_REF_CLONED, + unnecessary_result_map_or_else::UNNECESSARY_RESULT_MAP_OR_ELSE, + manual_c_str_literals::MANUAL_C_STR_LITERALS, + unnecessary_get_then_check::UNNECESSARY_GET_THEN_CHECK, + unnecessary_first_then_check::UNNECESSARY_FIRST_THEN_CHECK, + needless_character_iteration::NEEDLESS_CHARACTER_ITERATION, + manual_inspect::MANUAL_INSPECT, + unnecessary_min_or_max::UNNECESSARY_MIN_OR_MAX, + needless_as_bytes::NEEDLESS_AS_BYTES, + map_all_any_identity::MAP_ALL_ANY_IDENTITY, + map_with_unused_argument_over_ranges::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, + unnecessary_map_or::UNNECESSARY_MAP_OR, + double_ended_iterator_last::DOUBLE_ENDED_ITERATOR_LAST, + useless_nonzero_new_unchecked::USELESS_NONZERO_NEW_UNCHECKED, + manual_repeat_n::MANUAL_REPEAT_N, + sliced_string_as_bytes::SLICED_STRING_AS_BYTES, + return_and_then::RETURN_AND_THEN, + unbuffered_bytes::UNBUFFERED_BYTES, + manual_contains::MANUAL_CONTAINS, + io_other_error::IO_OTHER_ERROR, + swap_with_temporary::SWAP_WITH_TEMPORARY, + ip_constant::IP_CONSTANT, + iter_overeager_cloned::REDUNDANT_ITER_CLONED, +]); + +/// Extracts a method call name, args, and `Span` of the method name. +/// This ensures that neither the receiver nor any of the arguments +/// come from expansion. +fn method_call<'tcx>(recv: &'tcx Expr<'tcx>) -> Option<(Symbol, &'tcx Expr<'tcx>, &'tcx [Expr<'tcx>], Span, Span)> { + if let ExprKind::MethodCall(path, receiver, args, call_span) = recv.kind + && !args.iter().any(|e| e.span.from_expansion()) + && !receiver.span.from_expansion() + { + Some((path.ident.name, receiver, args, path.ident.span, call_span)) + } else { + None + } +} + +impl<'tcx> LateLintPass<'tcx> for Methods { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + self.check_methods(cx, expr); + + match expr.kind { + ExprKind::Call(func, args) => { + from_iter_instead_of_collect::check(cx, expr, args, func); + unnecessary_fallible_conversions::check_function(cx, expr, func); + manual_c_str_literals::check(cx, expr, func, args, self.msrv); + useless_nonzero_new_unchecked::check(cx, expr, func, args, self.msrv); + io_other_error::check(cx, expr, func, args, self.msrv); + swap_with_temporary::check(cx, expr, func, args); + ip_constant::check(cx, expr, func, args); + }, + ExprKind::MethodCall(method_call, receiver, args, _) => { + let method_span = method_call.ident.span; + or_fun_call::check(cx, expr, method_span, method_call.ident.name, receiver, args); + expect_fun_call::check( + cx, + &self.format_args, + expr, + method_span, + method_call.ident.name, + receiver, + args, + ); + clone_on_copy::check(cx, expr, method_call.ident.name, receiver, args); + clone_on_ref_ptr::check(cx, expr, method_call.ident.name, receiver, args); + inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args); + single_char_add_str::check(cx, expr, receiver, args); + into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, receiver); + unnecessary_to_owned::check(cx, expr, method_call.ident.name, receiver, args, self.msrv); + }, + ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => { + let mut info = BinaryExprInfo { + expr, + chain: lhs, + other: rhs, + eq: op.node == hir::BinOpKind::Eq, + }; + lint_binary_expr_with_method_call(cx, &mut info); + }, + _ => (), + } + } + + #[allow(clippy::too_many_lines)] + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { + if impl_item.span.in_external_macro(cx.sess().source_map()) { + return; + } + let name = impl_item.ident.name; + let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id; + let item = cx.tcx.hir_expect_item(parent); + let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); + + let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })); + if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind { + let method_sig = cx.tcx.fn_sig(impl_item.owner_id).instantiate_identity(); + let method_sig = cx.tcx.instantiate_bound_regions_with_erased(method_sig); + let first_arg_ty_opt = method_sig.inputs().iter().next().copied(); + // if this impl block implements a trait, lint in trait definition instead + if !implements_trait && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) { + // check missing trait implementations + for method_config in &TRAIT_METHODS { + if name == method_config.method_name + && sig.decl.inputs.len() == method_config.param_count + && method_config.output_type.matches(&sig.decl.output) + // in case there is no first arg, since we already have checked the number of arguments + // it's should be always true + && first_arg_ty_opt.is_none_or(|first_arg_ty| method_config + .self_kind.matches(cx, self_ty, first_arg_ty) + ) + && fn_header_equals(method_config.fn_header, sig.header) + && method_config.lifetime_param_cond(impl_item) + { + span_lint_and_help( + cx, + SHOULD_IMPLEMENT_TRAIT, + impl_item.span, + format!( + "method `{}` can be confused for the standard trait method `{}::{}`", + method_config.method_name, method_config.trait_name, method_config.method_name + ), + None, + format!( + "consider implementing the trait `{}` or choosing a less ambiguous method name", + method_config.trait_name + ), + ); + } + } + } + + if sig.decl.implicit_self.has_implicit_self() + && !(self.avoid_breaking_exported_api + && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id)) + && let Some(first_arg) = iter_input_pats(sig.decl, cx.tcx.hir_body(id)).next() + && let Some(first_arg_ty) = first_arg_ty_opt + { + wrong_self_convention::check( + cx, + name, + self_ty, + first_arg_ty, + first_arg.pat.span, + implements_trait, + false, + ); + } + } + + // if this impl block implements a trait, lint in trait definition instead + if implements_trait { + return; + } + + if let hir::ImplItemKind::Fn(_, _) = impl_item.kind { + let ret_ty = return_ty(cx, impl_item.owner_id); + + if contains_ty_adt_constructor_opaque(cx, ret_ty, self_ty) { + return; + } + + if name == sym::new && ret_ty != self_ty { + span_lint( + cx, + NEW_RET_NO_SELF, + impl_item.span, + "methods called `new` usually return `Self`", + ); + } + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { + if item.span.in_external_macro(cx.tcx.sess.source_map()) { + return; + } + + if let TraitItemKind::Fn(ref sig, _) = item.kind + && sig.decl.implicit_self.has_implicit_self() + && let Some(first_arg_hir_ty) = sig.decl.inputs.first() + && let Some(&first_arg_ty) = cx + .tcx + .fn_sig(item.owner_id) + .instantiate_identity() + .inputs() + .skip_binder() + .first() + { + let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty(); + wrong_self_convention::check( + cx, + item.ident.name, + self_ty, + first_arg_ty, + first_arg_hir_ty.span, + false, + true, + ); + } + + if item.ident.name == sym::new + && let TraitItemKind::Fn(_, _) = item.kind + && let ret_ty = return_ty(cx, item.owner_id) + && let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty() + && !ret_ty.contains(self_ty) + { + span_lint( + cx, + NEW_RET_NO_SELF, + item.span, + "methods called `new` usually return `Self`", + ); + } + } +} + +impl Methods { + #[allow(clippy::too_many_lines)] + fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + // Handle method calls whose receiver and arguments may not come from expansion + if let Some((name, recv, args, span, call_span)) = method_call(expr) { + match (name, args) { + ( + sym::add | sym::offset | sym::sub | sym::wrapping_offset | sym::wrapping_add | sym::wrapping_sub, + [_arg], + ) => { + zst_offset::check(cx, expr, recv); + }, + (sym::all, [arg]) => { + needless_character_iteration::check(cx, expr, recv, arg, true); + match method_call(recv) { + Some((sym::cloned, recv2, [], _, _)) => { + iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::NeedlessMove(arg), + false, + ); + }, + Some((sym::map, _, [map_arg], _, map_call_span)) => { + map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "all"); + }, + _ => {}, + } + }, + (sym::and_then, [arg]) => { + let biom_option_linted = bind_instead_of_map::check_and_then_some(cx, expr, recv, arg); + let biom_result_linted = bind_instead_of_map::check_and_then_ok(cx, expr, recv, arg); + if !biom_option_linted && !biom_result_linted { + let ule_and_linted = unnecessary_lazy_eval::check(cx, expr, recv, arg, "and"); + if !ule_and_linted { + return_and_then::check(cx, expr, recv, arg); + } + } + }, + (sym::any, [arg]) => { + needless_character_iteration::check(cx, expr, recv, arg, false); + match method_call(recv) { + Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::NeedlessMove(arg), + false, + ), + Some((sym::chars, recv, _, _, _)) + if let ExprKind::Closure(arg) = arg.kind + && let body = cx.tcx.hir_body(arg.body) + && let [param] = body.params => + { + string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), self.msrv); + }, + Some((sym::map, _, [map_arg], _, map_call_span)) => { + map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "any"); + }, + Some((sym::iter, iter_recv, ..)) => { + manual_contains::check(cx, expr, iter_recv, arg); + }, + _ => {}, + } + }, + (sym::arg, [arg]) => { + suspicious_command_arg_space::check(cx, recv, arg, span); + }, + (sym::as_deref | sym::as_deref_mut, []) => { + needless_option_as_deref::check(cx, expr, recv, name); + }, + (sym::as_bytes, []) => { + if let Some((sym::as_str, recv, [], as_str_span, _)) = method_call(recv) { + redundant_as_str::check(cx, expr, recv, as_str_span, span); + } + sliced_string_as_bytes::check(cx, expr, recv); + }, + (sym::as_mut | sym::as_ref, []) => useless_asref::check(cx, expr, name, recv), + (sym::as_ptr, []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, self.msrv), + (sym::assume_init, []) => uninit_assumed_init::check(cx, expr, recv), + (sym::bytes, []) => unbuffered_bytes::check(cx, expr, recv), + (sym::cloned, []) => { + cloned_instead_of_copied::check(cx, expr, recv, span, self.msrv); + option_as_ref_cloned::check(cx, recv, span); + }, + (sym::collect, []) if is_trait_method(cx, expr, sym::Iterator) => { + needless_collect::check(cx, span, expr, recv, call_span); + match method_call(recv) { + Some((name @ (sym::cloned | sym::copied), recv2, [], _, _)) => { + iter_cloned_collect::check(cx, name, expr, recv2); + }, + Some((sym::map, m_recv, [m_arg], m_ident_span, _)) => { + map_collect_result_unit::check(cx, expr, m_recv, m_arg); + format_collect::check(cx, expr, m_arg, m_ident_span); + }, + Some((sym::take, take_self_arg, [take_arg], _, _)) => { + if self.msrv.meets(cx, msrvs::STR_REPEAT) { + manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg); + } + }, + Some((sym::drain, recv, args, ..)) => { + drain_collect::check(cx, args, expr, recv); + }, + _ => {}, + } + }, + (sym::count, []) if is_trait_method(cx, expr, sym::Iterator) => match method_call(recv) { + Some((sym::cloned, recv2, [], _, _)) => { + iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::RmCloned, false); + }, + Some((name2 @ (sym::into_iter | sym::iter | sym::iter_mut), recv2, [], _, _)) => { + iter_count::check(cx, expr, recv2, name2); + }, + Some((sym::map, _, [arg], _, _)) => suspicious_map::check(cx, expr, recv, arg), + Some((sym::filter, recv2, [arg], _, _)) => bytecount::check(cx, expr, recv2, arg), + Some((sym::bytes, recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2), + _ => {}, + }, + (sym::min | sym::max, [arg]) => { + unnecessary_min_or_max::check(cx, expr, name, recv, arg); + }, + (sym::drain, ..) => { + if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.parent_hir_node(expr.hir_id) + && matches!(kind, StmtKind::Semi(_)) + && args.len() <= 1 + { + clear_with_drain::check(cx, expr, recv, span, args.first()); + } else if let [arg] = args { + iter_with_drain::check(cx, expr, recv, span, arg); + } + }, + (sym::ends_with, [arg]) => { + if let ExprKind::MethodCall(.., span) = expr.kind { + case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg, self.msrv); + } + path_ends_with_ext::check(cx, recv, arg, expr, self.msrv, &self.allowed_dotfiles); + }, + (sym::expect, [_]) => { + match method_call(recv) { + Some((sym::ok, recv, [], _, _)) => ok_expect::check(cx, expr, recv), + Some((sym::err, recv, [], err_span, _)) => { + err_expect::check(cx, expr, recv, span, err_span, self.msrv); + }, + _ => {}, + } + unnecessary_literal_unwrap::check(cx, expr, recv, name, args); + }, + (sym::expect_err, [_]) | (sym::unwrap_err | sym::unwrap_unchecked | sym::unwrap_err_unchecked, []) => { + unnecessary_literal_unwrap::check(cx, expr, recv, name, args); + }, + (sym::extend, [arg]) => { + string_extend_chars::check(cx, expr, recv, arg); + extend_with_drain::check(cx, expr, recv, arg); + }, + (sym::filter, [arg]) => { + if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) { + // if `arg` has side-effect, the semantic will change + iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::FixClosure(name, arg), + false, + ); + } + if self.msrv.meets(cx, msrvs::ITER_FLATTEN) { + // use the sourcemap to get the span of the closure + iter_filter::check(cx, expr, arg, span); + } + }, + (sym::find, [arg]) => { + if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) { + // if `arg` has side-effect, the semantic will change + iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::FixClosure(name, arg), + false, + ); + } + }, + (sym::filter_map, [arg]) => { + unnecessary_filter_map::check(cx, expr, arg, name); + filter_map_bool_then::check(cx, expr, arg, call_span); + filter_map_identity::check(cx, expr, arg, span); + }, + (sym::find_map, [arg]) => { + unnecessary_filter_map::check(cx, expr, arg, name); + }, + (sym::flat_map, [arg]) => { + flat_map_identity::check(cx, expr, arg, span); + flat_map_option::check(cx, expr, arg, span); + }, + (sym::flatten, []) => match method_call(recv) { + Some((sym::map, recv, [map_arg], map_span, _)) => { + map_flatten::check(cx, expr, recv, map_arg, map_span); + }, + Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::LaterCloned, + true, + ), + _ => {}, + }, + (sym::fold, [init, acc]) => { + manual_try_fold::check(cx, expr, init, acc, call_span, self.msrv); + unnecessary_fold::check(cx, expr, init, acc, span); + }, + (sym::for_each, [arg]) => match method_call(recv) { + Some((sym::inspect, _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2), + Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::NeedlessMove(arg), + false, + ), + _ => {}, + }, + (sym::get, [arg]) => { + get_first::check(cx, expr, recv, arg); + get_last_with_len::check(cx, expr, recv, arg); + }, + (sym::get_or_insert_with, [arg]) => { + unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"); + }, + (sym::hash, [arg]) => { + unit_hash::check(cx, expr, recv, arg); + }, + (sym::is_empty, []) => { + match method_call(recv) { + Some((prev_method @ (sym::as_bytes | sym::bytes), prev_recv, [], _, _)) => { + needless_as_bytes::check(cx, prev_method, name, prev_recv, expr.span); + }, + Some((sym::as_str, recv, [], as_str_span, _)) => { + redundant_as_str::check(cx, expr, recv, as_str_span, span); + }, + _ => {}, + } + is_empty::check(cx, expr, recv); + }, + (sym::is_file, []) => filetype_is_file::check(cx, expr, recv), + (sym::is_digit, [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv), + (sym::is_none, []) => check_is_some_is_none(cx, expr, recv, call_span, false), + (sym::is_some, []) => check_is_some_is_none(cx, expr, recv, call_span, true), + (sym::iter | sym::iter_mut | sym::into_iter, []) => { + iter_on_single_or_empty_collections::check(cx, expr, name, recv); + }, + (sym::join, [join_arg]) => { + if let Some((sym::collect, _, _, span, _)) = method_call(recv) { + unnecessary_join::check(cx, expr, recv, join_arg, span); + } else { + join_absolute_paths::check(cx, recv, join_arg, expr.span); + } + }, + (sym::last, []) => { + if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) { + iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::LaterCloned, + false, + ); + } + double_ended_iterator_last::check(cx, expr, recv, call_span); + }, + (sym::len, []) => { + if let Some((prev_method @ (sym::as_bytes | sym::bytes), prev_recv, [], _, _)) = method_call(recv) { + needless_as_bytes::check(cx, prev_method, sym::len, prev_recv, expr.span); + } + }, + (sym::lock, []) => { + mut_mutex_lock::check(cx, expr, recv, span); + }, + (name @ (sym::map | sym::map_err), [m_arg]) => { + if name == sym::map { + map_clone::check(cx, expr, recv, m_arg, self.msrv); + map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, self.msrv, span); + manual_is_variant_and::check_map(cx, expr); + match method_call(recv) { + Some((map_name @ (sym::iter | sym::into_iter), recv2, _, _, _)) => { + iter_kv_map::check(cx, map_name, expr, recv2, m_arg, self.msrv); + }, + Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::NeedlessMove(m_arg), + false, + ), + _ => {}, + } + } else { + map_err_ignore::check(cx, expr, m_arg); + } + if let Some((name, recv2, args, span2, _)) = method_call(recv) { + match (name, args) { + (sym::as_mut, []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, self.msrv), + (sym::as_ref, []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, self.msrv), + (sym::filter, [f_arg]) => { + filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false); + }, + (sym::find, [f_arg]) => { + filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, true); + }, + _ => {}, + } + } + map_identity::check(cx, expr, recv, m_arg, name, span); + manual_inspect::check(cx, expr, m_arg, name, span, self.msrv); + }, + (sym::map_or, [def, map]) => { + option_map_or_none::check(cx, expr, recv, def, map); + manual_ok_or::check(cx, expr, recv, def, map); + unnecessary_map_or::check(cx, expr, recv, def, map, span, self.msrv); + }, + (sym::map_or_else, [def, map]) => { + result_map_or_else_none::check(cx, expr, recv, def, map); + unnecessary_result_map_or_else::check(cx, expr, recv, def, map); + }, + (sym::next, []) => { + if let Some((name2, recv2, args2, _, _)) = method_call(recv) { + match (name2, args2) { + (sym::cloned, []) => iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::LaterCloned, + false, + ), + (sym::filter, [arg]) => filter_next::check(cx, expr, recv2, arg), + (sym::filter_map, [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv), + (sym::iter, []) => iter_next_slice::check(cx, expr, recv2), + (sym::skip, [arg]) => iter_skip_next::check(cx, expr, recv2, arg), + (sym::skip_while, [_]) => skip_while_next::check(cx, expr), + (sym::rev, []) => manual_next_back::check(cx, expr, recv, recv2), + _ => {}, + } + } + }, + (sym::nth, [n_arg]) => match method_call(recv) { + Some((sym::bytes, recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg), + Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::LaterCloned, + false, + ), + Some((iter_method @ (sym::iter | sym::iter_mut), iter_recv, [], iter_span, _)) => { + if !iter_nth::check(cx, expr, iter_recv, iter_method, iter_span, span) { + iter_nth_zero::check(cx, expr, recv, n_arg); + } + }, + _ => iter_nth_zero::check(cx, expr, recv, n_arg), + }, + (sym::ok_or_else, [arg]) => { + unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"); + }, + (sym::open, [_]) => { + open_options::check(cx, expr, recv); + }, + (sym::or_else, [arg]) => { + if !bind_instead_of_map::check_or_else_err(cx, expr, recv, arg) { + unnecessary_lazy_eval::check(cx, expr, recv, arg, "or"); + } + }, + (sym::push, [arg]) => { + path_buf_push_overwrite::check(cx, expr, arg); + }, + (sym::read_to_end, [_]) => { + verbose_file_reads::check(cx, expr, recv, verbose_file_reads::READ_TO_END_MSG); + }, + (sym::read_to_string, [_]) => { + verbose_file_reads::check(cx, expr, recv, verbose_file_reads::READ_TO_STRING_MSG); + }, + (sym::read_line, [arg]) => { + read_line_without_trim::check(cx, expr, recv, arg); + }, + (sym::repeat, [arg]) => { + repeat_once::check(cx, expr, recv, arg); + }, + (name @ (sym::replace | sym::replacen), [arg1, arg2] | [arg1, arg2, _]) => { + no_effect_replace::check(cx, expr, arg1, arg2); + + // Check for repeated `str::replace` calls to perform `collapsible_str_replace` lint + if self.msrv.meets(cx, msrvs::PATTERN_TRAIT_CHAR_ARRAY) + && name == sym::replace + && let Some((sym::replace, ..)) = method_call(recv) + { + collapsible_str_replace::check(cx, expr, arg1, arg2); + } + }, + (sym::resize, [count_arg, default_arg]) => { + vec_resize_to_zero::check(cx, expr, count_arg, default_arg, span); + }, + (sym::seek, [arg]) => { + if self.msrv.meets(cx, msrvs::SEEK_FROM_CURRENT) { + seek_from_current::check(cx, expr, recv, arg); + } + if self.msrv.meets(cx, msrvs::SEEK_REWIND) { + seek_to_start_instead_of_rewind::check(cx, expr, recv, arg, span); + } + }, + (sym::skip, [arg]) => { + iter_skip_zero::check(cx, expr, arg); + iter_out_of_bounds::check_skip(cx, expr, recv, arg); + + if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) { + iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::LaterCloned, + false, + ); + } + }, + (sym::sort, []) => { + stable_sort_primitive::check(cx, expr, recv); + }, + (sym::sort_by, [arg]) => { + unnecessary_sort_by::check(cx, expr, recv, arg, false); + }, + (sym::sort_unstable_by, [arg]) => { + unnecessary_sort_by::check(cx, expr, recv, arg, true); + }, + (sym::split, [arg]) => { + str_split::check(cx, expr, recv, arg); + }, + (sym::splitn | sym::rsplitn, [count_arg, pat_arg]) => { + if let Some(Constant::Int(count)) = ConstEvalCtxt::new(cx).eval(count_arg) { + suspicious_splitn::check(cx, name, expr, recv, count); + str_splitn::check(cx, name, expr, recv, pat_arg, count, self.msrv); + } + }, + (sym::splitn_mut | sym::rsplitn_mut, [count_arg, _]) => { + if let Some(Constant::Int(count)) = ConstEvalCtxt::new(cx).eval(count_arg) { + suspicious_splitn::check(cx, name, expr, recv, count); + } + }, + (sym::step_by, [arg]) => iterator_step_by_zero::check(cx, expr, arg), + (sym::take, [arg]) => { + iter_out_of_bounds::check_take(cx, expr, recv, arg); + manual_repeat_n::check(cx, expr, recv, arg, self.msrv); + if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) { + iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::LaterCloned, + false, + ); + } + }, + (sym::take, []) => needless_option_take::check(cx, expr, recv), + (sym::then, [arg]) => { + if !self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) { + return; + } + unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some"); + }, + (sym::try_into, []) if is_trait_method(cx, expr, sym::TryInto) => { + unnecessary_fallible_conversions::check_method(cx, expr); + }, + (sym::to_owned, []) => { + if !suspicious_to_owned::check(cx, expr, recv) { + implicit_clone::check(cx, name, expr, recv); + } + }, + (sym::to_os_string | sym::to_path_buf | sym::to_vec, []) => { + implicit_clone::check(cx, name, expr, recv); + }, + (sym::type_id, []) => { + type_id_on_box::check(cx, recv, expr.span); + }, + (sym::unwrap, []) => { + match method_call(recv) { + Some((sym::get, recv, [get_arg], _, _)) => { + get_unwrap::check(cx, expr, recv, get_arg, false); + }, + Some((sym::get_mut, recv, [get_arg], _, _)) => { + get_unwrap::check(cx, expr, recv, get_arg, true); + }, + Some((sym::or, recv, [or_arg], or_span, _)) => { + or_then_unwrap::check(cx, expr, recv, or_arg, or_span); + }, + _ => {}, + } + unnecessary_literal_unwrap::check(cx, expr, recv, name, args); + }, + (sym::unwrap_or, [u_arg]) => { + match method_call(recv) { + Some((arith @ (sym::checked_add | sym::checked_sub | sym::checked_mul), lhs, [rhs], _, _)) => { + manual_saturating_arithmetic::check( + cx, + expr, + lhs, + rhs, + u_arg, + &arith.as_str()[const { "checked_".len() }..], + ); + }, + Some((sym::map, m_recv, [m_arg], span, _)) => { + option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv); + }, + Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => { + obfuscated_if_else::check(cx, expr, t_recv, t_arg, Some(u_arg), then_method, name); + }, + _ => {}, + } + unnecessary_literal_unwrap::check(cx, expr, recv, name, args); + }, + (sym::unwrap_or_default, []) => { + match method_call(recv) { + Some((sym::map, m_recv, [arg], span, _)) => { + manual_is_variant_and::check(cx, expr, m_recv, arg, span, self.msrv); + }, + Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => { + obfuscated_if_else::check( + cx, + expr, + t_recv, + t_arg, + None, + then_method, + sym::unwrap_or_default, + ); + }, + _ => {}, + } + unnecessary_literal_unwrap::check(cx, expr, recv, name, args); + }, + (sym::unwrap_or_else, [u_arg]) => { + match method_call(recv) { + Some((sym::map, recv, [map_arg], _, _)) + if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {}, + Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => { + obfuscated_if_else::check( + cx, + expr, + t_recv, + t_arg, + Some(u_arg), + then_method, + sym::unwrap_or_else, + ); + }, + _ => { + unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or"); + }, + } + unnecessary_literal_unwrap::check(cx, expr, recv, name, args); + }, + (sym::wake, []) => { + waker_clone_wake::check(cx, expr, recv); + }, + (sym::write, []) => { + readonly_write_lock::check(cx, expr, recv); + }, + (sym::zip, [arg]) => { + if let ExprKind::MethodCall(name, iter_recv, [], _) = recv.kind + && name.ident.name == sym::iter + { + range_zip_with_len::check(cx, expr, iter_recv, arg); + } + }, + _ => {}, + } + } + // Handle method calls whose receiver and arguments may come from expansion + if let ExprKind::MethodCall(path, recv, args, _call_span) = expr.kind { + match (path.ident.name, args) { + (sym::expect, [_]) if !matches!(method_call(recv), Some((sym::ok | sym::err, _, [], _, _))) => { + unwrap_expect_used::check( + cx, + expr, + recv, + false, + self.allow_expect_in_consts, + self.allow_expect_in_tests, + unwrap_expect_used::Variant::Expect, + ); + }, + (sym::expect_err, [_]) => { + unwrap_expect_used::check( + cx, + expr, + recv, + true, + self.allow_expect_in_consts, + self.allow_expect_in_tests, + unwrap_expect_used::Variant::Expect, + ); + }, + (sym::unwrap, []) => { + unwrap_expect_used::check( + cx, + expr, + recv, + false, + self.allow_unwrap_in_consts, + self.allow_unwrap_in_tests, + unwrap_expect_used::Variant::Unwrap, + ); + }, + (sym::unwrap_err, []) => { + unwrap_expect_used::check( + cx, + expr, + recv, + true, + self.allow_unwrap_in_consts, + self.allow_unwrap_in_tests, + unwrap_expect_used::Variant::Unwrap, + ); + }, + _ => {}, + } + } + } +} + +fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, call_span: Span, is_some: bool) { + match method_call(recv) { + Some((name @ (sym::find | sym::position | sym::rposition), f_recv, [arg], span, _)) => { + search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span); + }, + Some((sym::get, f_recv, [arg], _, _)) => { + unnecessary_get_then_check::check(cx, call_span, recv, f_recv, arg, is_some); + }, + Some((sym::first, f_recv, [], _, _)) => { + unnecessary_first_then_check::check(cx, call_span, recv, f_recv, is_some); + }, + _ => {}, + } +} + +/// Used for `lint_binary_expr_with_method_call`. +#[derive(Copy, Clone)] +struct BinaryExprInfo<'a> { + expr: &'a Expr<'a>, + chain: &'a Expr<'a>, + other: &'a Expr<'a>, + eq: bool, +} + +/// Checks for the `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints. +fn lint_binary_expr_with_method_call(cx: &LateContext<'_>, info: &mut BinaryExprInfo<'_>) { + macro_rules! lint_with_both_lhs_and_rhs { + ($func:expr, $cx:expr, $info:ident) => { + if !$func($cx, $info) { + ::std::mem::swap(&mut $info.chain, &mut $info.other); + if $func($cx, $info) { + return; + } + } + }; + } + + lint_with_both_lhs_and_rhs!(chars_cmp::check_next, cx, info); + lint_with_both_lhs_and_rhs!(chars_cmp::check_last, cx, info); + lint_with_both_lhs_and_rhs!(chars_cmp::check_next_unwrap, cx, info); + lint_with_both_lhs_and_rhs!(chars_cmp::check_last_unwrap, cx, info); +} + +const FN_HEADER: hir::FnHeader = hir::FnHeader { + safety: hir::HeaderSafety::Normal(hir::Safety::Safe), + constness: hir::Constness::NotConst, + asyncness: hir::IsAsync::NotAsync, + abi: ExternAbi::Rust, +}; + +struct ShouldImplTraitCase { + trait_name: &'static str, + method_name: Symbol, + param_count: usize, + fn_header: hir::FnHeader, + // implicit self kind expected (none, self, &self, ...) + self_kind: SelfKind, + // checks against the output type + output_type: OutType, + // certain methods with explicit lifetimes can't implement the equivalent trait method + lint_explicit_lifetime: bool, +} +impl ShouldImplTraitCase { + const fn new( + trait_name: &'static str, + method_name: Symbol, + param_count: usize, + fn_header: hir::FnHeader, + self_kind: SelfKind, + output_type: OutType, + lint_explicit_lifetime: bool, + ) -> ShouldImplTraitCase { + ShouldImplTraitCase { + trait_name, + method_name, + param_count, + fn_header, + self_kind, + output_type, + lint_explicit_lifetime, + } + } + + fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool { + self.lint_explicit_lifetime + || !impl_item.generics.params.iter().any(|p| { + matches!( + p.kind, + hir::GenericParamKind::Lifetime { + kind: hir::LifetimeParamKind::Explicit + } + ) + }) + } +} + +#[rustfmt::skip] +const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ + ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), + ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), + ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), + ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), + ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, FN_HEADER, SelfKind::Ref, OutType::Any, true), + ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, FN_HEADER, SelfKind::Ref, OutType::Any, true), + ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, FN_HEADER, SelfKind::No, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true), + ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), + ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true), + ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true), + ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, FN_HEADER, SelfKind::No, OutType::Any, true), + ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, FN_HEADER, SelfKind::No, OutType::Any, true), + ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true), + ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true), + ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), + ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false), + ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), + ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, FN_HEADER, SelfKind::Value, OutType::Any, true), +]; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum SelfKind { + Value, + Ref, + RefMut, + No, // When we want the first argument type to be different than `Self` +} + +impl SelfKind { + fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + if ty == parent_ty { + true + } else if let Some(boxed_ty) = ty.boxed_ty() { + boxed_ty == parent_ty + } else if is_type_diagnostic_item(cx, ty, sym::Rc) || is_type_diagnostic_item(cx, ty, sym::Arc) { + if let ty::Adt(_, args) = ty.kind() { + args.types().next() == Some(parent_ty) + } else { + false + } + } else { + false + } + } + + fn matches_ref<'a>(cx: &LateContext<'a>, mutability: hir::Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + if let ty::Ref(_, t, m) = *ty.kind() { + return m == mutability && t == parent_ty; + } + + let trait_sym = match mutability { + hir::Mutability::Not => sym::AsRef, + hir::Mutability::Mut => sym::AsMut, + }; + + let Some(trait_def_id) = cx.tcx.get_diagnostic_item(trait_sym) else { + return false; + }; + implements_trait(cx, ty, trait_def_id, &[parent_ty.into()]) + } + + fn matches_none<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool { + !matches_value(cx, parent_ty, ty) + && !matches_ref(cx, hir::Mutability::Not, parent_ty, ty) + && !matches_ref(cx, hir::Mutability::Mut, parent_ty, ty) + } + + match self { + Self::Value => matches_value(cx, parent_ty, ty), + Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty), + Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty), + Self::No => matches_none(cx, parent_ty, ty), + } + } + + #[must_use] + fn description(self) -> &'static str { + match self { + Self::Value => "`self` by value", + Self::Ref => "`self` by reference", + Self::RefMut => "`self` by mutable reference", + Self::No => "no `self`", + } + } +} + +#[derive(Clone, Copy)] +enum OutType { + Unit, + Bool, + Any, + Ref, +} + +impl OutType { + fn matches(self, ty: &hir::FnRetTy<'_>) -> bool { + let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[])); + match (self, ty) { + (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true, + (Self::Unit, &hir::FnRetTy::Return(ty)) if is_unit(ty) => true, + (Self::Bool, &hir::FnRetTy::Return(ty)) if is_bool(ty) => true, + (Self::Any, &hir::FnRetTy::Return(ty)) if !is_unit(ty) => true, + (Self::Ref, &hir::FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Ref(_, _)), + _ => false, + } + } +} + +fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool { + expected.constness == actual.constness && expected.safety == actual.safety && expected.asyncness == actual.asyncness +} + +pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Conf, format_args: FormatArgsStorage) { + store.register_late_pass(move |_| Box::new(Methods::new(conf, format_args.clone()))); +} diff --git a/clippy_lints/src/methods/manual_c_str_literals.rs b/clippy_lints_methods/src/manual_c_str_literals.rs similarity index 84% rename from clippy_lints/src/methods/manual_c_str_literals.rs rename to clippy_lints_methods/src/manual_c_str_literals.rs index a8445b68dd60..453c2857eea1 100644 --- a/clippy_lints/src/methods/manual_c_str_literals.rs +++ b/clippy_lints_methods/src/manual_c_str_literals.rs @@ -9,7 +9,39 @@ use rustc_lint::LateContext; use rustc_span::edition::Edition::Edition2021; use rustc_span::{Span, Symbol}; -use super::MANUAL_C_STR_LITERALS; +declare_clippy_lint! { + /// ### What it does + /// Checks for the manual creation of C strings (a string with a `NUL` byte at the end), either + /// through one of the `CStr` constructor functions, or more plainly by calling `.as_ptr()` + /// on a (byte) string literal with a hardcoded `\0` byte at the end. + /// + /// ### Why is this bad? + /// This can be written more concisely using `c"str"` literals and is also less error-prone, + /// because the compiler checks for interior `NUL` bytes and the terminating `NUL` byte is inserted automatically. + /// + /// ### Example + /// ```no_run + /// # use std::ffi::CStr; + /// # mod libc { pub unsafe fn puts(_: *const i8) {} } + /// fn needs_cstr(_: &CStr) {} + /// + /// needs_cstr(CStr::from_bytes_with_nul(b"Hello\0").unwrap()); + /// unsafe { libc::puts("World\0".as_ptr().cast()) } + /// ``` + /// Use instead: + /// ```no_run + /// # use std::ffi::CStr; + /// # mod libc { pub unsafe fn puts(_: *const i8) {} } + /// fn needs_cstr(_: &CStr) {} + /// + /// needs_cstr(c"Hello"); + /// unsafe { libc::puts(c"World".as_ptr()) } + /// ``` + #[clippy::version = "1.78.0"] + pub MANUAL_C_STR_LITERALS, + complexity, + r#"creating a `CStr` through functions when `c""` literals can be used"# +} /// Checks: /// - `b"...".as_ptr()` diff --git a/clippy_lints/src/methods/manual_contains.rs b/clippy_lints_methods/src/manual_contains.rs similarity index 84% rename from clippy_lints/src/methods/manual_contains.rs rename to clippy_lints_methods/src/manual_contains.rs index 8ba0f835d3dd..01b9731021c5 100644 --- a/clippy_lints/src/methods/manual_contains.rs +++ b/clippy_lints_methods/src/manual_contains.rs @@ -11,7 +11,30 @@ use rustc_lint::LateContext; use rustc_middle::ty::{self}; use rustc_span::source_map::Spanned; -use super::MANUAL_CONTAINS; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `iter().any()` on slices when it can be replaced with `contains()` and suggests doing so. + /// + /// ### Why is this bad? + /// `contains()` is more concise and idiomatic, while also being faster in some cases. + /// + /// ### Example + /// ```no_run + /// fn foo(values: &[u8]) -> bool { + /// values.iter().any(|&v| v == 10) + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn foo(values: &[u8]) -> bool { + /// values.contains(&10) + /// } + /// ``` + #[clippy::version = "1.87.0"] + pub MANUAL_CONTAINS, + perf, + "unnecessary `iter().any()` on slices that can be replaced with `contains()`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { let mut app = Applicability::MachineApplicable; diff --git a/clippy_lints/src/methods/manual_inspect.rs b/clippy_lints_methods/src/manual_inspect.rs similarity index 94% rename from clippy_lints/src/methods/manual_inspect.rs rename to clippy_lints_methods/src/manual_inspect.rs index 21f2ce8b7c90..71c240261faf 100644 --- a/clippy_lints/src/methods/manual_inspect.rs +++ b/clippy_lints_methods/src/manual_inspect.rs @@ -11,7 +11,26 @@ use rustc_lint::LateContext; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_span::{DUMMY_SP, Span, Symbol}; -use super::MANUAL_INSPECT; +declare_clippy_lint! { + /// ### What it does + /// Checks for uses of `map` which return the original item. + /// + /// ### Why is this bad? + /// `inspect` is both clearer in intent and shorter. + /// + /// ### Example + /// ```no_run + /// let x = Some(0).map(|x| { println!("{x}"); x }); + /// ``` + /// Use instead: + /// ```no_run + /// let x = Some(0).inspect(|x| println!("{x}")); + /// ``` + #[clippy::version = "1.81.0"] + pub MANUAL_INSPECT, + complexity, + "use of `map` returning the original item" +} #[expect(clippy::too_many_lines)] pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: Symbol, name_span: Span, msrv: Msrv) { diff --git a/clippy_lints/src/methods/manual_is_variant_and.rs b/clippy_lints_methods/src/manual_is_variant_and.rs similarity index 81% rename from clippy_lints/src/methods/manual_is_variant_and.rs rename to clippy_lints_methods/src/manual_is_variant_and.rs index 4a61c223d2c1..6939b95d61f3 100644 --- a/clippy_lints/src/methods/manual_is_variant_and.rs +++ b/clippy_lints_methods/src/manual_is_variant_and.rs @@ -10,7 +10,32 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{BytePos, Span, sym}; -use super::MANUAL_IS_VARIANT_AND; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type. + /// + /// ### Why is this bad? + /// Readability. These can be written more concisely as `option.is_some_and(f)` and `result.is_ok_and(f)`. + /// + /// ### Example + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option.map(|a| a > 10).unwrap_or_default(); + /// result.map(|a| a > 10).unwrap_or_default(); + /// ``` + /// Use instead: + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option.is_some_and(|a| a > 10); + /// result.is_ok_and(|a| a > 10); + /// ``` + #[clippy::version = "1.77.0"] + pub MANUAL_IS_VARIANT_AND, + pedantic, + "using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`" +} pub(super) fn check( cx: &LateContext<'_>, diff --git a/clippy_lints/src/methods/manual_next_back.rs b/clippy_lints_methods/src/manual_next_back.rs similarity index 66% rename from clippy_lints/src/methods/manual_next_back.rs rename to clippy_lints_methods/src/manual_next_back.rs index 9a03559b2237..1c4ac2f45327 100644 --- a/clippy_lints/src/methods/manual_next_back.rs +++ b/clippy_lints_methods/src/manual_next_back.rs @@ -6,6 +6,29 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::symbol::sym; +declare_clippy_lint! { + /// ### What it does + /// Checks for `.rev().next()` on a `DoubleEndedIterator` + /// + /// ### Why is this bad? + /// `.next_back()` is cleaner. + /// + /// ### Example + /// ```no_run + /// # let foo = [0; 10]; + /// foo.iter().rev().next(); + /// ``` + /// Use instead: + /// ```no_run + /// # let foo = [0; 10]; + /// foo.iter().next_back(); + /// ``` + #[clippy::version = "1.71.0"] + pub MANUAL_NEXT_BACK, + style, + "manual reverse iteration of `DoubleEndedIterator`" +} + pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, @@ -25,7 +48,7 @@ pub(super) fn check<'tcx>( { span_lint_and_sugg( cx, - super::MANUAL_NEXT_BACK, + MANUAL_NEXT_BACK, expr.span.with_lo(rev_recv.span.hi()), "manual backwards iteration", "use", diff --git a/clippy_lints/src/methods/manual_ok_or.rs b/clippy_lints_methods/src/manual_ok_or.rs similarity index 78% rename from clippy_lints/src/methods/manual_ok_or.rs rename to clippy_lints_methods/src/manual_ok_or.rs index c286c5faaed3..41b05c381785 100644 --- a/clippy_lints/src/methods/manual_ok_or.rs +++ b/clippy_lints_methods/src/manual_ok_or.rs @@ -8,7 +8,31 @@ use rustc_hir::{Expr, ExprKind, PatKind}; use rustc_lint::LateContext; use rustc_span::symbol::sym; -use super::MANUAL_OK_OR; +declare_clippy_lint! { + /// ### What it does + /// + /// Finds patterns that reimplement `Option::ok_or`. + /// + /// ### Why is this bad? + /// + /// Concise code helps focusing on behavior instead of boilerplate. + /// + /// ### Examples + /// ```no_run + /// let foo: Option = None; + /// foo.map_or(Err("error"), |v| Ok(v)); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let foo: Option = None; + /// foo.ok_or("error"); + /// ``` + #[clippy::version = "1.49.0"] + pub MANUAL_OK_OR, + style, + "finds patterns that can be encoded more concisely with `Option::ok_or`" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/manual_repeat_n.rs b/clippy_lints_methods/src/manual_repeat_n.rs similarity index 65% rename from clippy_lints/src/methods/manual_repeat_n.rs rename to clippy_lints_methods/src/manual_repeat_n.rs index 83b57cca17bf..266db40d05d5 100644 --- a/clippy_lints/src/methods/manual_repeat_n.rs +++ b/clippy_lints_methods/src/manual_repeat_n.rs @@ -7,7 +7,28 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::sym; -use super::MANUAL_REPEAT_N; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `repeat().take()` that can be replaced with `repeat_n()`. + /// + /// ### Why is this bad? + /// + /// Using `repeat_n()` is more concise and clearer. Also, `repeat_n()` is sometimes faster than `repeat().take()` when the type of the element is non-trivial to clone because the original value can be reused for the last `.next()` call rather than always cloning. + /// + /// ### Example + /// ```no_run + /// let _ = std::iter::repeat(10).take(3); + /// ``` + /// Use instead: + /// ```no_run + /// let _ = std::iter::repeat_n(10, 3); + /// ``` + #[clippy::version = "1.86.0"] + pub MANUAL_REPEAT_N, + style, + "detect `repeat().take()` that can be replaced with `repeat_n()`" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/clippy_lints_methods/src/manual_saturating_arithmetic.rs similarity index 82% rename from clippy_lints/src/methods/manual_saturating_arithmetic.rs rename to clippy_lints_methods/src/manual_saturating_arithmetic.rs index c785b23bc9c4..4a57d45aee27 100644 --- a/clippy_lints/src/methods/manual_saturating_arithmetic.rs +++ b/clippy_lints_methods/src/manual_saturating_arithmetic.rs @@ -8,6 +8,35 @@ use rustc_hir::def::Res; use rustc_lint::LateContext; use rustc_middle::ty::layout::LayoutOf; +declare_clippy_lint! { + /// ### What it does + /// Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`. + /// + /// ### Why is this bad? + /// These can be written simply with `saturating_add/sub` methods. + /// + /// ### Example + /// ```no_run + /// # let y: u32 = 0; + /// # let x: u32 = 100; + /// let add = x.checked_add(y).unwrap_or(u32::MAX); + /// let sub = x.checked_sub(y).unwrap_or(u32::MIN); + /// ``` + /// + /// can be written using dedicated methods for saturating addition/subtraction as: + /// + /// ```no_run + /// # let y: u32 = 0; + /// # let x: u32 = 100; + /// let add = x.saturating_add(y); + /// let sub = x.saturating_sub(y); + /// ``` + #[clippy::version = "1.39.0"] + pub MANUAL_SATURATING_ARITHMETIC, + style, + "`.checked_add/sub(x).unwrap_or(MAX/MIN)`" +} + pub fn check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, @@ -48,7 +77,7 @@ pub fn check( let mut applicability = Applicability::MachineApplicable; span_lint_and_sugg( cx, - super::MANUAL_SATURATING_ARITHMETIC, + MANUAL_SATURATING_ARITHMETIC, expr.span, "manual saturating arithmetic", format!("consider using `saturating_{arith}`"), diff --git a/clippy_lints/src/methods/manual_str_repeat.rs b/clippy_lints_methods/src/manual_str_repeat.rs similarity index 86% rename from clippy_lints/src/methods/manual_str_repeat.rs rename to clippy_lints_methods/src/manual_str_repeat.rs index 8167e4f96053..1dbecb6187ab 100644 --- a/clippy_lints/src/methods/manual_str_repeat.rs +++ b/clippy_lints_methods/src/manual_str_repeat.rs @@ -11,7 +11,27 @@ use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::sym; use std::borrow::Cow; -use super::MANUAL_STR_REPEAT; +declare_clippy_lint! { + /// ### What it does + /// Checks for manual implementations of `str::repeat` + /// + /// ### Why is this bad? + /// These are both harder to read, as well as less performant. + /// + /// ### Example + /// ```no_run + /// let x: String = std::iter::repeat('x').take(10).collect(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let x: String = "x".repeat(10); + /// ``` + #[clippy::version = "1.54.0"] + pub MANUAL_STR_REPEAT, + perf, + "manual implementation of `str::repeat`" +} enum RepeatKind { String, diff --git a/clippy_lints/src/methods/manual_try_fold.rs b/clippy_lints_methods/src/manual_try_fold.rs similarity index 61% rename from clippy_lints/src/methods/manual_try_fold.rs rename to clippy_lints_methods/src/manual_try_fold.rs index 23dba47f60f4..6400e5ea4eb6 100644 --- a/clippy_lints/src/methods/manual_try_fold.rs +++ b/clippy_lints_methods/src/manual_try_fold.rs @@ -9,7 +9,34 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LintContext}; use rustc_span::{Span, sym}; -use super::MANUAL_TRY_FOLD; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `Iterator::fold` with a type that implements `Try`. + /// + /// ### Why is this bad? + /// The code should use `try_fold` instead, which short-circuits on failure, thus opening the + /// door for additional optimizations not possible with `fold` as rustc can guarantee the + /// function is never called on `None`, `Err`, etc., alleviating otherwise necessary checks. It's + /// also slightly more idiomatic. + /// + /// ### Known issues + /// This lint doesn't take into account whether a function does something on the failure case, + /// i.e., whether short-circuiting will affect behavior. Refactoring to `try_fold` is not + /// desirable in those cases. + /// + /// ### Example + /// ```no_run + /// vec![1, 2, 3].iter().fold(Some(0i32), |sum, i| sum?.checked_add(*i)); + /// ``` + /// Use instead: + /// ```no_run + /// vec![1, 2, 3].iter().try_fold(0i32, |sum, i| sum.checked_add(*i)); + /// ``` + #[clippy::version = "1.72.0"] + pub MANUAL_TRY_FOLD, + perf, + "checks for usage of `Iterator::fold` with a type that implements `Try`" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/map_all_any_identity.rs b/clippy_lints_methods/src/map_all_any_identity.rs similarity index 61% rename from clippy_lints/src/methods/map_all_any_identity.rs rename to clippy_lints_methods/src/map_all_any_identity.rs index ac11baa2d54c..1fc9bc345f4c 100644 --- a/clippy_lints/src/methods/map_all_any_identity.rs +++ b/clippy_lints_methods/src/map_all_any_identity.rs @@ -6,7 +6,30 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::MAP_ALL_ANY_IDENTITY; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.map(…)`, followed by `.all(identity)` or `.any(identity)`. + /// + /// ### Why is this bad? + /// The `.all(…)` or `.any(…)` methods can be called directly in place of `.map(…)`. + /// + /// ### Example + /// ``` + /// # let mut v = [""]; + /// let e1 = v.iter().map(|s| s.is_empty()).all(|a| a); + /// let e2 = v.iter().map(|s| s.is_empty()).any(std::convert::identity); + /// ``` + /// Use instead: + /// ``` + /// # let mut v = [""]; + /// let e1 = v.iter().all(|s| s.is_empty()); + /// let e2 = v.iter().any(|s| s.is_empty()); + /// ``` + #[clippy::version = "1.84.0"] + pub MAP_ALL_ANY_IDENTITY, + complexity, + "combine `.map(_)` followed by `.all(identity)`/`.any(identity)` into a single call" +} #[allow(clippy::too_many_arguments)] pub(super) fn check( diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints_methods/src/map_clone.rs similarity index 91% rename from clippy_lints/src/methods/map_clone.rs rename to clippy_lints_methods/src/map_clone.rs index 333a33f7527d..85e0ac7e1e24 100644 --- a/clippy_lints/src/methods/map_clone.rs +++ b/clippy_lints_methods/src/map_clone.rs @@ -13,7 +13,34 @@ use rustc_middle::ty::adjustment::Adjust; use rustc_span::symbol::Ident; use rustc_span::{Span, sym}; -use super::MAP_CLONE; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `map(|x| x.clone())` or + /// dereferencing closures for `Copy` types, on `Iterator` or `Option`, + /// and suggests `cloned()` or `copied()` instead + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely + /// + /// ### Example + /// ```no_run + /// let x = vec![42, 43]; + /// let y = x.iter(); + /// let z = y.map(|i| *i); + /// ``` + /// + /// The correct use would be: + /// + /// ```no_run + /// let x = vec![42, 43]; + /// 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" +} // If this `map` is called on an `Option` or a `Result` and the previous call is `as_ref`, we don't // run this lint because it would overlap with `useless_asref` which provides a better suggestion diff --git a/clippy_lints/src/methods/map_collect_result_unit.rs b/clippy_lints_methods/src/map_collect_result_unit.rs similarity index 65% rename from clippy_lints/src/methods/map_collect_result_unit.rs rename to clippy_lints_methods/src/map_collect_result_unit.rs index e944eac91e7a..e4cbd7cc9883 100644 --- a/clippy_lints/src/methods/map_collect_result_unit.rs +++ b/clippy_lints_methods/src/map_collect_result_unit.rs @@ -7,7 +7,26 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::symbol::sym; -use super::MAP_COLLECT_RESULT_UNIT; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.map(_).collect::()`. + /// + /// ### Why is this bad? + /// Using `try_for_each` instead is more readable and idiomatic. + /// + /// ### Example + /// ```no_run + /// (0..3).map(|t| Err(t)).collect::>(); + /// ``` + /// Use instead: + /// ```no_run + /// (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`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, iter: &hir::Expr<'_>, map_fn: &hir::Expr<'_>) { // return of collect `Result<(),_>` diff --git a/clippy_lints_methods/src/map_err_ignore.rs b/clippy_lints_methods/src/map_err_ignore.rs new file mode 100644 index 000000000000..b03a8b2cf8bf --- /dev/null +++ b/clippy_lints_methods/src/map_err_ignore.rs @@ -0,0 +1,137 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_hir::{CaptureBy, Closure, Expr, ExprKind, PatKind}; +use rustc_lint::LateContext; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Checks for instances of `map_err(|_| Some::Enum)` + /// + /// ### Why restrict this? + /// This `map_err` throws away the original error rather than allowing the enum to + /// contain and report the cause of the error. + /// + /// ### Example + /// Before: + /// ```no_run + /// use std::fmt; + /// + /// #[derive(Debug)] + /// enum Error { + /// Indivisible, + /// Remainder(u8), + /// } + /// + /// impl fmt::Display for Error { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// match self { + /// Error::Indivisible => write!(f, "could not divide input by three"), + /// Error::Remainder(remainder) => write!( + /// f, + /// "input is not divisible by three, remainder = {}", + /// remainder + /// ), + /// } + /// } + /// } + /// + /// impl std::error::Error for Error {} + /// + /// fn divisible_by_3(input: &str) -> Result<(), Error> { + /// input + /// .parse::() + /// .map_err(|_| Error::Indivisible) + /// .map(|v| v % 3) + /// .and_then(|remainder| { + /// if remainder == 0 { + /// Ok(()) + /// } else { + /// Err(Error::Remainder(remainder as u8)) + /// } + /// }) + /// } + /// ``` + /// + /// After: + /// ```rust + /// use std::{fmt, num::ParseIntError}; + /// + /// #[derive(Debug)] + /// enum Error { + /// Indivisible(ParseIntError), + /// Remainder(u8), + /// } + /// + /// impl fmt::Display for Error { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// match self { + /// Error::Indivisible(_) => write!(f, "could not divide input by three"), + /// Error::Remainder(remainder) => write!( + /// f, + /// "input is not divisible by three, remainder = {}", + /// remainder + /// ), + /// } + /// } + /// } + /// + /// impl std::error::Error for Error { + /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + /// match self { + /// Error::Indivisible(source) => Some(source), + /// _ => None, + /// } + /// } + /// } + /// + /// fn divisible_by_3(input: &str) -> Result<(), Error> { + /// input + /// .parse::() + /// .map_err(Error::Indivisible) + /// .map(|v| v % 3) + /// .and_then(|remainder| { + /// if remainder == 0 { + /// Ok(()) + /// } else { + /// Err(Error::Remainder(remainder as u8)) + /// } + /// }) + /// } + /// ``` + #[clippy::version = "1.48.0"] + pub MAP_ERR_IGNORE, + restriction, + "`map_err` should not ignore the original error" +} + +pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, arg: &Expr<'_>) { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && let Some(impl_id) = cx.tcx.impl_of_method(method_id) + && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::Result) + && let ExprKind::Closure(&Closure { + capture_clause: CaptureBy::Ref, + body, + fn_decl_span, + .. + }) = arg.kind + && let closure_body = cx.tcx.hir_body(body) + && let [param] = closure_body.params + && let PatKind::Wild = param.pat.kind + { + // span the area of the closure capture and warn that the + // original error will be thrown away + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( + cx, + MAP_ERR_IGNORE, + fn_decl_span, + "`map_err(|_|...` wildcard pattern discards the original error", + |diag| { + diag.help( + "consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)", + ); + }, + ); + } +} diff --git a/clippy_lints/src/methods/map_flatten.rs b/clippy_lints_methods/src/map_flatten.rs similarity index 77% rename from clippy_lints/src/methods/map_flatten.rs rename to clippy_lints_methods/src/map_flatten.rs index f7bb8c1d696d..f9017dd041bf 100644 --- a/clippy_lints/src/methods/map_flatten.rs +++ b/clippy_lints_methods/src/map_flatten.rs @@ -9,7 +9,35 @@ use rustc_middle::ty; use rustc_span::Span; use rustc_span::symbol::sym; -use super::MAP_FLATTEN; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.map(_).flatten(_)` on `Iterator` and `Option` + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.flat_map(_)` for `Iterator` or `_.and_then(_)` for `Option` + /// + /// ### Example + /// ```no_run + /// let vec = vec![vec![1]]; + /// let opt = Some(5); + /// + /// vec.iter().map(|x| x.iter()).flatten(); + /// opt.map(|x| Some(x * 2)).flatten(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let vec = vec![vec![1]]; + /// # let opt = Some(5); + /// vec.iter().flat_map(|x| x.iter()); + /// opt.and_then(|x| Some(x * 2)); + /// ``` + #[clippy::version = "1.31.0"] + pub MAP_FLATTEN, + complexity, + "using combinations of `flatten` and `map` which can usually be written as a single method call" +} /// lint use of `map().flatten()` for `Iterators` and 'Options' pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) { diff --git a/clippy_lints/src/methods/map_identity.rs b/clippy_lints_methods/src/map_identity.rs similarity index 72% rename from clippy_lints/src/methods/map_identity.rs rename to clippy_lints_methods/src/map_identity.rs index 98def66ca149..4928684d4ebc 100644 --- a/clippy_lints/src/methods/map_identity.rs +++ b/clippy_lints_methods/src/map_identity.rs @@ -7,7 +7,28 @@ use rustc_hir::{self as hir, Node, PatKind}; use rustc_lint::LateContext; use rustc_span::{Span, Symbol, sym}; -use super::MAP_IDENTITY; +declare_clippy_lint! { + /// ### What it does + /// Checks for instances of `map(f)` where `f` is the identity function. + /// + /// ### Why is this bad? + /// It can be written more concisely without the call to `map`. + /// + /// ### Example + /// ```no_run + /// let x = [1, 2, 3]; + /// let y: Vec<_> = x.iter().map(|x| x).map(|x| 2*x).collect(); + /// ``` + /// Use instead: + /// ```no_run + /// let x = [1, 2, 3]; + /// let y: Vec<_> = x.iter().map(|x| 2*x).collect(); + /// ``` + #[clippy::version = "1.47.0"] + pub MAP_IDENTITY, + complexity, + "using iterator.map(|x| x)" +} pub(super) fn check( cx: &LateContext<'_>, diff --git a/clippy_lints/src/methods/map_unwrap_or.rs b/clippy_lints_methods/src/map_unwrap_or.rs similarity index 67% rename from clippy_lints/src/methods/map_unwrap_or.rs rename to clippy_lints_methods/src/map_unwrap_or.rs index df5a0de3392b..7f38d2520ecc 100644 --- a/clippy_lints/src/methods/map_unwrap_or.rs +++ b/clippy_lints_methods/src/map_unwrap_or.rs @@ -8,7 +8,42 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::symbol::sym; -use super::MAP_UNWRAP_OR; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `option.map(_).unwrap_or(_)` or `option.map(_).unwrap_or_else(_)` or + /// `result.map(_).unwrap_or_else(_)`. + /// + /// ### Why is this bad? + /// Readability, these can be written more concisely (resp.) as + /// `option.map_or(_, _)`, `option.map_or_else(_, _)` and `result.map_or_else(_, _)`. + /// + /// ### Known problems + /// The order of the arguments is not in execution order + /// + /// ### Examples + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// # fn some_function(foo: ()) -> usize { 1 } + /// option.map(|a| a + 1).unwrap_or(0); + /// option.map(|a| a > 10).unwrap_or(false); + /// result.map(|a| a + 1).unwrap_or_else(some_function); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// # fn some_function(foo: ()) -> usize { 1 } + /// option.map_or(0, |a| a + 1); + /// option.is_some_and(|a| a > 10); + /// result.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)`" +} /// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s /// diff --git a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs b/clippy_lints_methods/src/map_with_unused_argument_over_ranges.rs similarity index 81% rename from clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs rename to clippy_lints_methods/src/map_with_unused_argument_over_ranges.rs index a2a522a60687..faba908f4dda 100644 --- a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs +++ b/clippy_lints_methods/src/map_with_unused_argument_over_ranges.rs @@ -1,4 +1,3 @@ -use crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; @@ -12,6 +11,40 @@ use rustc_hir::{Body, Closure, Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::Span; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `Iterator::map` over ranges without using the parameter which + /// could be more clearly expressed using `std::iter::repeat(...).take(...)` + /// or `std::iter::repeat_n`. + /// + /// ### Why is this bad? + /// + /// It expresses the intent more clearly to `take` the correct number of times + /// from a generating function than to apply a closure to each number in a + /// range only to discard them. + /// + /// ### Example + /// + /// ```no_run + /// let random_numbers : Vec<_> = (0..10).map(|_| { 3 + 1 }).collect(); + /// ``` + /// Use instead: + /// ```no_run + /// let f : Vec<_> = std::iter::repeat( 3 + 1 ).take(10).collect(); + /// ``` + /// + /// ### Known Issues + /// + /// This lint may suggest replacing a `Map` with a `Take`. + /// The former implements some traits that the latter does not, such as + /// `DoubleEndedIterator`. + #[clippy::version = "1.84.0"] + pub MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, + restriction, + "map of a trivial closure (not dependent on parameter) over a range" +} + fn extract_count_with_applicability( cx: &LateContext<'_>, range: higher::Range<'_>, diff --git a/clippy_lints/src/methods/mut_mutex_lock.rs b/clippy_lints_methods/src/mut_mutex_lock.rs similarity index 53% rename from clippy_lints/src/methods/mut_mutex_lock.rs rename to clippy_lints_methods/src/mut_mutex_lock.rs index 320523aceb67..9ace5023f9a4 100644 --- a/clippy_lints/src/methods/mut_mutex_lock.rs +++ b/clippy_lints_methods/src/mut_mutex_lock.rs @@ -6,7 +6,41 @@ use rustc_hir::{Expr, Mutability}; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::MUT_MUTEX_LOCK; +declare_clippy_lint! { + /// ### What it does + /// Checks for `&mut Mutex::lock` calls + /// + /// ### Why is this bad? + /// `Mutex::lock` is less efficient than + /// calling `Mutex::get_mut`. In addition you also have a statically + /// guarantee that the mutex isn't locked, instead of just a runtime + /// guarantee. + /// + /// ### Example + /// ```no_run + /// use std::sync::{Arc, Mutex}; + /// + /// let mut value_rc = Arc::new(Mutex::new(42_u8)); + /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + /// + /// let mut value = value_mutex.lock().unwrap(); + /// *value += 1; + /// ``` + /// Use instead: + /// ```no_run + /// use std::sync::{Arc, Mutex}; + /// + /// let mut value_rc = Arc::new(Mutex::new(42_u8)); + /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + /// + /// 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" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>, recv: &'tcx Expr<'tcx>, name_span: Span) { if matches!(expr_custom_deref_adjustment(cx, recv), None | Some(Mutability::Mut)) diff --git a/clippy_lints/src/methods/needless_as_bytes.rs b/clippy_lints_methods/src/needless_as_bytes.rs similarity index 55% rename from clippy_lints/src/methods/needless_as_bytes.rs rename to clippy_lints_methods/src/needless_as_bytes.rs index 635d06330e05..119fabc1b110 100644 --- a/clippy_lints/src/methods/needless_as_bytes.rs +++ b/clippy_lints_methods/src/needless_as_bytes.rs @@ -6,7 +6,30 @@ use rustc_hir::{Expr, LangItem}; use rustc_lint::LateContext; use rustc_span::{Span, Symbol}; -use super::NEEDLESS_AS_BYTES; +declare_clippy_lint! { + /// ### What it does + /// It detects useless calls to `str::as_bytes()` before calling `len()` or `is_empty()`. + /// + /// ### Why is this bad? + /// The `len()` and `is_empty()` methods are also directly available on strings, and they + /// return identical results. In particular, `len()` on a string returns the number of + /// bytes. + /// + /// ### Example + /// ``` + /// let len = "some string".as_bytes().len(); + /// let b = "some string".as_bytes().is_empty(); + /// ``` + /// Use instead: + /// ``` + /// let len = "some string".len(); + /// let b = "some string".is_empty(); + /// ``` + #[clippy::version = "1.84.0"] + pub NEEDLESS_AS_BYTES, + complexity, + "detect useless calls to `as_bytes()`" +} pub fn check(cx: &LateContext<'_>, prev_method: Symbol, method: Symbol, prev_recv: &Expr<'_>, span: Span) { let ty1 = cx.typeck_results().expr_ty_adjusted(prev_recv).peel_refs(); diff --git a/clippy_lints/src/methods/needless_character_iteration.rs b/clippy_lints_methods/src/needless_character_iteration.rs similarity index 89% rename from clippy_lints/src/methods/needless_character_iteration.rs rename to clippy_lints_methods/src/needless_character_iteration.rs index 71c1576cd57d..97b8b474b939 100644 --- a/clippy_lints/src/methods/needless_character_iteration.rs +++ b/clippy_lints_methods/src/needless_character_iteration.rs @@ -1,14 +1,33 @@ +use super::utils::get_last_chain_binding_hir_id; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::{is_path_diagnostic_item, path_to_local_id, peel_blocks, sym}; use rustc_errors::Applicability; use rustc_hir::{Closure, Expr, ExprKind, HirId, StmtKind, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::Span; -use super::NEEDLESS_CHARACTER_ITERATION; -use super::utils::get_last_chain_binding_hir_id; -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::SpanRangeExt; -use clippy_utils::{is_path_diagnostic_item, path_to_local_id, peel_blocks, sym}; +declare_clippy_lint! { + /// ### What it does + /// Checks if an iterator is used to check if a string is ascii. + /// + /// ### Why is this bad? + /// The `str` type already implements the `is_ascii` method. + /// + /// ### Example + /// ```no_run + /// "foo".chars().all(|c| c.is_ascii()); + /// ``` + /// Use instead: + /// ```no_run + /// "foo".is_ascii(); + /// ``` + #[clippy::version = "1.81.0"] + pub NEEDLESS_CHARACTER_ITERATION, + suspicious, + "is_ascii() called on a char iterator" +} fn peels_expr_ref<'a, 'tcx>(mut expr: &'a Expr<'tcx>) -> &'a Expr<'tcx> { while let ExprKind::AddrOf(_, _, e) = expr.kind { diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints_methods/src/needless_collect.rs similarity index 96% rename from clippy_lints/src/methods/needless_collect.rs rename to clippy_lints_methods/src/needless_collect.rs index 0075bf166cc1..0f560a7e52de 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints_methods/src/needless_collect.rs @@ -1,6 +1,3 @@ -use std::ops::ControlFlow; - -use super::NEEDLESS_COLLECT; 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; @@ -22,6 +19,32 @@ use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, AssocTag, ClauseKind, EarlyBinder, GenericArg, GenericArgKind, Ty}; use rustc_span::Span; use rustc_span::symbol::Ident; +use std::ops::ControlFlow; + +declare_clippy_lint! { + /// ### What it does + /// Checks for functions collecting an iterator when collect + /// is not needed. + /// + /// ### Why is this bad? + /// `collect` causes the allocation of a new data structure, + /// when this allocation may not be needed. + /// + /// ### Example + /// ```no_run + /// # let iterator = vec![1].into_iter(); + /// let len = iterator.collect::>().len(); + /// ``` + /// Use instead: + /// ```no_run + /// # let iterator = vec![1].into_iter(); + /// let len = iterator.count(); + /// ``` + #[clippy::version = "1.30.0"] + pub NEEDLESS_COLLECT, + nursery, + "collecting an iterator when collect is not needed" +} const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; diff --git a/clippy_lints/src/methods/needless_option_as_deref.rs b/clippy_lints_methods/src/needless_option_as_deref.rs similarity index 64% rename from clippy_lints/src/methods/needless_option_as_deref.rs rename to clippy_lints_methods/src/needless_option_as_deref.rs index d77d044340dc..73692fcc1ecf 100644 --- a/clippy_lints/src/methods/needless_option_as_deref.rs +++ b/clippy_lints_methods/src/needless_option_as_deref.rs @@ -9,7 +9,30 @@ use rustc_hir::def::Res; use rustc_lint::LateContext; use rustc_span::Symbol; -use super::NEEDLESS_OPTION_AS_DEREF; +declare_clippy_lint! { + /// ### What it does + /// Checks for no-op uses of `Option::{as_deref, as_deref_mut}`, + /// for example, `Option<&T>::as_deref()` returns the same type. + /// + /// ### Why is this bad? + /// Redundant code and improving readability. + /// + /// ### Example + /// ```no_run + /// let a = Some(&1); + /// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32> + /// ``` + /// + /// Use instead: + /// ```no_run + /// 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`." +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, name: Symbol) { let typeck = cx.typeck_results(); diff --git a/clippy_lints/src/methods/needless_option_take.rs b/clippy_lints_methods/src/needless_option_take.rs similarity index 77% rename from clippy_lints/src/methods/needless_option_take.rs rename to clippy_lints_methods/src/needless_option_take.rs index 1544a12e6ba8..45589df16c3c 100644 --- a/clippy_lints/src/methods/needless_option_take.rs +++ b/clippy_lints_methods/src/needless_option_take.rs @@ -5,7 +5,29 @@ use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_span::{Symbol, sym}; -use super::NEEDLESS_OPTION_TAKE; +declare_clippy_lint! { + /// ### What it does + /// Checks for calling `take` function after `as_ref`. + /// + /// ### Why is this bad? + /// Redundant code. `take` writes `None` to its argument. + /// In this case the modification is useless as it's a temporary that cannot be read from afterwards. + /// + /// ### Example + /// ```no_run + /// let x = Some(3); + /// x.as_ref().take(); + /// ``` + /// Use instead: + /// ```no_run + /// let x = Some(3); + /// x.as_ref(); + /// ``` + #[clippy::version = "1.62.0"] + pub NEEDLESS_OPTION_TAKE, + complexity, + "using `.as_ref().take()` on a temporary value" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { // Checks if expression type is equal to sym::Option and if the expr is not a syntactic place diff --git a/clippy_lints/src/methods/no_effect_replace.rs b/clippy_lints_methods/src/no_effect_replace.rs similarity index 74% rename from clippy_lints/src/methods/no_effect_replace.rs rename to clippy_lints_methods/src/no_effect_replace.rs index 32f32f1b2167..a258f8e2b72a 100644 --- a/clippy_lints/src/methods/no_effect_replace.rs +++ b/clippy_lints_methods/src/no_effect_replace.rs @@ -5,7 +5,23 @@ use rustc_ast::LitKind; use rustc_hir::{ExprKind, LangItem}; use rustc_lint::LateContext; -use super::NO_EFFECT_REPLACE; +declare_clippy_lint! { + /// ### What it does + /// Checks for `replace` statements which have no effect. + /// + /// ### Why is this bad? + /// It's either a mistake or confusing. + /// + /// ### Example + /// ```no_run + /// "1234".replace("12", "12"); + /// "1234".replacen("12", "12", 1); + /// ``` + #[clippy::version = "1.63.0"] + pub NO_EFFECT_REPLACE, + suspicious, + "replace with no effect" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/obfuscated_if_else.rs b/clippy_lints_methods/src/obfuscated_if_else.rs similarity index 78% rename from clippy_lints/src/methods/obfuscated_if_else.rs rename to clippy_lints_methods/src/obfuscated_if_else.rs index 604b48656aea..380a4735c506 100644 --- a/clippy_lints/src/methods/obfuscated_if_else.rs +++ b/clippy_lints_methods/src/obfuscated_if_else.rs @@ -1,4 +1,3 @@ -use super::OBFUSCATED_IF_ELSE; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::source::snippet_with_applicability; @@ -10,6 +9,35 @@ use rustc_hir::ExprKind; use rustc_lint::LateContext; use rustc_span::Symbol; +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary method chains that can be simplified into `if .. else ..`. + /// + /// ### Why is this bad? + /// This can be written more clearly with `if .. else ..` + /// + /// ### Limitations + /// This lint currently only looks for usages of + /// `.{then, then_some}(..).{unwrap_or, unwrap_or_else, unwrap_or_default}(..)`, but will be expanded + /// to account for similar patterns. + /// + /// ### Example + /// ```no_run + /// let x = true; + /// x.then_some("a").unwrap_or("b"); + /// ``` + /// Use instead: + /// ```no_run + /// let x = true; + /// if x { "a" } else { "b" }; + /// ``` + #[clippy::version = "1.64.0"] + pub OBFUSCATED_IF_ELSE, + style, + "use of `.then_some(..).unwrap_or(..)` can be written \ + more clearly with `if .. else ..`" +} + pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, diff --git a/clippy_lints/src/methods/ok_expect.rs b/clippy_lints_methods/src/ok_expect.rs similarity index 63% rename from clippy_lints/src/methods/ok_expect.rs rename to clippy_lints_methods/src/ok_expect.rs index e10bc0216e5f..8dce01898e63 100644 --- a/clippy_lints/src/methods/ok_expect.rs +++ b/clippy_lints_methods/src/ok_expect.rs @@ -5,7 +5,30 @@ use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; use rustc_span::sym; -use super::OK_EXPECT; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `ok().expect(..)`. + /// + /// ### Why is this bad? + /// Because you usually call `expect()` on the `Result` + /// directly to get a better error message. + /// + /// ### Example + /// ```no_run + /// # let x = Ok::<_, ()>(()); + /// x.ok().expect("why did I do this again?"); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let x = Ok::<_, ()>(()); + /// 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" +} /// lint use of `ok().expect()` for `Result`s pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { diff --git a/clippy_lints/src/methods/open_options.rs b/clippy_lints_methods/src/open_options.rs similarity index 78% rename from clippy_lints/src/methods/open_options.rs rename to clippy_lints_methods/src/open_options.rs index fd368024177a..6ebd3ed0585e 100644 --- a/clippy_lints/src/methods/open_options.rs +++ b/clippy_lints_methods/src/open_options.rs @@ -10,7 +10,64 @@ use rustc_middle::ty::Ty; use rustc_span::Span; use rustc_span::source_map::Spanned; -use super::{NONSENSICAL_OPEN_OPTIONS, SUSPICIOUS_OPEN_OPTIONS}; +declare_clippy_lint! { + /// ### What it does + /// Checks for duplicate open options as well as combinations + /// that make no sense. + /// + /// ### Why is this bad? + /// In the best case, the code will be harder to read than + /// necessary. I don't know the worst case. + /// + /// ### Example + /// ```no_run + /// use std::fs::OpenOptions; + /// + /// 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" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for the suspicious use of `OpenOptions::create()` + /// without an explicit `OpenOptions::truncate()`. + /// + /// ### Why is this bad? + /// `create()` alone will either create a new file or open an + /// existing file. If the file already exists, it will be + /// overwritten when written to, but the file will not be + /// truncated by default. + /// If less data is written to the file + /// than it already contains, the remainder of the file will + /// remain unchanged, and the end of the file will contain old + /// data. + /// In most cases, one should either use `create_new` to ensure + /// the file is created from scratch, or ensure `truncate` is + /// called so that the truncation behaviour is explicit. `truncate(true)` + /// will ensure the file is entirely overwritten with new data, whereas + /// `truncate(false)` will explicitly keep the default behavior. + /// + /// ### Example + /// ```rust,no_run + /// use std::fs::OpenOptions; + /// + /// OpenOptions::new().create(true); + /// ``` + /// Use instead: + /// ```rust,no_run + /// use std::fs::OpenOptions; + /// + /// OpenOptions::new().create(true).truncate(true); + /// ``` + #[clippy::version = "1.77.0"] + pub SUSPICIOUS_OPEN_OPTIONS, + suspicious, + "suspicious combination of options for opening a file" +} fn is_open_options(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { is_type_diagnostic_item(cx, ty, sym::FsOpenOptions) || paths::TOKIO_IO_OPEN_OPTIONS.matches_ty(cx, ty) diff --git a/clippy_lints/src/methods/option_as_ref_cloned.rs b/clippy_lints_methods/src/option_as_ref_cloned.rs similarity index 57% rename from clippy_lints/src/methods/option_as_ref_cloned.rs rename to clippy_lints_methods/src/option_as_ref_cloned.rs index 3c38deca6cd1..05ae955526c2 100644 --- a/clippy_lints/src/methods/option_as_ref_cloned.rs +++ b/clippy_lints_methods/src/option_as_ref_cloned.rs @@ -1,3 +1,4 @@ +use super::method_call; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sym; use clippy_utils::ty::is_type_diagnostic_item; @@ -6,7 +7,30 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::Span; -use super::{OPTION_AS_REF_CLONED, method_call}; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.as_ref().cloned()` and `.as_mut().cloned()` on `Option`s + /// + /// ### Why is this bad? + /// This can be written more concisely by cloning the `Option` directly. + /// + /// ### Example + /// ```no_run + /// fn foo(bar: &Option>) -> Option> { + /// bar.as_ref().cloned() + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn foo(bar: &Option>) -> Option> { + /// bar.clone() + /// } + /// ``` + #[clippy::version = "1.77.0"] + pub OPTION_AS_REF_CLONED, + pedantic, + "cloning an `Option` via `as_ref().cloned()`" +} pub(super) fn check(cx: &LateContext<'_>, cloned_recv: &Expr<'_>, cloned_ident_span: Span) { if let Some((method @ (sym::as_ref | sym::as_mut), as_ref_recv, [], as_ref_ident_span, _)) = diff --git a/clippy_lints/src/methods/option_as_ref_deref.rs b/clippy_lints_methods/src/option_as_ref_deref.rs similarity index 85% rename from clippy_lints/src/methods/option_as_ref_deref.rs rename to clippy_lints_methods/src/option_as_ref_deref.rs index 63ee922acfa0..41b86b21b038 100644 --- a/clippy_lints/src/methods/option_as_ref_deref.rs +++ b/clippy_lints_methods/src/option_as_ref_deref.rs @@ -9,7 +9,31 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Symbol, sym}; -use super::OPTION_AS_REF_DEREF; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.as_ref().map(Deref::deref)` or its aliases (such as String::as_str). + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.as_deref()`. + /// + /// ### Example + /// ```no_run + /// # let opt = Some("".to_string()); + /// opt.as_ref().map(String::as_str) + /// # ; + /// ``` + /// Can be written as + /// ```no_run + /// # let opt = Some("".to_string()); + /// 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()`" +} /// lint use of `_.as_ref().map(Deref::deref)` for `Option`s pub(super) fn check( diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints_methods/src/option_map_or_none.rs similarity index 73% rename from clippy_lints/src/methods/option_map_or_none.rs rename to clippy_lints_methods/src/option_map_or_none.rs index 1a273f77fb7d..15753a4e8f52 100644 --- a/clippy_lints/src/methods/option_map_or_none.rs +++ b/clippy_lints_methods/src/option_map_or_none.rs @@ -8,7 +8,58 @@ use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; use rustc_span::symbol::sym; -use super::{OPTION_MAP_OR_NONE, RESULT_MAP_OR_INTO_OPTION}; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.map_or(None, _)`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.and_then(_)`. + /// + /// ### Known problems + /// The order of the arguments is not in execution order. + /// + /// ### Example + /// ```no_run + /// # let opt = Some(1); + /// opt.map_or(None, |a| Some(a + 1)); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let opt = Some(1); + /// 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)`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.map_or(None, Some)`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.ok()`. + /// + /// ### Example + /// ```no_run + /// # let r: Result = Ok(1); + /// assert_eq!(Some(1), r.map_or(None, Some)); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # 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()`" +} // The expression inside a closure may or may not have surrounding braces // which causes problems when generating a suggestion. diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints_methods/src/option_map_unwrap_or.rs similarity index 99% rename from clippy_lints/src/methods/option_map_unwrap_or.rs rename to clippy_lints_methods/src/option_map_unwrap_or.rs index 4ba8e0109042..0b98b0bfbe33 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints_methods/src/option_map_unwrap_or.rs @@ -12,7 +12,7 @@ use rustc_middle::hir::nested_filter; use rustc_span::{Span, sym}; use std::ops::ControlFlow; -use super::MAP_UNWRAP_OR; +use crate::map_unwrap_or::MAP_UNWRAP_OR; /// lint use of `map().unwrap_or()` for `Option`s #[expect(clippy::too_many_arguments)] diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints_methods/src/or_fun_call.rs similarity index 80% rename from clippy_lints/src/methods/or_fun_call.rs rename to clippy_lints_methods/src/or_fun_call.rs index 6ce7dd3d4d0a..d4d372273fa2 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints_methods/src/or_fun_call.rs @@ -14,7 +14,78 @@ use rustc_middle::ty; use rustc_span::{Span, Symbol}; use {rustc_ast as ast, rustc_hir as hir}; -use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT}; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`, + /// `.or_insert(foo(..))` etc., and suggests to use `.or_else(|| foo(..))`, + /// `.unwrap_or_else(|| foo(..))`, `.unwrap_or_default()` or `.or_default()` + /// etc. instead. + /// + /// ### Why is this bad? + /// The function will always be called. This is only bad if it allocates or + /// does some non-trivial amount of work. + /// + /// ### Known problems + /// If the function has side-effects, not calling it will change the + /// semantic of the program, but you shouldn't rely on that. + /// + /// The lint also cannot figure out whether the function you call is + /// actually expensive to call or not. + /// + /// ### Example + /// ```no_run + /// # let foo = Some(String::new()); + /// foo.unwrap_or(String::from("empty")); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let foo = Some(String::new()); + /// foo.unwrap_or_else(|| String::from("empty")); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub OR_FUN_CALL, + nursery, + "using any `*or` method with a function call, which suggests `*or_else`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usages of the following functions with an argument that constructs a default value + /// (e.g., `Default::default` or `String::new`): + /// - `unwrap_or` + /// - `unwrap_or_else` + /// - `or_insert` + /// - `or_insert_with` + /// + /// ### Why is this bad? + /// Readability. Using `unwrap_or_default` in place of `unwrap_or`/`unwrap_or_else`, or `or_default` + /// in place of `or_insert`/`or_insert_with`, is simpler and more concise. + /// + /// ### Known problems + /// In some cases, the argument of `unwrap_or`, etc. is needed for type inference. The lint uses a + /// heuristic to try to identify such cases. However, the heuristic can produce false negatives. + /// + /// ### Examples + /// ```no_run + /// # let x = Some(1); + /// # let mut map = std::collections::HashMap::::new(); + /// x.unwrap_or(Default::default()); + /// map.entry(42).or_insert_with(String::new); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let x = Some(1); + /// # let mut map = std::collections::HashMap::::new(); + /// x.unwrap_or_default(); + /// map.entry(42).or_default(); + /// ``` + #[clippy::version = "1.56.0"] + pub UNWRAP_OR_DEFAULT, + style, + "using `.unwrap_or`, etc. with an argument that constructs a default value" +} /// Checks for the `OR_FUN_CALL` lint. #[expect(clippy::too_many_lines)] diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints_methods/src/or_then_unwrap.rs similarity index 65% rename from clippy_lints/src/methods/or_then_unwrap.rs rename to clippy_lints_methods/src/or_then_unwrap.rs index 3e64e15dc860..69031b6abc50 100644 --- a/clippy_lints/src/methods/or_then_unwrap.rs +++ b/clippy_lints_methods/src/or_then_unwrap.rs @@ -8,7 +8,41 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::OR_THEN_UNWRAP; +declare_clippy_lint! { + /// ### What it does + /// Checks for `.or(…).unwrap()` calls to Options and Results. + /// + /// ### Why is this bad? + /// You should use `.unwrap_or(…)` instead for clarity. + /// + /// ### Example + /// ```no_run + /// # let fallback = "fallback"; + /// // Result + /// # type Error = &'static str; + /// # let result: Result<&str, Error> = Err("error"); + /// let value = result.or::(Ok(fallback)).unwrap(); + /// + /// // Option + /// # let option: Option<&str> = None; + /// let value = option.or(Some(fallback)).unwrap(); + /// ``` + /// Use instead: + /// ```no_run + /// # let fallback = "fallback"; + /// // Result + /// # let result: Result<&str, &str> = Err("error"); + /// let value = result.unwrap_or(fallback); + /// + /// // Option + /// # let option: Option<&str> = None; + /// let value = option.unwrap_or(fallback); + /// ``` + #[clippy::version = "1.61.0"] + pub OR_THEN_UNWRAP, + complexity, + "checks for `.or(…).unwrap()` calls to Options and Results." +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/path_buf_push_overwrite.rs b/clippy_lints_methods/src/path_buf_push_overwrite.rs similarity index 59% rename from clippy_lints/src/methods/path_buf_push_overwrite.rs rename to clippy_lints_methods/src/path_buf_push_overwrite.rs index 38d9c5f16778..5a4d6c5afcad 100644 --- a/clippy_lints/src/methods/path_buf_push_overwrite.rs +++ b/clippy_lints_methods/src/path_buf_push_overwrite.rs @@ -7,7 +7,37 @@ use rustc_lint::LateContext; use rustc_span::symbol::sym; use std::path::{Component, Path}; -use super::PATH_BUF_PUSH_OVERWRITE; +declare_clippy_lint! { + /// ### What it does + ///* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push) + /// calls on `PathBuf` that can cause overwrites. + /// + /// ### Why is this bad? + /// Calling `push` with a root path at the start can overwrite the + /// previous defined path. + /// + /// ### Example + /// ```no_run + /// use std::path::PathBuf; + /// + /// let mut x = PathBuf::from("/foo"); + /// x.push("/bar"); + /// assert_eq!(x, PathBuf::from("/bar")); + /// ``` + /// Could be written: + /// + /// ```no_run + /// use std::path::PathBuf; + /// + /// let mut x = PathBuf::from("/foo"); + /// 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" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) diff --git a/clippy_lints/src/methods/path_ends_with_ext.rs b/clippy_lints_methods/src/path_ends_with_ext.rs similarity index 53% rename from clippy_lints/src/methods/path_ends_with_ext.rs rename to clippy_lints_methods/src/path_ends_with_ext.rs index d3f513e7abd2..3693673c29dc 100644 --- a/clippy_lints/src/methods/path_ends_with_ext.rs +++ b/clippy_lints_methods/src/path_ends_with_ext.rs @@ -1,4 +1,3 @@ -use super::PATH_ENDS_WITH_EXT; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet; @@ -11,6 +10,45 @@ use rustc_lint::LateContext; use rustc_span::sym; use std::fmt::Write; +declare_clippy_lint! { + /// ### What it does + /// Looks for calls to `Path::ends_with` calls where the argument looks like a file extension. + /// + /// By default, Clippy has a short list of known filenames that start with a dot + /// but aren't necessarily file extensions (e.g. the `.git` folder), which are allowed by default. + /// The `allowed-dotfiles` configuration can be used to allow additional + /// file extensions that Clippy should not lint. + /// + /// ### Why is this bad? + /// This doesn't actually compare file extensions. Rather, `ends_with` compares the given argument + /// to the last **component** of the path and checks if it matches exactly. + /// + /// ### Known issues + /// File extensions are often at most three characters long, so this only lints in those cases + /// in an attempt to avoid false positives. + /// Any extension names longer than that are assumed to likely be real path components and are + /// therefore ignored. + /// + /// ### Example + /// ```no_run + /// # use std::path::Path; + /// fn is_markdown(path: &Path) -> bool { + /// path.ends_with(".md") + /// } + /// ``` + /// Use instead: + /// ```no_run + /// # use std::path::Path; + /// fn is_markdown(path: &Path) -> bool { + /// path.extension().is_some_and(|ext| ext == "md") + /// } + /// ``` + #[clippy::version = "1.74.0"] + pub PATH_ENDS_WITH_EXT, + suspicious, + "attempting to compare file extensions using `Path::ends_with`" +} + pub const DEFAULT_ALLOWED_DOTFILES: &[&str] = &[ "git", "svn", "gem", "npm", "vim", "env", "rnd", "ssh", "vnc", "smb", "nvm", "bin", ]; diff --git a/clippy_lints/src/methods/range_zip_with_len.rs b/clippy_lints_methods/src/range_zip_with_len.rs similarity index 69% rename from clippy_lints/src/methods/range_zip_with_len.rs rename to clippy_lints_methods/src/range_zip_with_len.rs index 3a5e32172086..e310164e61e2 100644 --- a/clippy_lints/src/methods/range_zip_with_len.rs +++ b/clippy_lints_methods/src/range_zip_with_len.rs @@ -6,7 +6,30 @@ use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_span::sym; -use super::RANGE_ZIP_WITH_LEN; +declare_clippy_lint! { + /// ### What it does + /// Checks for zipping a collection with the range of + /// `0.._.len()`. + /// + /// ### Why is this bad? + /// The code is better expressed with `.enumerate()`. + /// + /// ### Example + /// ```no_run + /// # let x = vec![1]; + /// let _ = x.iter().zip(0..x.len()); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let x = vec![1]; + /// let _ = x.iter().enumerate(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub RANGE_ZIP_WITH_LEN, + complexity, + "zipping iterator with a range when `enumerate()` would do" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, zip_arg: &'tcx Expr<'_>) { if is_trait_method(cx, expr, sym::Iterator) diff --git a/clippy_lints/src/methods/read_line_without_trim.rs b/clippy_lints_methods/src/read_line_without_trim.rs similarity index 81% rename from clippy_lints/src/methods/read_line_without_trim.rs rename to clippy_lints_methods/src/read_line_without_trim.rs index 407f2e80aff2..0fe0094b14fb 100644 --- a/clippy_lints/src/methods/read_line_without_trim.rs +++ b/clippy_lints_methods/src/read_line_without_trim.rs @@ -12,7 +12,36 @@ use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; -use super::READ_LINE_WITHOUT_TRIM; +declare_clippy_lint! { + /// ### What it does + /// Looks for calls to [`Stdin::read_line`] to read a line from the standard input + /// into a string, then later attempting to use that string for an operation that will never + /// work for strings with a trailing newline character in it (e.g. parsing into a `i32`). + /// + /// ### Why is this bad? + /// The operation will always fail at runtime no matter what the user enters, thus + /// making it a useless operation. + /// + /// ### Example + /// ```rust,ignore + /// let mut input = String::new(); + /// std::io::stdin().read_line(&mut input).expect("Failed to read a line"); + /// let num: i32 = input.parse().expect("Not a number!"); + /// assert_eq!(num, 42); // we never even get here! + /// ``` + /// Use instead: + /// ```rust,ignore + /// let mut input = String::new(); + /// std::io::stdin().read_line(&mut input).expect("Failed to read a line"); + /// let num: i32 = input.trim_end().parse().expect("Not a number!"); + /// // ^^^^^^^^^^^ remove the trailing newline + /// assert_eq!(num, 42); + /// ``` + #[clippy::version = "1.73.0"] + pub READ_LINE_WITHOUT_TRIM, + correctness, + "calling `Stdin::read_line`, then trying to parse it without first trimming" +} fn expr_is_string_literal_without_trailing_newline(expr: &Expr<'_>) -> bool { if let ExprKind::Lit(lit) = expr.kind diff --git a/clippy_lints/src/methods/readonly_write_lock.rs b/clippy_lints_methods/src/readonly_write_lock.rs similarity index 73% rename from clippy_lints/src/methods/readonly_write_lock.rs rename to clippy_lints_methods/src/readonly_write_lock.rs index 40b6becd4532..c5898775e80b 100644 --- a/clippy_lints/src/methods/readonly_write_lock.rs +++ b/clippy_lints_methods/src/readonly_write_lock.rs @@ -1,4 +1,3 @@ -use super::READONLY_WRITE_LOCK; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::mir::{enclosing_mir, visit_local_usage}; use clippy_utils::source::snippet; @@ -9,6 +8,37 @@ use rustc_lint::LateContext; use rustc_middle::mir::{Location, START_BLOCK}; use rustc_span::sym; +declare_clippy_lint! { + /// ### What it does + /// Looks for calls to `RwLock::write` where the lock is only used for reading. + /// + /// ### Why is this bad? + /// The write portion of `RwLock` is exclusive, meaning that no other thread + /// can access the lock while this writer is active. + /// + /// ### Example + /// ```no_run + /// use std::sync::RwLock; + /// fn assert_is_zero(lock: &RwLock) { + /// let num = lock.write().unwrap(); + /// assert_eq!(*num, 0); + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// use std::sync::RwLock; + /// fn assert_is_zero(lock: &RwLock) { + /// let num = lock.read().unwrap(); + /// assert_eq!(*num, 0); + /// } + /// ``` + #[clippy::version = "1.73.0"] + pub READONLY_WRITE_LOCK, + perf, + "acquiring a write lock when a read lock would work" +} + fn is_unwrap_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if let ExprKind::MethodCall(path, receiver, [], _) = expr.kind && path.ident.name == sym::unwrap diff --git a/clippy_lints/src/methods/redundant_as_str.rs b/clippy_lints_methods/src/redundant_as_str.rs similarity index 55% rename from clippy_lints/src/methods/redundant_as_str.rs rename to clippy_lints_methods/src/redundant_as_str.rs index 24de1979c631..47945a43244a 100644 --- a/clippy_lints/src/methods/redundant_as_str.rs +++ b/clippy_lints_methods/src/redundant_as_str.rs @@ -1,4 +1,3 @@ -use super::REDUNDANT_AS_STR; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; @@ -6,6 +5,32 @@ use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_span::Span; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `as_str()` on a `String` chained with a method available on the `String` itself. + /// + /// ### Why is this bad? + /// The `as_str()` conversion is pointless and can be removed for simplicity and cleanliness. + /// + /// ### Example + /// ```no_run + /// let owned_string = "This is a string".to_owned(); + /// owned_string.as_str().as_bytes() + /// # ; + /// ``` + /// + /// Use instead: + /// ```no_run + /// let owned_string = "This is a string".to_owned(); + /// owned_string.as_bytes() + /// # ; + /// ``` + #[clippy::version = "1.74.0"] + pub REDUNDANT_AS_STR, + complexity, + "`as_str` used to call a method on `str` that is also available on `String`" +} + pub(super) fn check( cx: &LateContext<'_>, _expr: &Expr<'_>, diff --git a/clippy_lints/src/methods/repeat_once.rs b/clippy_lints_methods/src/repeat_once.rs similarity index 60% rename from clippy_lints/src/methods/repeat_once.rs rename to clippy_lints_methods/src/repeat_once.rs index 7837517ed5d8..5f49efad1a5e 100644 --- a/clippy_lints/src/methods/repeat_once.rs +++ b/clippy_lints_methods/src/repeat_once.rs @@ -6,7 +6,37 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, LangItem}; use rustc_lint::LateContext; -use super::REPEAT_ONCE; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.repeat(1)` and suggest the following method for each types. + /// - `.to_string()` for `str` + /// - `.clone()` for `String` + /// - `.to_vec()` for `slice` + /// + /// The lint will evaluate constant expressions and values as arguments of `.repeat(..)` and emit a message if + /// they are equivalent to `1`. (Related discussion in [rust-clippy#7306](https://github.com/rust-lang/rust-clippy/issues/7306)) + /// + /// ### Why is this bad? + /// For example, `String.repeat(1)` is equivalent to `.clone()`. If cloning + /// the string is the intention behind this, `clone()` should be used. + /// + /// ### Example + /// ```no_run + /// fn main() { + /// let x = String::from("hello world").repeat(1); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn main() { + /// 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()` " +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/result_map_or_else_none.rs b/clippy_lints_methods/src/result_map_or_else_none.rs similarity index 96% rename from clippy_lints/src/methods/result_map_or_else_none.rs rename to clippy_lints_methods/src/result_map_or_else_none.rs index af619c9e3bb1..65a866a8828d 100644 --- a/clippy_lints/src/methods/result_map_or_else_none.rs +++ b/clippy_lints_methods/src/result_map_or_else_none.rs @@ -8,7 +8,7 @@ use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; use rustc_span::symbol::sym; -use super::RESULT_MAP_OR_INTO_OPTION; +use crate::option_map_or_none::RESULT_MAP_OR_INTO_OPTION; /// lint use of `_.map_or_else(|_| None, Some)` for `Result`s pub(super) fn check<'tcx>( diff --git a/clippy_lints/src/methods/return_and_then.rs b/clippy_lints_methods/src/return_and_then.rs similarity index 76% rename from clippy_lints/src/methods/return_and_then.rs rename to clippy_lints_methods/src/return_and_then.rs index 54f38a322b8d..d4d1ddc0c35a 100644 --- a/clippy_lints/src/methods/return_and_then.rs +++ b/clippy_lints_methods/src/return_and_then.rs @@ -11,7 +11,44 @@ use clippy_utils::ty::get_type_diagnostic_name; use clippy_utils::visitors::for_each_unconsumed_temporary; use clippy_utils::{peel_blocks, potential_return_of_enclosing_body}; -use super::RETURN_AND_THEN; +declare_clippy_lint! { + /// ### What it does + /// Detect functions that end with `Option::and_then` or `Result::and_then`, and suggest using + /// the `?` operator instead. + /// + /// ### Why is this bad? + /// The `and_then` method is used to chain a computation that returns an `Option` or a `Result`. + /// This can be replaced with the `?` operator, which is more concise and idiomatic. + /// + /// ### Example + /// + /// ```no_run + /// fn test(opt: Option) -> Option { + /// opt.and_then(|n| { + /// if n > 1 { + /// Some(n + 1) + /// } else { + /// None + /// } + /// }) + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn test(opt: Option) -> Option { + /// let n = opt?; + /// if n > 1 { + /// Some(n + 1) + /// } else { + /// None + /// } + /// } + /// ``` + #[clippy::version = "1.86.0"] + pub RETURN_AND_THEN, + restriction, + "using `Option::and_then` or `Result::and_then` to chain a computation that returns an `Option` or a `Result`" +} /// lint if `and_then` is the last expression in a block, and /// there are no references or temporaries in the receiver diff --git a/clippy_lints/src/methods/search_is_some.rs b/clippy_lints_methods/src/search_is_some.rs similarity index 86% rename from clippy_lints/src/methods/search_is_some.rs rename to clippy_lints_methods/src/search_is_some.rs index 855babb797a2..07dac3846ca7 100644 --- a/clippy_lints/src/methods/search_is_some.rs +++ b/clippy_lints_methods/src/search_is_some.rs @@ -10,7 +10,36 @@ use rustc_hir::PatKind; use rustc_lint::LateContext; use rustc_span::{Span, Symbol}; -use super::SEARCH_IS_SOME; +declare_clippy_lint! { + /// ### What it does + /// Checks for an iterator or string search (such as `find()`, + /// `position()`, or `rposition()`) followed by a call to `is_some()` or `is_none()`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as: + /// * `_.any(_)`, or `_.contains(_)` for `is_some()`, + /// * `!_.any(_)`, or `!_.contains(_)` for `is_none()`. + /// + /// ### Example + /// ```no_run + /// let vec = vec![1]; + /// vec.iter().find(|x| **x == 0).is_some(); + /// + /// "hello world".find("world").is_none(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let vec = vec![1]; + /// vec.iter().any(|x| *x == 0); + /// + /// !"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()`)" +} /// lint searching an Iterator followed by `is_some()` /// or calling `find()` on a string followed by `is_some()` or `is_none()` diff --git a/clippy_lints/src/methods/seek_from_current.rs b/clippy_lints_methods/src/seek_from_current.rs similarity index 58% rename from clippy_lints/src/methods/seek_from_current.rs rename to clippy_lints_methods/src/seek_from_current.rs index 8b51268da465..e4bf5d1f115d 100644 --- a/clippy_lints/src/methods/seek_from_current.rs +++ b/clippy_lints_methods/src/seek_from_current.rs @@ -9,7 +9,48 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::implements_trait; use clippy_utils::{is_enum_variant_ctor, sym}; -use super::SEEK_FROM_CURRENT; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks if the `seek` method of the `Seek` trait is called with `SeekFrom::Current(0)`, + /// and if it is, suggests using `stream_position` instead. + /// + /// ### Why is this bad? + /// + /// Readability. Use dedicated method. + /// + /// ### Example + /// + /// ```rust,no_run + /// use std::fs::File; + /// use std::io::{self, Write, Seek, SeekFrom}; + /// + /// fn main() -> io::Result<()> { + /// let mut f = File::create("foo.txt")?; + /// f.write_all(b"Hello")?; + /// eprintln!("Written {} bytes", f.seek(SeekFrom::Current(0))?); + /// + /// Ok(()) + /// } + /// ``` + /// Use instead: + /// ```rust,no_run + /// use std::fs::File; + /// use std::io::{self, Write, Seek, SeekFrom}; + /// + /// fn main() -> io::Result<()> { + /// let mut f = File::create("foo.txt")?; + /// f.write_all(b"Hello")?; + /// eprintln!("Written {} bytes", f.stream_position()?); + /// + /// Ok(()) + /// } + /// ``` + #[clippy::version = "1.67.0"] + pub SEEK_FROM_CURRENT, + complexity, + "use dedicated method for seek from current position" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { let ty = cx.typeck_results().expr_ty(recv); diff --git a/clippy_lints/src/methods/seek_to_start_instead_of_rewind.rs b/clippy_lints_methods/src/seek_to_start_instead_of_rewind.rs similarity index 66% rename from clippy_lints/src/methods/seek_to_start_instead_of_rewind.rs rename to clippy_lints_methods/src/seek_to_start_instead_of_rewind.rs index b8405a78f23a..53e4d2695f8f 100644 --- a/clippy_lints/src/methods/seek_to_start_instead_of_rewind.rs +++ b/clippy_lints_methods/src/seek_to_start_instead_of_rewind.rs @@ -8,7 +8,36 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::Span; -use super::SEEK_TO_START_INSTEAD_OF_REWIND; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for jumps to the start of a stream that implements `Seek` + /// and uses the `seek` method providing `Start` as parameter. + /// + /// ### Why is this bad? + /// + /// Readability. There is a specific method that was implemented for + /// this exact scenario. + /// + /// ### Example + /// ```no_run + /// # use std::io; + /// fn foo(t: &mut T) { + /// t.seek(io::SeekFrom::Start(0)); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// # use std::io; + /// fn foo(t: &mut T) { + /// t.rewind(); + /// } + /// ``` + #[clippy::version = "1.67.0"] + pub SEEK_TO_START_INSTEAD_OF_REWIND, + complexity, + "jumping to the start of stream using `seek` method" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints_methods/src/single_char_add_str.rs b/clippy_lints_methods/src/single_char_add_str.rs new file mode 100644 index 000000000000..c55900fa660f --- /dev/null +++ b/clippy_lints_methods/src/single_char_add_str.rs @@ -0,0 +1,145 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal}; +use rustc_ast::BorrowKind; +use rustc_errors::Applicability; +use rustc_hir::{self as hir, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Warns when using `push_str`/`insert_str` with a single-character string literal + /// where `push`/`insert` with a `char` would work fine. + /// + /// ### Why is this bad? + /// It's less clear that we are pushing a single character. + /// + /// ### Example + /// ```no_run + /// # let mut string = String::new(); + /// string.insert_str(0, "R"); + /// string.push_str("R"); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let mut string = String::new(); + /// 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" +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { + if cx.tcx.is_diagnostic_item(sym::string_push_str, fn_def_id) { + check_push_str(cx, expr, receiver, args); + } else if cx.tcx.is_diagnostic_item(sym::string_insert_str, fn_def_id) { + check_insert_str(cx, expr, receiver, args); + } + } +} + +/// lint for length-1 `str`s as argument for `push_str` +fn check_push_str(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let mut applicability = Applicability::MachineApplicable; + if let Some(extension_string) = str_literal_to_char_literal(cx, &args[0], &mut applicability, false) { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); + let sugg = format!("{base_string_snippet}.push({extension_string})"); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `push_str()` using a single-character string literal", + "consider using `push` with a character literal", + sugg, + applicability, + ); + } + + if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[0].kind + && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind + && path_segment.ident.name == sym::to_string + && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) + { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); + let extension_string = + snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); + let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; + + let sugg = format!("{base_string_snippet}.push({deref_string}{extension_string})"); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `push_str()` using a single-character converted to string", + "consider using `push` without `to_string()`", + sugg, + applicability, + ); + } +} + +/// lint for length-1 `str`s as argument for `insert_str` +fn check_insert_str(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let mut applicability = Applicability::MachineApplicable; + if let Some(extension_string) = str_literal_to_char_literal(cx, &args[1], &mut applicability, false) { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability); + let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); + let sugg = format!("{base_string_snippet}.insert({pos_arg}, {extension_string})"); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `insert_str()` using a single-character string literal", + "consider using `insert` with a character literal", + sugg, + applicability, + ); + } + + if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[1].kind + && let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind + && path_segment.ident.name == sym::to_string + && (is_ref_char(cx, method_arg) || is_char(cx, method_arg)) + { + let base_string_snippet = + snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability); + let extension_string = + snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability); + let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability); + let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" }; + + let sugg = format!("{base_string_snippet}.insert({pos_arg}, {deref_string}{extension_string})"); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `insert_str()` using a single-character converted to string", + "consider using `insert` without `to_string()`", + sugg, + applicability, + ); + } +} + +fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + if cx.typeck_results().expr_ty(expr).is_ref() + && let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind() + && ty.is_char() + { + return true; + } + + false +} + +fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + cx.typeck_results().expr_ty(expr).is_char() +} diff --git a/clippy_lints/src/methods/skip_while_next.rs b/clippy_lints_methods/src/skip_while_next.rs similarity index 50% rename from clippy_lints/src/methods/skip_while_next.rs rename to clippy_lints_methods/src/skip_while_next.rs index 9f0b6c34ea2e..5f4d0681f546 100644 --- a/clippy_lints/src/methods/skip_while_next.rs +++ b/clippy_lints_methods/src/skip_while_next.rs @@ -4,7 +4,30 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::SKIP_WHILE_NEXT; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `_.skip_while(condition).next()`. + /// + /// ### Why is this bad? + /// Readability, this can be written more concisely as + /// `_.find(!condition)`. + /// + /// ### Example + /// ```no_run + /// # let vec = vec![1]; + /// vec.iter().skip_while(|x| **x == 0).next(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # 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)`" +} /// lint use of `skip_while().next()` for `Iterators` pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { diff --git a/clippy_lints/src/methods/sliced_string_as_bytes.rs b/clippy_lints_methods/src/sliced_string_as_bytes.rs similarity index 52% rename from clippy_lints/src/methods/sliced_string_as_bytes.rs rename to clippy_lints_methods/src/sliced_string_as_bytes.rs index 6d4cfdb34f31..7377dacdde6a 100644 --- a/clippy_lints/src/methods/sliced_string_as_bytes.rs +++ b/clippy_lints_methods/src/sliced_string_as_bytes.rs @@ -5,7 +5,33 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem, is_range_literal}; use rustc_lint::LateContext; -use super::SLICED_STRING_AS_BYTES; +declare_clippy_lint! { + /// ### What it does + /// Checks for string slices immediately followed by `as_bytes`. + /// + /// ### Why is this bad? + /// It involves doing an unnecessary UTF-8 alignment check which is less efficient, and can cause a panic. + /// + /// ### Known problems + /// In some cases, the UTF-8 validation and potential panic from string slicing may be required for + /// the code's correctness. If you need to ensure the slice boundaries fall on valid UTF-8 character + /// boundaries, the original form (`s[1..5].as_bytes()`) should be preferred. + /// + /// ### Example + /// ```rust + /// let s = "Lorem ipsum"; + /// s[1..5].as_bytes(); + /// ``` + /// Use instead: + /// ```rust + /// let s = "Lorem ipsum"; + /// &s.as_bytes()[1..5]; + /// ``` + #[clippy::version = "1.86.0"] + pub SLICED_STRING_AS_BYTES, + perf, + "slicing a string and immediately calling as_bytes is less efficient and can lead to panics" +} pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>) { if let ExprKind::Index(indexed, index, _) = recv.kind diff --git a/clippy_lints_methods/src/stable_sort_primitive.rs b/clippy_lints_methods/src/stable_sort_primitive.rs new file mode 100644 index 000000000000..0856ad7b1b13 --- /dev/null +++ b/clippy_lints_methods/src/stable_sort_primitive.rs @@ -0,0 +1,67 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_slice_of_primitives; +use clippy_utils::source::snippet_with_context; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; + +declare_clippy_lint! { + /// ### What it does + /// When sorting primitive values (integers, bools, chars, as well + /// as arrays, slices, and tuples of such items), it is typically better to + /// use an unstable sort than a stable sort. + /// + /// ### Why is this bad? + /// Typically, using a stable sort consumes more memory and cpu cycles. + /// Because values which compare equal are identical, preserving their + /// relative order (the guarantee that a stable sort provides) means + /// nothing, while the extra costs still apply. + /// + /// ### Known problems + /// + /// As pointed out in + /// [issue #8241](https://github.com/rust-lang/rust-clippy/issues/8241), + /// a stable sort can instead be significantly faster for certain scenarios + /// (eg. when a sorted vector is extended with new data and resorted). + /// + /// For more information and benchmarking results, please refer to the + /// issue linked above. + /// + /// ### Example + /// ```no_run + /// let mut vec = vec![2, 1, 3]; + /// vec.sort(); + /// ``` + /// Use instead: + /// ```no_run + /// let mut vec = vec![2, 1, 3]; + /// vec.sort_unstable(); + /// ``` + #[clippy::version = "1.47.0"] + pub STABLE_SORT_PRIMITIVE, + pedantic, + "use of sort() when sort_unstable() is equivalent" +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && let Some(impl_id) = cx.tcx.impl_of_method(method_id) + && cx.tcx.type_of(impl_id).instantiate_identity().is_slice() + && let Some(slice_type) = is_slice_of_primitives(cx, recv) + { + span_lint_and_then( + cx, + STABLE_SORT_PRIMITIVE, + e.span, + format!("used `sort` on primitive type `{slice_type}`"), + |diag| { + let mut app = Applicability::MachineApplicable; + let recv_snip = snippet_with_context(cx, recv.span, e.span.ctxt(), "..", &mut app).0; + diag.span_suggestion(e.span, "try", format!("{recv_snip}.sort_unstable()"), app); + diag.note( + "an unstable sort typically performs faster without any observable difference for this data type", + ); + }, + ); + } +} diff --git a/clippy_lints/src/methods/str_split.rs b/clippy_lints_methods/src/str_split.rs similarity index 64% rename from clippy_lints/src/methods/str_split.rs rename to clippy_lints_methods/src/str_split.rs index 479064a0671e..0c106b1baf24 100644 --- a/clippy_lints/src/methods/str_split.rs +++ b/clippy_lints_methods/src/str_split.rs @@ -7,7 +7,34 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; -use super::STR_SPLIT_AT_NEWLINE; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for usages of `str.trim().split("\n")` and `str.trim().split("\r\n")`. + /// + /// ### Why is this bad? + /// + /// Hard-coding the line endings makes the code less compatible. `str.lines` should be used instead. + /// + /// ### Example + /// ```no_run + /// "some\ntext\nwith\nnewlines\n".trim().split('\n'); + /// ``` + /// Use instead: + /// ```no_run + /// "some\ntext\nwith\nnewlines\n".lines(); + /// ``` + /// + /// ### Known Problems + /// + /// This lint cannot detect if the split is intentionally restricted to a single type of newline (`"\n"` or + /// `"\r\n"`), for example during the parsing of a specific file format in which precisely one newline type is + /// valid. + #[clippy::version = "1.77.0"] + pub STR_SPLIT_AT_NEWLINE, + pedantic, + "splitting a trimmed string at hard-coded newlines" +} pub(super) fn check<'a>(cx: &LateContext<'a>, expr: &'_ Expr<'_>, split_recv: &'a Expr<'_>, split_arg: &'_ Expr<'_>) { // We're looking for `A.trim().split(B)`, where the adjusted type of `A` is `&str` (e.g. an diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints_methods/src/str_splitn.rs similarity index 86% rename from clippy_lints/src/methods/str_splitn.rs rename to clippy_lints_methods/src/str_splitn.rs index 6f78d6c61281..c55905ecbe05 100644 --- a/clippy_lints/src/methods/str_splitn.rs +++ b/clippy_lints_methods/src/str_splitn.rs @@ -14,7 +14,64 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Span, Symbol, SyntaxContext}; -use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN}; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `str::splitn(2, _)` + /// + /// ### Why is this bad? + /// `split_once` is both clearer in intent and slightly more efficient. + /// + /// ### Example + /// ```rust,ignore + /// let s = "key=value=add"; + /// let (key, value) = s.splitn(2, '=').next_tuple()?; + /// let value = s.splitn(2, '=').nth(1)?; + /// + /// let mut parts = s.splitn(2, '='); + /// let key = parts.next()?; + /// let value = parts.next()?; + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// let s = "key=value=add"; + /// let (key, value) = s.split_once('=')?; + /// let value = s.split_once('=')?.1; + /// + /// let (key, value) = s.split_once('=')?; + /// ``` + /// + /// ### Limitations + /// The multiple statement variant currently only detects `iter.next()?`/`iter.next().unwrap()` + /// in two separate `let` statements that immediately follow the `splitn()` + #[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 usage 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 + /// ```no_run + /// let str = "key=value=add"; + /// let _ = str.splitn(3, '=').next().unwrap(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let str = "key=value=add"; + /// let _ = str.split('=').next().unwrap(); + /// ``` + #[clippy::version = "1.59.0"] + pub NEEDLESS_SPLITN, + complexity, + "usages of `str::splitn` that can be replaced with `str::split`" +} pub(super) fn check( cx: &LateContext<'_>, diff --git a/clippy_lints/src/methods/string_extend_chars.rs b/clippy_lints_methods/src/string_extend_chars.rs similarity index 66% rename from clippy_lints/src/methods/string_extend_chars.rs rename to clippy_lints_methods/src/string_extend_chars.rs index f11a41f90f1a..a3fe671a11f3 100644 --- a/clippy_lints/src/methods/string_extend_chars.rs +++ b/clippy_lints_methods/src/string_extend_chars.rs @@ -6,7 +6,35 @@ use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; -use super::STRING_EXTEND_CHARS; +declare_clippy_lint! { + /// ### What it does + /// Checks for the use of `.extend(s.chars())` where s is a + /// `&str` or `String`. + /// + /// ### Why is this bad? + /// `.push_str(s)` is clearer + /// + /// ### Example + /// ```no_run + /// let abc = "abc"; + /// let def = String::from("def"); + /// let mut s = String::new(); + /// s.extend(abc.chars()); + /// s.extend(def.chars()); + /// ``` + /// The correct use would be: + /// ```no_run + /// let abc = "abc"; + /// let def = String::from("def"); + /// let mut s = String::new(); + /// 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`" +} pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) { let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); diff --git a/clippy_lints/src/methods/string_lit_chars_any.rs b/clippy_lints_methods/src/string_lit_chars_any.rs similarity index 67% rename from clippy_lints/src/methods/string_lit_chars_any.rs rename to clippy_lints_methods/src/string_lit_chars_any.rs index f0f9d30d3000..8dc915bd1c52 100644 --- a/clippy_lints/src/methods/string_lit_chars_any.rs +++ b/clippy_lints_methods/src/string_lit_chars_any.rs @@ -9,7 +9,33 @@ use rustc_hir::{BinOpKind, Expr, ExprKind, Param, PatKind}; use rustc_lint::LateContext; use rustc_span::sym; -use super::STRING_LIT_CHARS_ANY; +declare_clippy_lint! { + /// ### What it does + /// Checks for `.chars().any(|i| i == c)`. + /// + /// ### Why is this bad? + /// It's significantly slower than using a pattern instead, like + /// `matches!(c, '\\' | '.' | '+')`. + /// + /// Despite this being faster, this is not `perf` as this is pretty common, and is a rather nice + /// way to check if a `char` is any in a set. In any case, this `restriction` lint is available + /// for situations where that additional performance is absolutely necessary. + /// + /// ### Example + /// ```no_run + /// # let c = 'c'; + /// "\\.+*?()|[]{}^$#&-~".chars().any(|x| x == c); + /// ``` + /// Use instead: + /// ```no_run + /// # let c = 'c'; + /// matches!(c, '\\' | '.' | '+' | '*' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' | '~'); + /// ``` + #[clippy::version = "1.73.0"] + pub STRING_LIT_CHARS_ANY, + restriction, + "checks for `.chars().any(|i| i == c)`" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/suspicious_command_arg_space.rs b/clippy_lints_methods/src/suspicious_command_arg_space.rs similarity index 59% rename from clippy_lints/src/methods/suspicious_command_arg_space.rs rename to clippy_lints_methods/src/suspicious_command_arg_space.rs index c60a49067ec0..1608dc874d58 100644 --- a/clippy_lints/src/methods/suspicious_command_arg_space.rs +++ b/clippy_lints_methods/src/suspicious_command_arg_space.rs @@ -5,7 +5,31 @@ use rustc_lint::LateContext; use rustc_span::{Span, sym}; use {rustc_ast as ast, rustc_hir as hir}; -use super::SUSPICIOUS_COMMAND_ARG_SPACE; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `Command::arg()` invocations that look like they + /// should be multiple arguments instead, such as `arg("-t ext2")`. + /// + /// ### Why is this bad? + /// + /// `Command::arg()` does not split arguments by space. An argument like `arg("-t ext2")` + /// will be passed as a single argument to the command, + /// which is likely not what was intended. + /// + /// ### Example + /// ```no_run + /// std::process::Command::new("echo").arg("-n hello").spawn().unwrap(); + /// ``` + /// Use instead: + /// ```no_run + /// std::process::Command::new("echo").args(["-n", "hello"]).spawn().unwrap(); + /// ``` + #[clippy::version = "1.69.0"] + pub SUSPICIOUS_COMMAND_ARG_SPACE, + suspicious, + "single command line argument that looks like it should be multiple arguments" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, span: Span) { let ty = cx.typeck_results().expr_ty(recv).peel_refs(); diff --git a/clippy_lints/src/methods/suspicious_map.rs b/clippy_lints_methods/src/suspicious_map.rs similarity index 65% rename from clippy_lints/src/methods/suspicious_map.rs rename to clippy_lints_methods/src/suspicious_map.rs index 788014d9bb63..3a7460930d05 100644 --- a/clippy_lints/src/methods/suspicious_map.rs +++ b/clippy_lints_methods/src/suspicious_map.rs @@ -5,7 +5,25 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::SUSPICIOUS_MAP; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `map` followed by a `count`. + /// + /// ### Why is this bad? + /// It looks suspicious. Maybe `map` was confused with `filter`. + /// If the `map` call is intentional, this should be rewritten + /// using `inspect`. Or, if you intend to drive the iterator to + /// completion, you can just use `for_each` instead. + /// + /// ### Example + /// ```no_run + /// let _ = (0..3).map(|x| x + 2).count(); + /// ``` + #[clippy::version = "1.39.0"] + pub SUSPICIOUS_MAP, + suspicious, + "suspicious usage of map" +} pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, count_recv: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) { if is_trait_method(cx, count_recv, sym::Iterator) diff --git a/clippy_lints/src/methods/suspicious_splitn.rs b/clippy_lints_methods/src/suspicious_splitn.rs similarity index 67% rename from clippy_lints/src/methods/suspicious_splitn.rs rename to clippy_lints_methods/src/suspicious_splitn.rs index f8b6d4349fbe..cc5ff10d8887 100644 --- a/clippy_lints/src/methods/suspicious_splitn.rs +++ b/clippy_lints_methods/src/suspicious_splitn.rs @@ -5,7 +5,36 @@ use rustc_lint::LateContext; use rustc_span::Symbol; use rustc_span::source_map::Spanned; -use super::SUSPICIOUS_SPLITN; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to [`splitn`] + /// (https://doc.rust-lang.org/std/primitive.str.html#method.splitn) and + /// related functions with either zero or one splits. + /// + /// ### Why is this bad? + /// These calls don't actually split the value and are + /// likely to be intended as a different number. + /// + /// ### Example + /// ```no_run + /// # let s = ""; + /// for x in s.splitn(1, ":") { + /// // .. + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let s = ""; + /// for x in s.splitn(2, ":") { + /// // .. + /// } + /// ``` + #[clippy::version = "1.54.0"] + pub SUSPICIOUS_SPLITN, + correctness, + "checks for `.splitn(0, ..)` and `.splitn(1, ..)`" +} pub(super) fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &Expr<'_>, self_arg: &Expr<'_>, count: u128) { if count <= 1 diff --git a/clippy_lints/src/methods/suspicious_to_owned.rs b/clippy_lints_methods/src/suspicious_to_owned.rs similarity index 52% rename from clippy_lints/src/methods/suspicious_to_owned.rs rename to clippy_lints_methods/src/suspicious_to_owned.rs index ce7aefed01f4..bc57cd1384d1 100644 --- a/clippy_lints/src/methods/suspicious_to_owned.rs +++ b/clippy_lints_methods/src/suspicious_to_owned.rs @@ -8,7 +8,53 @@ use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_middle::ty::{self}; use rustc_span::sym; -use super::SUSPICIOUS_TO_OWNED; +declare_clippy_lint! { + /// ### What it does + /// Checks for the usage of `_.to_owned()`, on a `Cow<'_, _>`. + /// + /// ### Why is this bad? + /// Calling `to_owned()` on a `Cow` creates a clone of the `Cow` + /// itself, without taking ownership of the `Cow` contents (i.e. + /// it's equivalent to calling `Cow::clone`). + /// The similarly named `into_owned` method, on the other hand, + /// clones the `Cow` contents, effectively turning any `Cow::Borrowed` + /// into a `Cow::Owned`. + /// + /// Given the potential ambiguity, consider replacing `to_owned` + /// with `clone` for better readability or, if getting a `Cow::Owned` + /// was the original intent, using `into_owned` instead. + /// + /// ### Example + /// ```no_run + /// # use std::borrow::Cow; + /// let s = "Hello world!"; + /// let cow = Cow::Borrowed(s); + /// + /// let data = cow.to_owned(); + /// assert!(matches!(data, Cow::Borrowed(_))) + /// ``` + /// Use instead: + /// ```no_run + /// # use std::borrow::Cow; + /// let s = "Hello world!"; + /// let cow = Cow::Borrowed(s); + /// + /// let data = cow.clone(); + /// assert!(matches!(data, Cow::Borrowed(_))) + /// ``` + /// or + /// ```no_run + /// # use std::borrow::Cow; + /// let s = "Hello world!"; + /// let cow = Cow::Borrowed(s); + /// + /// let _data: String = cow.into_owned(); + /// ``` + #[clippy::version = "1.65.0"] + pub SUSPICIOUS_TO_OWNED, + suspicious, + "calls to `to_owned` on a `Cow<'_, _>` might not do what they are expected" +} pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) -> bool { if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) diff --git a/clippy_lints/src/methods/swap_with_temporary.rs b/clippy_lints_methods/src/swap_with_temporary.rs similarity index 78% rename from clippy_lints/src/methods/swap_with_temporary.rs rename to clippy_lints_methods/src/swap_with_temporary.rs index e378cbd6ae0a..2b0a2780d452 100644 --- a/clippy_lints/src/methods/swap_with_temporary.rs +++ b/clippy_lints_methods/src/swap_with_temporary.rs @@ -7,7 +7,52 @@ use rustc_lint::LateContext; use rustc_middle::ty::adjustment::Adjust; use rustc_span::sym; -use super::SWAP_WITH_TEMPORARY; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `std::mem::swap` with temporary values. + /// + /// ### Why is this bad? + /// Storing a new value in place of a temporary value which will + /// be dropped right after the `swap` is an inefficient way of performing + /// an assignment. The same result can be achieved by using a regular + /// assignment. + /// + /// ### Examples + /// ```no_run + /// fn replace_string(s: &mut String) { + /// std::mem::swap(s, &mut String::from("replaced")); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn replace_string(s: &mut String) { + /// *s = String::from("replaced"); + /// } + /// ``` + /// + /// Also, swapping two temporary values has no effect, as they will + /// both be dropped right after swapping them. This is likely an indication + /// of a bug. For example, the following code swaps the references to + /// the last element of the vectors, instead of swapping the elements + /// themselves: + /// + /// ```no_run + /// fn bug(v1: &mut [i32], v2: &mut [i32]) { + /// // Incorrect: swapping temporary references (`&mut &mut` passed to swap) + /// std::mem::swap(&mut v1.last_mut().unwrap(), &mut v2.last_mut().unwrap()); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn correct(v1: &mut [i32], v2: &mut [i32]) { + /// std::mem::swap(v1.last_mut().unwrap(), v2.last_mut().unwrap()); + /// } + /// ``` + #[clippy::version = "1.88.0"] + pub SWAP_WITH_TEMPORARY, + complexity, + "detect swap with a temporary value" +} const MSG_TEMPORARY: &str = "this expression returns a temporary value"; const MSG_TEMPORARY_REFMUT: &str = "this is a mutable reference to a temporary value"; diff --git a/clippy_lints/src/methods/type_id_on_box.rs b/clippy_lints_methods/src/type_id_on_box.rs similarity index 67% rename from clippy_lints/src/methods/type_id_on_box.rs rename to clippy_lints_methods/src/type_id_on_box.rs index e67ba5c4d314..c416d6d767bc 100644 --- a/clippy_lints/src/methods/type_id_on_box.rs +++ b/clippy_lints_methods/src/type_id_on_box.rs @@ -1,4 +1,3 @@ -use crate::methods::TYPE_ID_ON_BOX; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use rustc_errors::Applicability; @@ -9,6 +8,46 @@ use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_middle::ty::{self, ExistentialPredicate, Ty}; use rustc_span::{Span, sym}; +declare_clippy_lint! { + /// ### What it does + /// Looks for calls to `.type_id()` on a `Box`. + /// + /// ### Why is this bad? + /// This almost certainly does not do what the user expects and can lead to subtle bugs. + /// Calling `.type_id()` on a `Box` returns a fixed `TypeId` of the `Box` itself, + /// rather than returning the `TypeId` of the underlying type behind the trait object. + /// + /// For `Box` specifically (and trait objects that have `Any` as its supertrait), + /// this lint will provide a suggestion, which is to dereference the receiver explicitly + /// to go from `Box` to `dyn Any`. + /// This makes sure that `.type_id()` resolves to a dynamic call on the trait object + /// and not on the box. + /// + /// If the fixed `TypeId` of the `Box` is the intended behavior, it's better to be explicit about it + /// and write `TypeId::of::>()`: + /// this makes it clear that a fixed `TypeId` is returned and not the `TypeId` of the implementor. + /// + /// ### Example + /// ```rust,ignore + /// use std::any::{Any, TypeId}; + /// + /// let any_box: Box = Box::new(42_i32); + /// assert_eq!(any_box.type_id(), TypeId::of::()); // ⚠️ this fails! + /// ``` + /// Use instead: + /// ```no_run + /// use std::any::{Any, TypeId}; + /// + /// let any_box: Box = Box::new(42_i32); + /// assert_eq!((*any_box).type_id(), TypeId::of::()); + /// // ^ dereference first, to call `type_id` on `dyn Any` + /// ``` + #[clippy::version = "1.73.0"] + pub TYPE_ID_ON_BOX, + suspicious, + "calling `.type_id()` on a boxed trait object" +} + /// Checks if the given type is `dyn Any`, or a trait object that has `Any` as a supertrait. /// Only in those cases will its vtable have a `type_id` method that returns the implementor's /// `TypeId`, and only in those cases can we give a proper suggestion to dereference the box. diff --git a/clippy_lints/src/methods/unbuffered_bytes.rs b/clippy_lints_methods/src/unbuffered_bytes.rs similarity index 50% rename from clippy_lints/src/methods/unbuffered_bytes.rs rename to clippy_lints_methods/src/unbuffered_bytes.rs index dd5566f8c8ba..4d03aaeb675e 100644 --- a/clippy_lints/src/methods/unbuffered_bytes.rs +++ b/clippy_lints_methods/src/unbuffered_bytes.rs @@ -1,4 +1,3 @@ -use super::UNBUFFERED_BYTES; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::is_trait_method; use clippy_utils::ty::implements_trait; @@ -6,6 +5,33 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `Read::bytes` on types which don't implement `BufRead`. + /// + /// ### Why is this bad? + /// The default implementation calls `read` for each byte, which can be very inefficient for data that’s not in memory, such as `File`. + /// + /// ### Example + /// ```no_run + /// use std::io::Read; + /// use std::fs::File; + /// let file = File::open("./bytes.txt").unwrap(); + /// file.bytes(); + /// ``` + /// Use instead: + /// ```no_run + /// use std::io::{BufReader, Read}; + /// use std::fs::File; + /// let file = BufReader::new(File::open("./bytes.txt").unwrap()); + /// file.bytes(); + /// ``` + #[clippy::version = "1.87.0"] + pub UNBUFFERED_BYTES, + perf, + "calling .bytes() is very inefficient when data is not in memory" +} + pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { // Lint if the `.bytes()` call is from the `Read` trait and the implementor is not buffered. if is_trait_method(cx, expr, sym::IoRead) diff --git a/clippy_lints_methods/src/uninit_assumed_init.rs b/clippy_lints_methods/src/uninit_assumed_init.rs new file mode 100644 index 000000000000..46c4a4c94956 --- /dev/null +++ b/clippy_lints_methods/src/uninit_assumed_init.rs @@ -0,0 +1,56 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::is_path_diagnostic_item; +use clippy_utils::ty::is_uninit_value_valid_for_ty; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `MaybeUninit::uninit().assume_init()`. + /// + /// ### Why is this bad? + /// For most types, this is undefined behavior. + /// + /// ### Known problems + /// For now, we accept empty tuples and tuples / arrays + /// of `MaybeUninit`. There may be other types that allow uninitialized + /// data, but those are not yet rigorously defined. + /// + /// ### Example + /// ```no_run + /// // Beware the UB + /// use std::mem::MaybeUninit; + /// + /// let _: usize = unsafe { MaybeUninit::uninit().assume_init() }; + /// ``` + /// + /// Note that the following is OK: + /// + /// ```no_run + /// use std::mem::MaybeUninit; + /// + /// let _: [MaybeUninit; 5] = unsafe { + /// MaybeUninit::uninit().assume_init() + /// }; + /// ``` + #[clippy::version = "1.39.0"] + pub UNINIT_ASSUMED_INIT, + correctness, + "`MaybeUninit::uninit().assume_init()`" +} + +/// lint for `MaybeUninit::uninit().assume_init()` (we already have the latter) +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { + if let hir::ExprKind::Call(callee, []) = recv.kind + && is_path_diagnostic_item(cx, callee, sym::maybe_uninit_uninit) + && !is_uninit_value_valid_for_ty(cx, cx.typeck_results().expr_ty_adjusted(expr)) + { + span_lint( + cx, + UNINIT_ASSUMED_INIT, + expr.span, + "this call for this type may be undefined behavior", + ); + } +} diff --git a/clippy_lints_methods/src/unit_hash.rs b/clippy_lints_methods/src/unit_hash.rs new file mode 100644 index 000000000000..a9af8f18c080 --- /dev/null +++ b/clippy_lints_methods/src/unit_hash.rs @@ -0,0 +1,66 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_trait_method; +use clippy_utils::source::snippet; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Detects `().hash(_)`. + /// + /// ### Why is this bad? + /// Hashing a unit value doesn't do anything as the implementation of `Hash` for `()` is a no-op. + /// + /// ### Example + /// ```no_run + /// # use std::hash::Hash; + /// # use std::collections::hash_map::DefaultHasher; + /// # enum Foo { Empty, WithValue(u8) } + /// # use Foo::*; + /// # let mut state = DefaultHasher::new(); + /// # let my_enum = Foo::Empty; + /// match my_enum { + /// Empty => ().hash(&mut state), + /// WithValue(x) => x.hash(&mut state), + /// } + /// ``` + /// Use instead: + /// ```no_run + /// # use std::hash::Hash; + /// # use std::collections::hash_map::DefaultHasher; + /// # enum Foo { Empty, WithValue(u8) } + /// # use Foo::*; + /// # let mut state = DefaultHasher::new(); + /// # let my_enum = Foo::Empty; + /// match my_enum { + /// Empty => 0_u8.hash(&mut state), + /// WithValue(x) => x.hash(&mut state), + /// } + /// ``` + #[clippy::version = "1.58.0"] + pub UNIT_HASH, + correctness, + "hashing a unit value, which does nothing" +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { + if is_trait_method(cx, expr, sym::Hash) && cx.typeck_results().expr_ty(recv).is_unit() { + span_lint_and_then( + cx, + UNIT_HASH, + expr.span, + "this call to `hash` on the unit type will do nothing", + |diag| { + diag.span_suggestion( + expr.span, + "remove the call to `hash` or consider using", + format!("0_u8.hash({})", snippet(cx, arg.span, ".."),), + Applicability::MaybeIncorrect, + ); + diag.note("the implementation of `Hash` for `()` is a no-op"); + }, + ); + } +} diff --git a/clippy_lints/src/methods/unnecessary_fallible_conversions.rs b/clippy_lints_methods/src/unnecessary_fallible_conversions.rs similarity index 88% rename from clippy_lints/src/methods/unnecessary_fallible_conversions.rs rename to clippy_lints_methods/src/unnecessary_fallible_conversions.rs index ce81282ddfeb..7bacf2cf0521 100644 --- a/clippy_lints/src/methods/unnecessary_fallible_conversions.rs +++ b/clippy_lints_methods/src/unnecessary_fallible_conversions.rs @@ -8,7 +8,32 @@ use rustc_middle::ty; use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_span::{Span, sym}; -use super::UNNECESSARY_FALLIBLE_CONVERSIONS; +declare_clippy_lint! { + /// ### What it does + /// Checks for calls to `TryInto::try_into` and `TryFrom::try_from` when their infallible counterparts + /// could be used. + /// + /// ### Why is this bad? + /// In those cases, the `TryInto` and `TryFrom` trait implementation is a blanket impl that forwards + /// to `Into` or `From`, which always succeeds. + /// The returned `Result<_, Infallible>` requires error handling to get the contained value + /// even though the conversion can never fail. + /// + /// ### Example + /// ```rust + /// let _: Result = 1i32.try_into(); + /// let _: Result = <_>::try_from(1i32); + /// ``` + /// Use `from`/`into` instead: + /// ```rust + /// let _: i64 = 1i32.into(); + /// let _: i64 = <_>::from(1i32); + /// ``` + #[clippy::version = "1.75.0"] + pub UNNECESSARY_FALLIBLE_CONVERSIONS, + style, + "calling the `try_from` and `try_into` trait methods when `From`/`Into` is implemented" +} #[derive(Copy, Clone)] enum SpansKind { diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints_methods/src/unnecessary_filter_map.rs similarity index 71% rename from clippy_lints/src/methods/unnecessary_filter_map.rs rename to clippy_lints_methods/src/unnecessary_filter_map.rs index d260e0ef6e19..fa6800f19889 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints_methods/src/unnecessary_filter_map.rs @@ -11,7 +11,65 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::Symbol; -use super::{UNNECESSARY_FILTER_MAP, UNNECESSARY_FIND_MAP}; +declare_clippy_lint! { + /// ### What it does + /// Checks for `filter_map` calls that could be replaced by `filter` or `map`. + /// More specifically it checks if the closure provided is only performing one of the + /// filter or map operations and suggests the appropriate option. + /// + /// ### Why is this bad? + /// Complexity. The intent is also clearer if only a single + /// operation is being performed. + /// + /// ### Example + /// ```no_run + /// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None }); + /// + /// // As there is no transformation of the argument this could be written as: + /// let _ = (0..3).filter(|&x| x > 2); + /// ``` + /// + /// ```no_run + /// let _ = (0..4).filter_map(|x| Some(x + 1)); + /// + /// // 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" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `find_map` calls that could be replaced by `find` or `map`. More + /// specifically it checks if the closure provided is only performing one of the + /// find or map operations and suggests the appropriate option. + /// + /// ### Why is this bad? + /// Complexity. The intent is also clearer if only a single + /// operation is being performed. + /// + /// ### Example + /// ```no_run + /// let _ = (0..3).find_map(|x| if x > 2 { Some(x) } else { None }); + /// + /// // As there is no transformation of the argument this could be written as: + /// let _ = (0..3).find(|&x| x > 2); + /// ``` + /// + /// ```no_run + /// let _ = (0..4).find_map(|x| Some(x + 1)); + /// + /// // As there is no conditional check on the argument this could be written as: + /// let _ = (0..4).map(|x| x + 1).next(); + /// ``` + #[clippy::version = "1.61.0"] + pub UNNECESSARY_FIND_MAP, + complexity, + "using `find_map` when a more succinct alternative exists" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/unnecessary_first_then_check.rs b/clippy_lints_methods/src/unnecessary_first_then_check.rs similarity index 66% rename from clippy_lints/src/methods/unnecessary_first_then_check.rs rename to clippy_lints_methods/src/unnecessary_first_then_check.rs index d322909bef35..378c7941e845 100644 --- a/clippy_lints/src/methods/unnecessary_first_then_check.rs +++ b/clippy_lints_methods/src/unnecessary_first_then_check.rs @@ -6,7 +6,33 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::Span; -use super::UNNECESSARY_FIRST_THEN_CHECK; +declare_clippy_lint! { + /// ### What it does + /// Checks the usage of `.first().is_some()` or `.first().is_none()` to check if a slice is + /// empty. + /// + /// ### Why is this bad? + /// Using `.is_empty()` is shorter and better communicates the intention. + /// + /// ### Example + /// ```no_run + /// let v = vec![1, 2, 3]; + /// if v.first().is_none() { + /// // The vector is empty... + /// } + /// ``` + /// Use instead: + /// ```no_run + /// let v = vec![1, 2, 3]; + /// if v.is_empty() { + /// // The vector is empty... + /// } + /// ``` + #[clippy::version = "1.83.0"] + pub UNNECESSARY_FIRST_THEN_CHECK, + complexity, + "calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`" +} pub(super) fn check( cx: &LateContext<'_>, diff --git a/clippy_lints/src/methods/unnecessary_fold.rs b/clippy_lints_methods/src/unnecessary_fold.rs similarity index 90% rename from clippy_lints/src/methods/unnecessary_fold.rs rename to clippy_lints_methods/src/unnecessary_fold.rs index 8e3cc9abe832..4cecffc4f781 100644 --- a/clippy_lints/src/methods/unnecessary_fold.rs +++ b/clippy_lints_methods/src/unnecessary_fold.rs @@ -10,7 +10,29 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Span, sym}; -use super::UNNECESSARY_FOLD; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `fold` when a more succinct alternative exists. + /// Specifically, this checks for `fold`s which could be replaced by `any`, `all`, + /// `sum` or `product`. + /// + /// ### Why is this bad? + /// Readability. + /// + /// ### Example + /// ```no_run + /// (0..3).fold(false, |acc, x| acc || x > 2); + /// ``` + /// + /// Use instead: + /// ```no_run + /// (0..3).any(|x| x > 2); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub UNNECESSARY_FOLD, + style, + "using `fold` when a more succinct alternative exists" +} /// Do we need to suggest turbofish when suggesting a replacement method? /// Changing `fold` to `sum` needs it sometimes when the return type can't be diff --git a/clippy_lints/src/methods/unnecessary_get_then_check.rs b/clippy_lints_methods/src/unnecessary_get_then_check.rs similarity index 76% rename from clippy_lints/src/methods/unnecessary_get_then_check.rs rename to clippy_lints_methods/src/unnecessary_get_then_check.rs index 39fce2c40c91..a881a3542361 100644 --- a/clippy_lints/src/methods/unnecessary_get_then_check.rs +++ b/clippy_lints_methods/src/unnecessary_get_then_check.rs @@ -8,7 +8,34 @@ use rustc_lint::LateContext; use rustc_middle::ty::Ty; use rustc_span::{Span, sym}; -use super::UNNECESSARY_GET_THEN_CHECK; +declare_clippy_lint! { + /// ### What it does + /// Checks the usage of `.get().is_some()` or `.get().is_none()` on std map types. + /// + /// ### Why is this bad? + /// It can be done in one call with `.contains()`/`.contains_key()`. + /// + /// ### Example + /// ```no_run + /// # use std::collections::HashSet; + /// let s: HashSet = HashSet::new(); + /// if s.get("a").is_some() { + /// // code + /// } + /// ``` + /// Use instead: + /// ```no_run + /// # use std::collections::HashSet; + /// let s: HashSet = HashSet::new(); + /// if s.contains("a") { + /// // code + /// } + /// ``` + #[clippy::version = "1.78.0"] + pub UNNECESSARY_GET_THEN_CHECK, + suspicious, + "calling `.get().is_some()` or `.get().is_none()` instead of `.contains()` or `.contains_key()`" +} fn is_a_std_set_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { is_type_diagnostic_item(cx, ty, sym::HashSet) || is_type_diagnostic_item(cx, ty, sym::BTreeSet) diff --git a/clippy_lints_methods/src/unnecessary_join.rs b/clippy_lints_methods/src/unnecessary_join.rs new file mode 100644 index 000000000000..3add04930b68 --- /dev/null +++ b/clippy_lints_methods/src/unnecessary_join.rs @@ -0,0 +1,70 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::ty::is_type_lang_item; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, LangItem}; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.collect::>().join("")` on iterators. + /// + /// ### Why is this bad? + /// `.collect::()` is more concise and might be more performant + /// + /// ### Example + /// ```no_run + /// let vector = vec!["hello", "world"]; + /// let output = vector.iter().map(|item| item.to_uppercase()).collect::>().join(""); + /// println!("{}", output); + /// ``` + /// The correct use would be: + /// ```no_run + /// let vector = vec!["hello", "world"]; + /// let output = vector.iter().map(|item| item.to_uppercase()).collect::(); + /// println!("{}", output); + /// ``` + /// ### Known problems + /// While `.collect::()` is sometimes more performant, there are cases where + /// using `.collect::()` over `.collect::>().join("")` + /// will prevent loop unrolling and will result in a negative performance impact. + /// + /// Additionally, differences have been observed between aarch64 and x86_64 assembly output, + /// with aarch64 tending to producing faster assembly in more cases when using `.collect::()` + #[clippy::version = "1.61.0"] + pub UNNECESSARY_JOIN, + pedantic, + "using `.collect::>().join(\"\")` on an iterator" +} + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + join_self_arg: &'tcx Expr<'tcx>, + join_arg: &'tcx Expr<'tcx>, + span: Span, +) { + let applicability = Applicability::MachineApplicable; + let collect_output_adjusted_type = cx.typeck_results().expr_ty_adjusted(join_self_arg); + if let ty::Ref(_, ref_type, _) = collect_output_adjusted_type.kind() + // the turbofish for collect is ::> + && let ty::Slice(slice) = ref_type.kind() + && is_type_lang_item(cx, *slice, LangItem::String) + // the argument for join is "" + && let ExprKind::Lit(spanned) = &join_arg.kind + && let LitKind::Str(symbol, _) = spanned.node + && symbol.is_empty() + { + span_lint_and_sugg( + cx, + UNNECESSARY_JOIN, + span.with_hi(expr.span.hi()), + r#"called `.collect::>().join("")` on an iterator"#, + "consider using", + "collect::()".to_owned(), + applicability, + ); + } +} diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints_methods/src/unnecessary_lazy_eval.rs similarity index 71% rename from clippy_lints/src/methods/unnecessary_lazy_eval.rs rename to clippy_lints_methods/src/unnecessary_lazy_eval.rs index 71e606add526..2eec2ad381b8 100644 --- a/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/clippy_lints_methods/src/unnecessary_lazy_eval.rs @@ -8,7 +8,44 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; -use super::UNNECESSARY_LAZY_EVALUATIONS; +declare_clippy_lint! { + /// ### What it does + /// As the counterpart to `or_fun_call`, this lint looks for unnecessary + /// lazily evaluated closures on `Option` and `Result`. + /// + /// This lint suggests changing the following functions, when eager evaluation results in + /// simpler code: + /// - `unwrap_or_else` to `unwrap_or` + /// - `and_then` to `and` + /// - `or_else` to `or` + /// - `get_or_insert_with` to `get_or_insert` + /// - `ok_or_else` to `ok_or` + /// - `then` to `then_some` (for msrv >= 1.62.0) + /// + /// ### Why is this bad? + /// Using eager evaluation is shorter and simpler in some cases. + /// + /// ### Known problems + /// It is possible, but not recommended for `Deref` and `Index` to have + /// side effects. Eagerly evaluating them can change the semantics of the program. + /// + /// ### Example + /// ```no_run + /// let opt: Option = None; + /// + /// opt.unwrap_or_else(|| 42); + /// ``` + /// Use instead: + /// ```no_run + /// let opt: Option = None; + /// + /// 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" +} /// lint use of `_else(simple closure)` for `Option`s and `Result`s that can be /// replaced with `(return value of simple closure)` diff --git a/clippy_lints/src/methods/unnecessary_literal_unwrap.rs b/clippy_lints_methods/src/unnecessary_literal_unwrap.rs similarity index 88% rename from clippy_lints/src/methods/unnecessary_literal_unwrap.rs rename to clippy_lints_methods/src/unnecessary_literal_unwrap.rs index cc4448192d3e..9d02078da681 100644 --- a/clippy_lints/src/methods/unnecessary_literal_unwrap.rs +++ b/clippy_lints_methods/src/unnecessary_literal_unwrap.rs @@ -7,7 +7,31 @@ use rustc_middle::ty; use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_span::Symbol; -use super::UNNECESSARY_LITERAL_UNWRAP; +declare_clippy_lint! { + /// ### What it does + /// Checks for `.unwrap()` related calls on `Result`s and `Option`s that are constructed. + /// + /// ### Why is this bad? + /// It is better to write the value directly without the indirection. + /// + /// ### Examples + /// ```no_run + /// let val1 = Some(1).unwrap(); + /// let val2 = Ok::<_, ()>(1).unwrap(); + /// let val3 = Err::<(), _>(1).unwrap_err(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let val1 = 1; + /// let val2 = 1; + /// let val3 = 1; + /// ``` + #[clippy::version = "1.72.0"] + pub UNNECESSARY_LITERAL_UNWRAP, + complexity, + "using `unwrap()` related calls on `Result` and `Option` constructors" +} fn get_ty_from_args<'a>(args: Option<&'a [hir::GenericArg<'a>]>, index: usize) -> Option<&'a hir::Ty<'a, AmbigArg>> { let args = args?; diff --git a/clippy_lints/src/methods/unnecessary_map_or.rs b/clippy_lints_methods/src/unnecessary_map_or.rs similarity index 82% rename from clippy_lints/src/methods/unnecessary_map_or.rs rename to clippy_lints_methods/src/unnecessary_map_or.rs index b90748dd1585..e52701077788 100644 --- a/clippy_lints/src/methods/unnecessary_map_or.rs +++ b/clippy_lints_methods/src/unnecessary_map_or.rs @@ -13,7 +13,40 @@ use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind}; use rustc_lint::LateContext; use rustc_span::{Span, sym}; -use super::UNNECESSARY_MAP_OR; +declare_clippy_lint! { + /// ### What it does + /// Converts some constructs mapping an Enum value for equality comparison. + /// + /// ### Why is this bad? + /// Calls such as `opt.map_or(false, |val| val == 5)` are needlessly long and cumbersome, + /// and can be reduced to, for example, `opt == Some(5)` assuming `opt` implements `PartialEq`. + /// Also, calls such as `opt.map_or(true, |val| val == 5)` can be reduced to + /// `opt.is_none_or(|val| val == 5)`. + /// This lint offers readability and conciseness improvements. + /// + /// ### Example + /// ```no_run + /// pub fn a(x: Option) -> (bool, bool) { + /// ( + /// x.map_or(false, |n| n == 5), + /// x.map_or(true, |n| n > 5), + /// ) + /// } + /// ``` + /// Use instead: + /// ```no_run + /// pub fn a(x: Option) -> (bool, bool) { + /// ( + /// x == Some(5), + /// x.is_none_or(|n| n > 5), + /// ) + /// } + /// ``` + #[clippy::version = "1.84.0"] + pub UNNECESSARY_MAP_OR, + style, + "reduce unnecessary calls to `.map_or(bool, …)`" +} pub(super) enum Variant { Ok, diff --git a/clippy_lints/src/methods/unnecessary_min_or_max.rs b/clippy_lints_methods/src/unnecessary_min_or_max.rs similarity index 81% rename from clippy_lints/src/methods/unnecessary_min_or_max.rs rename to clippy_lints_methods/src/unnecessary_min_or_max.rs index 413881d5ec99..229cc57110e3 100644 --- a/clippy_lints/src/methods/unnecessary_min_or_max.rs +++ b/clippy_lints_methods/src/unnecessary_min_or_max.rs @@ -1,16 +1,38 @@ -use std::cmp::Ordering; - -use super::UNNECESSARY_MIN_OR_MAX; use clippy_utils::consts::{ConstEvalCtxt, Constant, ConstantSource, FullInt}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; - use clippy_utils::sym; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Span, Symbol}; +use std::cmp::Ordering; + +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary calls to `min()` or `max()` in the following cases + /// - Either both side is constant + /// - One side is clearly larger than the other, like i32::MIN and an i32 variable + /// + /// ### Why is this bad? + /// + /// In the aforementioned cases it is not necessary to call `min()` or `max()` + /// to compare values, it may even cause confusion. + /// + /// ### Example + /// ```no_run + /// let _ = 0.min(7_u32); + /// ``` + /// Use instead: + /// ```no_run + /// let _ = 0; + /// ``` + #[clippy::version = "1.81.0"] + pub UNNECESSARY_MIN_OR_MAX, + complexity, + "using 'min()/max()' when there is no need for it" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/unnecessary_result_map_or_else.rs b/clippy_lints_methods/src/unnecessary_result_map_or_else.rs similarity index 77% rename from clippy_lints/src/methods/unnecessary_result_map_or_else.rs rename to clippy_lints_methods/src/unnecessary_result_map_or_else.rs index f84d0d6dff0a..74783090bc0c 100644 --- a/clippy_lints/src/methods/unnecessary_result_map_or_else.rs +++ b/clippy_lints_methods/src/unnecessary_result_map_or_else.rs @@ -1,3 +1,4 @@ +use super::utils::get_last_chain_binding_hir_id; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::peel_blocks; use clippy_utils::source::snippet; @@ -8,8 +9,30 @@ use rustc_hir::{Closure, Expr, ExprKind, HirId, QPath}; use rustc_lint::LateContext; use rustc_span::symbol::sym; -use super::UNNECESSARY_RESULT_MAP_OR_ELSE; -use super::utils::get_last_chain_binding_hir_id; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.map_or_else()` "map closure" for `Result` type. + /// + /// ### Why is this bad? + /// This can be written more concisely by using `unwrap_or_else()`. + /// + /// ### Example + /// ```no_run + /// # fn handle_error(_: ()) -> u32 { 0 } + /// let x: Result = Ok(0); + /// let y = x.map_or_else(|err| handle_error(err), |n| n); + /// ``` + /// Use instead: + /// ```no_run + /// # fn handle_error(_: ()) -> u32 { 0 } + /// let x: Result = Ok(0); + /// let y = x.unwrap_or_else(|err| handle_error(err)); + /// ``` + #[clippy::version = "1.78.0"] + pub UNNECESSARY_RESULT_MAP_OR_ELSE, + suspicious, + "making no use of the \"map closure\" when calling `.map_or_else(|err| handle_error(err), |n| n)`" +} fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>) { let msg = "unused \"map closure\" when calling `Result::map_or_else` value"; diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints_methods/src/unnecessary_sort_by.rs similarity index 89% rename from clippy_lints/src/methods/unnecessary_sort_by.rs rename to clippy_lints_methods/src/unnecessary_sort_by.rs index edd4ecd2b4ac..68c216211eb7 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints_methods/src/unnecessary_sort_by.rs @@ -11,7 +11,39 @@ use rustc_span::sym; use rustc_span::symbol::Ident; use std::iter; -use super::UNNECESSARY_SORT_BY; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `Vec::sort_by` passing in a closure + /// which compares the two arguments, either directly or indirectly. + /// + /// ### Why is this bad? + /// It is more clear to use `Vec::sort_by_key` (or `Vec::sort` if + /// possible) than to use `Vec::sort_by` and a more complicated + /// closure. + /// + /// ### Known problems + /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't already + /// imported by a use statement, then it will need to be added manually. + /// + /// ### Example + /// ```no_run + /// # struct A; + /// # impl A { fn foo(&self) {} } + /// # let mut vec: Vec = Vec::new(); + /// vec.sort_by(|a, b| a.foo().cmp(&b.foo())); + /// ``` + /// Use instead: + /// ```no_run + /// # struct A; + /// # impl A { fn foo(&self) {} } + /// # 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" +} enum LintTrigger { Sort(SortDetection), diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints_methods/src/unnecessary_to_owned.rs similarity index 81% rename from clippy_lints/src/methods/unnecessary_to_owned.rs rename to clippy_lints_methods/src/unnecessary_to_owned.rs index 769526d131bf..a09a1a87539e 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints_methods/src/unnecessary_to_owned.rs @@ -1,18 +1,20 @@ use super::implicit_clone::is_clone_like; -use super::unnecessary_iter_cloned::{self, is_into_iter}; +use crate::utils::clone_or_copy_needed; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::higher::ForLoop; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{SpanRangeExt, snippet}; use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item}; -use clippy_utils::visitors::find_all_ret_expressions; +use clippy_utils::visitors::{find_all_ret_expressions, for_each_expr_without_closures}; use clippy_utils::{ - fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, is_expr_temporary_value, peel_middle_ty_refs, - return_ty, sym, + can_mut_borrow_both, fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, is_expr_temporary_value, + path_to_local, peel_middle_ty_refs, return_ty, sym, }; +use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, LangItem, Node}; +use rustc_hir::{BindingMode, BorrowKind, Expr, ExprKind, ItemKind, LangItem, Node, PatKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::mir::Mutability; @@ -24,7 +26,36 @@ use rustc_span::Symbol; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_trait_selection::traits::{Obligation, ObligationCause}; -use super::UNNECESSARY_TO_OWNED; +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary calls to [`ToOwned::to_owned`](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned) + /// and other `to_owned`-like functions. + /// + /// ### Why is this bad? + /// The unnecessary calls result in useless allocations. + /// + /// ### Known problems + /// `unnecessary_to_owned` can falsely trigger if `IntoIterator::into_iter` is applied to an + /// owned copy of a resource and the resource is later used mutably. See + /// [#8148](https://github.com/rust-lang/rust-clippy/issues/8148). + /// + /// ### Example + /// ```no_run + /// let path = std::path::Path::new("x"); + /// foo(&path.to_string_lossy().to_string()); + /// fn foo(s: &str) {} + /// ``` + /// Use instead: + /// ```no_run + /// let path = std::path::Path::new("x"); + /// foo(&path.to_string_lossy()); + /// fn foo(s: &str) {} + /// ``` + #[clippy::version = "1.59.0"] + pub UNNECESSARY_TO_OWNED, + perf, + "unnecessary calls to `to_owned`-like functions" +} pub fn check<'tcx>( cx: &LateContext<'tcx>, @@ -38,7 +69,7 @@ pub fn check<'tcx>( && args.is_empty() { if is_cloned_or_copied(cx, method_name, method_def_id) { - unnecessary_iter_cloned::check(cx, expr, method_name, receiver); + check_iter_cloned(cx, expr, method_name, receiver); } else if is_to_owned_like(cx, expr, method_name, method_def_id) { if check_split_call_arg(cx, expr, method_name, receiver) { return; @@ -223,7 +254,7 @@ fn check_into_iter_call_arg( // Calling `iter()` on a temporary object can lead to false positives. #14242 && !is_expr_temporary_value(cx, receiver) { - if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) { + if check_for_loop_iter(cx, parent, method_name, receiver, true) { return true; } @@ -755,3 +786,130 @@ fn check_borrow_predicate<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { check_if_applicable_to_argument(cx, &arg); } } + +fn check_iter_cloned(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool { + if let Some(parent) = get_parent_expr(cx, expr) + && let Some(callee_def_id) = fn_def_id(cx, parent) + && is_into_iter(cx, callee_def_id) + { + check_for_loop_iter(cx, parent, method_name, receiver, false) + } else { + false + } +} + +/// Checks whether `expr` is an iterator in a `for` loop and, if so, determines whether the +/// iterated-over items could be iterated over by reference. The reason why `check` above does not +/// include this code directly is so that it can be called from +/// `unnecessary_into_owned::check_into_iter_call_arg`. +pub fn check_for_loop_iter( + cx: &LateContext<'_>, + expr: &Expr<'_>, + method_name: Symbol, + receiver: &Expr<'_>, + cloned_before_iter: bool, +) -> bool { + if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent)) + && let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent) + && let (clone_or_copy_needed, references_to_binding) = clone_or_copy_needed(cx, pat, body) + && !clone_or_copy_needed + && let Some(receiver_snippet) = receiver.span.get_source_text(cx) + { + // Issue 12098 + // https://github.com/rust-lang/rust-clippy/issues/12098 + // if the assignee have `mut borrow` conflict with the iteratee + // the lint should not execute, former didn't consider the mut case + + // check whether `expr` is mutable + fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let Some(hir_id) = path_to_local(expr) + && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) + { + matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..)) + } else { + true + } + } + + fn is_caller_or_fields_change(cx: &LateContext<'_>, body: &Expr<'_>, caller: &Expr<'_>) -> bool { + let mut change = false; + if let ExprKind::Block(block, ..) = body.kind { + for_each_expr_without_closures(block, |e| { + match e.kind { + ExprKind::Assign(assignee, _, _) | ExprKind::AssignOp(_, assignee, _) => { + change |= !can_mut_borrow_both(cx, caller, assignee); + }, + _ => {}, + } + // the return value has no effect but the function need one return value + ControlFlow::<()>::Continue(()) + }); + } + change + } + + if let ExprKind::Call(_, [child, ..]) = expr.kind { + // filter first layer of iterator + let mut child = child; + // get inner real caller requests for clone + while let ExprKind::MethodCall(_, caller, _, _) = child.kind { + child = caller; + } + if is_mutable(cx, child) && is_caller_or_fields_change(cx, body, child) { + // skip lint + return true; + } + } + + // the lint should not be executed if no violation happens + let snippet = if let ExprKind::MethodCall(maybe_iter_method_name, collection, [], _) = receiver.kind + && maybe_iter_method_name.ident.name == sym::iter + && let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator) + && let receiver_ty = cx.typeck_results().expr_ty(receiver) + && implements_trait(cx, receiver_ty, iterator_trait_id, &[]) + && let Some(iter_item_ty) = get_iterator_item_ty(cx, receiver_ty) + && let Some(into_iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator) + && let collection_ty = cx.typeck_results().expr_ty(collection) + && implements_trait(cx, collection_ty, into_iterator_trait_id, &[]) + && let Some(into_iter_item_ty) = cx.get_associated_type(collection_ty, into_iterator_trait_id, sym::Item) + && iter_item_ty == into_iter_item_ty + && let Some(collection_snippet) = collection.span.get_source_text(cx) + { + collection_snippet + } else { + receiver_snippet + }; + span_lint_and_then( + cx, + UNNECESSARY_TO_OWNED, + expr.span, + format!("unnecessary use of `{method_name}`"), + |diag| { + // If `check_into_iter_call_arg` called `check_for_loop_iter` because a call to + // a `to_owned`-like function was removed, then the next suggestion may be + // incorrect. This is because the iterator that results from the call's removal + // could hold a reference to a resource that is used mutably. See + // https://github.com/rust-lang/rust-clippy/issues/8148. + let applicability = if cloned_before_iter { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }; + + let combined = references_to_binding + .into_iter() + .chain(vec![(expr.span, snippet.to_owned())]) + .collect::>(); + + diag.multipart_suggestion("remove any references to the binding", combined, applicability); + }, + ); + return true; + } + false +} + +/// Returns true if the named method is `IntoIterator::into_iter`. +fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool { + Some(callee_def_id) == cx.tcx.lang_items().into_iter_fn() +} diff --git a/clippy_lints_methods/src/unwrap_expect_used.rs b/clippy_lints_methods/src/unwrap_expect_used.rs new file mode 100644 index 000000000000..f6ebe98b6cb5 --- /dev/null +++ b/clippy_lints_methods/src/unwrap_expect_used.rs @@ -0,0 +1,173 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::ty::{is_never_like, is_type_diagnostic_item}; +use clippy_utils::{is_in_test, is_inside_always_const_context, is_lint_allowed}; +use rustc_hir::Expr; +use rustc_lint::{LateContext, Lint}; +use rustc_middle::ty; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `.unwrap()` or `.unwrap_err()` calls on `Result`s and `.unwrap()` call on `Option`s. + /// + /// ### Why restrict this? + /// It is better to handle the `None` or `Err` case, + /// or at least call `.expect(_)` with a more helpful message. Still, for a lot of + /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is + /// `Allow` by default. + /// + /// `result.unwrap()` will let the thread panic on `Err` values. + /// Normally, you want to implement more sophisticated error handling, + /// and propagate errors upwards with `?` operator. + /// + /// Even if you want to panic on errors, not all `Error`s implement good + /// messages on display. Therefore, it may be beneficial to look at the places + /// where they may get displayed. Activate this lint to do just that. + /// + /// ### Examples + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option.unwrap(); + /// result.unwrap(); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option.expect("more helpful message"); + /// result.expect("more helpful message"); + /// ``` + /// + /// If [expect_used](#expect_used) is enabled, instead: + /// ```rust,ignore + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option?; + /// + /// // or + /// + /// result?; + /// ``` + #[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()`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `.expect()` or `.expect_err()` calls on `Result`s and `.expect()` call on `Option`s. + /// + /// ### Why restrict this? + /// Usually it is better to handle the `None` or `Err` case. + /// Still, for a lot of quick-and-dirty code, `expect` is a good choice, which is why + /// this lint is `Allow` by default. + /// + /// `result.expect()` will let the thread panic on `Err` + /// values. Normally, you want to implement more sophisticated error handling, + /// and propagate errors upwards with `?` operator. + /// + /// ### Examples + /// ```rust,ignore + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option.expect("one"); + /// result.expect("one"); + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// # let option = Some(1); + /// # let result: Result = Ok(1); + /// option?; + /// + /// // or + /// + /// result?; + /// ``` + #[clippy::version = "1.45.0"] + pub EXPECT_USED, + restriction, + "using `.expect()` on `Result` or `Option`, which might be better handled" +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub(super) enum Variant { + Unwrap, + Expect, +} + +impl Variant { + fn method_name(self, is_err: bool) -> &'static str { + match (self, is_err) { + (Variant::Unwrap, true) => "unwrap_err", + (Variant::Unwrap, false) => "unwrap", + (Variant::Expect, true) => "expect_err", + (Variant::Expect, false) => "expect", + } + } + + fn lint(self) -> &'static Lint { + match self { + Variant::Unwrap => UNWRAP_USED, + Variant::Expect => EXPECT_USED, + } + } +} + +/// Lint usage of `unwrap` or `unwrap_err` for `Result` and `unwrap()` for `Option` (and their +/// `expect` counterparts). +pub(super) fn check( + cx: &LateContext<'_>, + expr: &Expr<'_>, + recv: &Expr<'_>, + is_err: bool, + allow_unwrap_in_consts: bool, + allow_unwrap_in_tests: bool, + variant: Variant, +) { + let ty = cx.typeck_results().expr_ty(recv).peel_refs(); + + let (kind, none_value, none_prefix) = if is_type_diagnostic_item(cx, ty, sym::Option) && !is_err { + ("an `Option`", "None", "") + } else if is_type_diagnostic_item(cx, ty, sym::Result) + && let ty::Adt(_, substs) = ty.kind() + && let Some(t_or_e_ty) = substs[usize::from(!is_err)].as_type() + { + if is_never_like(t_or_e_ty) { + return; + } + + ("a `Result`", if is_err { "Ok" } else { "Err" }, "an ") + } else { + return; + }; + + let method_suffix = if is_err { "_err" } else { "" }; + + if allow_unwrap_in_tests && is_in_test(cx.tcx, expr.hir_id) { + return; + } + + if allow_unwrap_in_consts && is_inside_always_const_context(cx.tcx, expr.hir_id) { + return; + } + + span_lint_and_then( + cx, + variant.lint(), + expr.span, + format!("used `{}()` on {kind} value", variant.method_name(is_err)), + |diag| { + diag.note(format!("if this value is {none_prefix}`{none_value}`, it will panic")); + + if variant == Variant::Unwrap && is_lint_allowed(cx, EXPECT_USED, expr.hir_id) { + diag.help(format!( + "consider using `expect{method_suffix}()` to provide a better panic message" + )); + } + }, + ); +} diff --git a/clippy_lints/src/methods/useless_asref.rs b/clippy_lints_methods/src/useless_asref.rs similarity index 91% rename from clippy_lints/src/methods/useless_asref.rs rename to clippy_lints_methods/src/useless_asref.rs index d30c12e0c483..d2803c6a4939 100644 --- a/clippy_lints/src/methods/useless_asref.rs +++ b/clippy_lints_methods/src/useless_asref.rs @@ -11,7 +11,31 @@ use rustc_span::{Span, Symbol, sym}; use core::ops::ControlFlow; -use super::USELESS_ASREF; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.as_ref()` or `.as_mut()` where the + /// types before and after the call are the same. + /// + /// ### Why is this bad? + /// The call is unnecessary. + /// + /// ### Example + /// ```no_run + /// # fn do_stuff(x: &[i32]) {} + /// let x: &[i32] = &[1, 2, 3, 4, 5]; + /// do_stuff(x.as_ref()); + /// ``` + /// The correct use would be: + /// ```no_run + /// # fn do_stuff(x: &[i32]) {} + /// 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" +} /// Returns the first type inside the `Option`/`Result` type passed as argument. fn get_enum_ty(enum_ty: Ty<'_>) -> Option> { diff --git a/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs b/clippy_lints_methods/src/useless_nonzero_new_unchecked.rs similarity index 72% rename from clippy_lints/src/methods/useless_nonzero_new_unchecked.rs rename to clippy_lints_methods/src/useless_nonzero_new_unchecked.rs index 22df1f3f485e..22e7ef4d5959 100644 --- a/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs +++ b/clippy_lints_methods/src/useless_nonzero_new_unchecked.rs @@ -8,7 +8,32 @@ use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, Node, QPath, UnsafeSource use rustc_lint::LateContext; use rustc_span::sym; -use super::USELESS_NONZERO_NEW_UNCHECKED; +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `NonZero*::new_unchecked()` being used in a `const` context. + /// + /// ### Why is this bad? + /// + /// Using `NonZero*::new_unchecked()` is an `unsafe` function and requires an `unsafe` context. When used in a + /// context evaluated at compilation time, `NonZero*::new().unwrap()` will provide the same result with identical + /// runtime performances while not requiring `unsafe`. + /// + /// ### Example + /// ```no_run + /// use std::num::NonZeroUsize; + /// const PLAYERS: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(3) }; + /// ``` + /// Use instead: + /// ```no_run + /// use std::num::NonZeroUsize; + /// const PLAYERS: NonZeroUsize = NonZeroUsize::new(3).unwrap(); + /// ``` + #[clippy::version = "1.86.0"] + pub USELESS_NONZERO_NEW_UNCHECKED, + complexity, + "using `NonZero::new_unchecked()` in a `const` context" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, func: &Expr<'tcx>, args: &[Expr<'_>], msrv: Msrv) { if let ExprKind::Path(QPath::TypeRelative(ty, segment)) = func.kind diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints_methods/src/utils.rs similarity index 100% rename from clippy_lints/src/methods/utils.rs rename to clippy_lints_methods/src/utils.rs diff --git a/clippy_lints/src/methods/vec_resize_to_zero.rs b/clippy_lints_methods/src/vec_resize_to_zero.rs similarity index 72% rename from clippy_lints/src/methods/vec_resize_to_zero.rs rename to clippy_lints_methods/src/vec_resize_to_zero.rs index 5ea4ada128a2..f9236fc4f23d 100644 --- a/clippy_lints/src/methods/vec_resize_to_zero.rs +++ b/clippy_lints_methods/src/vec_resize_to_zero.rs @@ -8,7 +8,27 @@ use rustc_lint::LateContext; use rustc_span::source_map::Spanned; use rustc_span::{Span, sym}; -use super::VEC_RESIZE_TO_ZERO; +declare_clippy_lint! { + /// ### What it does + /// Finds occurrences of `Vec::resize(0, an_int)` + /// + /// ### Why is this bad? + /// This is probably an argument inversion mistake. + /// + /// ### Example + /// ```no_run + /// vec![1, 2, 3, 4, 5].resize(0, 5) + /// ``` + /// + /// Use instead: + /// ```no_run + /// vec![1, 2, 3, 4, 5].clear() + /// ``` + #[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" +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/methods/verbose_file_reads.rs b/clippy_lints_methods/src/verbose_file_reads.rs similarity index 51% rename from clippy_lints/src/methods/verbose_file_reads.rs rename to clippy_lints_methods/src/verbose_file_reads.rs index 8ed61637eca2..38a675e3e0c3 100644 --- a/clippy_lints/src/methods/verbose_file_reads.rs +++ b/clippy_lints_methods/src/verbose_file_reads.rs @@ -5,7 +5,32 @@ use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_span::sym; -use super::VERBOSE_FILE_READS; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of File::read_to_end and File::read_to_string. + /// + /// ### Why restrict this? + /// `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. + /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) + /// + /// ### Example + /// ```rust,no_run + /// # use std::io::Read; + /// # use std::fs::File; + /// let mut f = File::open("foo.txt").unwrap(); + /// let mut bytes = Vec::new(); + /// f.read_to_end(&mut bytes).unwrap(); + /// ``` + /// Can be written more concisely as + /// ```rust,no_run + /// # 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`" +} pub(super) const READ_TO_END_MSG: (&str, &str) = ("use of `File::read_to_end`", "consider using `fs::read` instead"); pub(super) const READ_TO_STRING_MSG: (&str, &str) = ( diff --git a/clippy_lints/src/methods/waker_clone_wake.rs b/clippy_lints_methods/src/waker_clone_wake.rs similarity index 65% rename from clippy_lints/src/methods/waker_clone_wake.rs rename to clippy_lints_methods/src/waker_clone_wake.rs index b5f34a9be2e2..b7b24b5e07e5 100644 --- a/clippy_lints/src/methods/waker_clone_wake.rs +++ b/clippy_lints_methods/src/waker_clone_wake.rs @@ -6,7 +6,27 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_span::sym; -use super::WAKER_CLONE_WAKE; +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `waker.clone().wake()` + /// + /// ### Why is this bad? + /// Cloning the waker is not necessary, `wake_by_ref()` enables the same operation + /// without extra cloning/dropping. + /// + /// ### Example + /// ```rust,ignore + /// waker.clone().wake(); + /// ``` + /// Should be written + /// ```rust,ignore + /// waker.wake_by_ref(); + /// ``` + #[clippy::version = "1.75.0"] + pub WAKER_CLONE_WAKE, + perf, + "cloning a `Waker` only to wake it" +} pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { let ty = cx.typeck_results().expr_ty(recv); diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints_methods/src/wrong_self_convention.rs similarity index 71% rename from clippy_lints/src/methods/wrong_self_convention.rs rename to clippy_lints_methods/src/wrong_self_convention.rs index ad9b3c364542..5cc73a2d1c91 100644 --- a/clippy_lints/src/methods/wrong_self_convention.rs +++ b/clippy_lints_methods/src/wrong_self_convention.rs @@ -1,4 +1,4 @@ -use crate::methods::SelfKind; +use crate::SelfKind; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::is_copy; use rustc_lint::LateContext; @@ -6,7 +6,65 @@ use rustc_middle::ty::Ty; use rustc_span::{Span, Symbol}; use std::fmt; -use super::WRONG_SELF_CONVENTION; +declare_clippy_lint! { + /// ### What it does + /// Checks for methods with certain name prefixes or suffixes, and which + /// do not adhere to standard conventions regarding how `self` is taken. + /// The actual rules are: + /// + /// |Prefix |Postfix |`self` taken | `self` type | + /// |-------|------------|-------------------------------|--------------| + /// |`as_` | none |`&self` or `&mut self` | any | + /// |`from_`| none | none | any | + /// |`into_`| none |`self` | any | + /// |`is_` | none |`&mut self` or `&self` or none | any | + /// |`to_` | `_mut` |`&mut self` | any | + /// |`to_` | not `_mut` |`self` | `Copy` | + /// |`to_` | not `_mut` |`&self` | not `Copy` | + /// + /// Note: Clippy doesn't trigger methods with `to_` prefix in: + /// - Traits definition. + /// Clippy can not tell if a type that implements a trait is `Copy` or not. + /// - Traits implementation, when `&self` is taken. + /// The method signature is controlled by the trait and often `&self` is required for all types that implement the trait + /// (see e.g. the `std::string::ToString` trait). + /// + /// Clippy allows `Pin<&Self>` and `Pin<&mut Self>` if `&self` and `&mut self` is required. + /// + /// Please find more info here: + /// https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv + /// + /// ### Why is this bad? + /// Consistency breeds readability. If you follow the + /// conventions, your users won't be surprised that they, e.g., need to supply a + /// mutable reference to a `as_..` function. + /// + /// ### Example + /// ```no_run + /// # struct X; + /// impl X { + /// fn as_str(self) -> &'static str { + /// // .. + /// # "" + /// } + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// # struct X; + /// impl X { + /// fn as_str(&self) -> &'static str { + /// // .. + /// # "" + /// } + /// } + /// ``` + #[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" +} #[rustfmt::skip] const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [ diff --git a/clippy_lints_methods/src/zst_offset.rs b/clippy_lints_methods/src/zst_offset.rs new file mode 100644 index 000000000000..07b6fc9c01b5 --- /dev/null +++ b/clippy_lints_methods/src/zst_offset.rs @@ -0,0 +1,31 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `offset(_)`, `wrapping_`{`add`, `sub`}, etc. on raw pointers to + /// zero-sized types + /// + /// ### Why is this bad? + /// This is a no-op, and likely unintended + /// + /// ### Example + /// ```no_run + /// 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" +} + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { + if let ty::RawPtr(ty, _) = cx.typeck_results().expr_ty(recv).kind() + && let Ok(layout) = cx.tcx.layout_of(cx.typing_env().as_query_input(*ty)) + && layout.is_zst() + { + span_lint(cx, ZST_OFFSET, expr.span, "offset calculation on zero-sized value"); + } +} diff --git a/src/driver.rs b/src/driver.rs index c4076cbaa77b..37475139d282 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -159,10 +159,12 @@ impl rustc_driver::Callbacks for ClippyCallbacks { let mut list_builder = LintListBuilder::default(); list_builder.insert(clippy_lints::declared_lints::LINTS); + list_builder.insert(clippy_lints_methods::declared_lints::LINTS); list_builder.register(lint_store); let conf = clippy_config::Conf::read(sess, &conf_path); - clippy_lints::register_lint_passes(lint_store, conf); + let format_args = clippy_lints::register_lint_passes(lint_store, conf); + clippy_lints_methods::register_lint_passes(lint_store, conf, format_args); #[cfg(feature = "internal")] clippy_lints_internal::register_lints(lint_store); diff --git a/tests/dogfood.rs b/tests/dogfood.rs index 389616801fca..071d187b8de9 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -38,6 +38,7 @@ fn dogfood() { "clippy_dev", "clippy_lints_internal", "clippy_lints", + "clippy_lints_methods", "clippy_utils", "clippy_config", "declare_clippy_lint", diff --git a/tests/ui-internal/check_formulation.rs b/tests/ui-internal/check_formulation.rs index bcbb0d783198..092404cfcda8 100644 --- a/tests/ui-internal/check_formulation.rs +++ b/tests/ui-internal/check_formulation.rs @@ -1,5 +1,4 @@ #![deny(clippy::almost_standard_lint_formulation)] -#![allow(clippy::lint_without_lint_pass)] #![feature(rustc_private)] #[macro_use] diff --git a/tests/ui-internal/check_formulation.stderr b/tests/ui-internal/check_formulation.stderr index 9aeb9e1f2d49..f498f04c5fe0 100644 --- a/tests/ui-internal/check_formulation.stderr +++ b/tests/ui-internal/check_formulation.stderr @@ -1,5 +1,5 @@ error: non-standard lint formulation - --> tests/ui-internal/check_formulation.rs:24:5 + --> tests/ui-internal/check_formulation.rs:23:5 | LL | /// Check for lint formulations that are correct | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -12,7 +12,7 @@ LL | #![deny(clippy::almost_standard_lint_formulation)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: non-standard lint formulation - --> tests/ui-internal/check_formulation.rs:35:5 + --> tests/ui-internal/check_formulation.rs:34:5 | LL | /// Detects uses of incorrect formulations | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui-internal/lint_without_lint_pass.rs b/tests/ui-internal/lint_without_lint_pass.rs deleted file mode 100644 index 6b649132aca3..000000000000 --- a/tests/ui-internal/lint_without_lint_pass.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![deny(clippy::lint_without_lint_pass)] -#![allow(clippy::missing_clippy_version_attribute)] -#![feature(rustc_private)] - -#[macro_use] -extern crate rustc_middle; -#[macro_use] -extern crate rustc_session; -extern crate rustc_lint; -use rustc_lint::{LintPass, LintVec}; - -declare_tool_lint! { -//~^ lint_without_lint_pass - pub clippy::TEST_LINT, - Warn, - "", - report_in_external_macro: true -} - -declare_tool_lint! { - pub clippy::TEST_LINT_REGISTERED, - Warn, - "", - report_in_external_macro: true -} - -declare_tool_lint! { - pub clippy::TEST_LINT_REGISTERED_ONLY_IMPL, - Warn, - "", - report_in_external_macro: true -} - -pub struct Pass; -impl LintPass for Pass { - fn name(&self) -> &'static str { - "TEST_LINT" - } - fn get_lints(&self) -> LintVec { - vec![TEST_LINT] - } -} - -declare_lint_pass!(Pass2 => [TEST_LINT_REGISTERED]); - -pub struct Pass3; -impl_lint_pass!(Pass3 => [TEST_LINT_REGISTERED_ONLY_IMPL]); - -fn main() {} diff --git a/tests/ui-internal/lint_without_lint_pass.stderr b/tests/ui-internal/lint_without_lint_pass.stderr deleted file mode 100644 index 3798293f4c11..000000000000 --- a/tests/ui-internal/lint_without_lint_pass.stderr +++ /dev/null @@ -1,21 +0,0 @@ -error: the lint `TEST_LINT` is not added to any `LintPass` - --> tests/ui-internal/lint_without_lint_pass.rs:12:1 - | -LL | / declare_tool_lint! { -LL | | -LL | | pub clippy::TEST_LINT, -LL | | Warn, -LL | | "", -LL | | report_in_external_macro: true -LL | | } - | |_^ - | -note: the lint level is defined here - --> tests/ui-internal/lint_without_lint_pass.rs:1:9 - | -LL | #![deny(clippy::lint_without_lint_pass)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: this error originates in the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: aborting due to 1 previous error - diff --git a/tests/ui/iter_overeager_cloned.fixed b/tests/ui/iter_overeager_cloned.fixed index b0e548f17909..4171f19469a4 100644 --- a/tests/ui/iter_overeager_cloned.fixed +++ b/tests/ui/iter_overeager_cloned.fixed @@ -1,4 +1,4 @@ -#![warn(clippy::iter_overeager_cloned, clippy::redundant_clone, clippy::filter_next)] +#![warn(clippy::iter_overeager_cloned, clippy::redundant_iter_cloned, clippy::filter_next)] #![allow( dead_code, clippy::let_unit_value, @@ -16,7 +16,7 @@ fn main() { //~^ iter_overeager_cloned let _: usize = vec.iter().filter(|x| x == &"2").count(); - //~^ redundant_clone + //~^ redundant_iter_cloned let _: Vec<_> = vec.iter().take(2).cloned().collect(); //~^ iter_overeager_cloned @@ -77,19 +77,19 @@ fn main() { } let _ = vec.iter().map(|x| x.len()); - //~^ redundant_clone + //~^ redundant_iter_cloned // This would fail if changed. let _ = vec.iter().cloned().map(|x| x + "2"); let _ = vec.iter().for_each(|x| assert!(!x.is_empty())); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().all(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().any(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned // Should probably stay as it is. let _ = [0, 1, 2, 3, 4].iter().cloned().take(10); diff --git a/tests/ui/iter_overeager_cloned.rs b/tests/ui/iter_overeager_cloned.rs index cedf62a6b473..fe6aba24dd3e 100644 --- a/tests/ui/iter_overeager_cloned.rs +++ b/tests/ui/iter_overeager_cloned.rs @@ -1,4 +1,4 @@ -#![warn(clippy::iter_overeager_cloned, clippy::redundant_clone, clippy::filter_next)] +#![warn(clippy::iter_overeager_cloned, clippy::redundant_iter_cloned, clippy::filter_next)] #![allow( dead_code, clippy::let_unit_value, @@ -16,7 +16,7 @@ fn main() { //~^ iter_overeager_cloned let _: usize = vec.iter().filter(|x| x == &"2").cloned().count(); - //~^ redundant_clone + //~^ redundant_iter_cloned let _: Vec<_> = vec.iter().cloned().take(2).collect(); //~^ iter_overeager_cloned @@ -78,19 +78,19 @@ fn main() { } let _ = vec.iter().cloned().map(|x| x.len()); - //~^ redundant_clone + //~^ redundant_iter_cloned // This would fail if changed. let _ = vec.iter().cloned().map(|x| x + "2"); let _ = vec.iter().cloned().for_each(|x| assert!(!x.is_empty())); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().cloned().all(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned let _ = vec.iter().cloned().any(|x| x.len() == 1); - //~^ redundant_clone + //~^ redundant_iter_cloned // Should probably stay as it is. let _ = [0, 1, 2, 3, 4].iter().cloned().take(10); diff --git a/tests/ui/iter_overeager_cloned.stderr b/tests/ui/iter_overeager_cloned.stderr index 1616dec95b79..f234d19e4aaa 100644 --- a/tests/ui/iter_overeager_cloned.stderr +++ b/tests/ui/iter_overeager_cloned.stderr @@ -25,8 +25,8 @@ LL | let _: usize = vec.iter().filter(|x| x == &"2").cloned().count(); | | | help: try: `.count()` | - = note: `-D clippy::redundant-clone` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::redundant_clone)]` + = note: `-D clippy::redundant-iter-cloned` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::redundant_iter_cloned)]` error: unnecessarily eager cloning of iterator items --> tests/ui/iter_overeager_cloned.rs:21:21 diff --git a/tests/ui/rename.fixed b/tests/ui/rename.fixed index ff81c6426027..07f999e74c19 100644 --- a/tests/ui/rename.fixed +++ b/tests/ui/rename.fixed @@ -3,67 +3,6 @@ // Manual edits will be overwritten. #![allow(clippy::duplicated_attributes)] -#![allow(clippy::almost_complete_range)] -#![allow(clippy::disallowed_names)] -#![allow(clippy::blocks_in_conditions)] -#![allow(clippy::box_collection)] -#![allow(invalid_reference_casting)] -#![allow(suspicious_double_ref_op)] -#![allow(invalid_nan_comparisons)] -#![allow(clippy::redundant_static_lifetimes)] -#![allow(clippy::cognitive_complexity)] -#![allow(clippy::derived_hash_with_manual_eq)] -#![allow(clippy::disallowed_methods)] -#![allow(clippy::disallowed_types)] -#![allow(double_negations)] -#![allow(drop_bounds)] -#![allow(dropping_copy_types)] -#![allow(dropping_references)] -#![allow(clippy::mixed_read_write_in_expression)] -#![allow(clippy::manual_filter_map)] -#![allow(clippy::manual_find_map)] -#![allow(unpredictable_function_pointer_comparisons)] -#![allow(useless_ptr_null_checks)] -#![allow(for_loops_over_fallibles)] -#![allow(forgetting_copy_types)] -#![allow(forgetting_references)] -#![allow(clippy::useless_conversion)] -#![allow(clippy::redundant_pattern_matching)] -#![allow(clippy::match_result_ok)] -#![allow(clippy::non_canonical_clone_impl)] -#![allow(clippy::non_canonical_partial_ord_impl)] -#![allow(clippy::arithmetic_side_effects)] -#![allow(array_into_iter)] -#![allow(invalid_atomic_ordering)] -#![allow(invalid_null_arguments)] -#![allow(invalid_value)] -#![allow(invalid_from_utf8_unchecked)] -#![allow(let_underscore_drop)] -#![allow(clippy::overly_complex_bool_expr)] -#![allow(unexpected_cfgs)] -#![allow(enum_intrinsics_non_enums)] -#![allow(clippy::new_without_default)] -#![allow(clippy::bind_instead_of_map)] -#![allow(clippy::expect_used)] -#![allow(clippy::map_unwrap_or)] -#![allow(clippy::unwrap_used)] -#![allow(clippy::panicking_overflow_checks)] -#![allow(non_fmt_panics)] -#![allow(named_arguments_used_positionally)] -#![allow(clippy::needless_borrow)] -#![allow(clippy::reversed_empty_ranges)] -#![allow(clippy::single_char_add_str)] -#![allow(clippy::module_name_repetitions)] -#![allow(dangling_pointers_from_temporaries)] -#![allow(clippy::missing_const_for_thread_local)] -#![allow(clippy::recursive_format_impl)] -#![allow(unnecessary_transmutes)] -#![allow(undropped_manually_drops)] -#![allow(unknown_lints)] -#![allow(unused_labels)] -#![allow(clippy::unwrap_or_default)] -#![allow(ambiguous_wide_pointer_comparisons)] -#![allow(clippy::invisible_characters)] #![warn(clippy::almost_complete_range)] //~ ERROR: lint `clippy::almost_complete_letter_range` #![warn(clippy::disallowed_names)] //~ ERROR: lint `clippy::blacklisted_name` #![warn(clippy::blocks_in_conditions)] //~ ERROR: lint `clippy::block_in_if_condition_expr` diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index b5d5d07e639a..721bf4734e54 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -3,67 +3,6 @@ // Manual edits will be overwritten. #![allow(clippy::duplicated_attributes)] -#![allow(clippy::almost_complete_range)] -#![allow(clippy::disallowed_names)] -#![allow(clippy::blocks_in_conditions)] -#![allow(clippy::box_collection)] -#![allow(invalid_reference_casting)] -#![allow(suspicious_double_ref_op)] -#![allow(invalid_nan_comparisons)] -#![allow(clippy::redundant_static_lifetimes)] -#![allow(clippy::cognitive_complexity)] -#![allow(clippy::derived_hash_with_manual_eq)] -#![allow(clippy::disallowed_methods)] -#![allow(clippy::disallowed_types)] -#![allow(double_negations)] -#![allow(drop_bounds)] -#![allow(dropping_copy_types)] -#![allow(dropping_references)] -#![allow(clippy::mixed_read_write_in_expression)] -#![allow(clippy::manual_filter_map)] -#![allow(clippy::manual_find_map)] -#![allow(unpredictable_function_pointer_comparisons)] -#![allow(useless_ptr_null_checks)] -#![allow(for_loops_over_fallibles)] -#![allow(forgetting_copy_types)] -#![allow(forgetting_references)] -#![allow(clippy::useless_conversion)] -#![allow(clippy::redundant_pattern_matching)] -#![allow(clippy::match_result_ok)] -#![allow(clippy::non_canonical_clone_impl)] -#![allow(clippy::non_canonical_partial_ord_impl)] -#![allow(clippy::arithmetic_side_effects)] -#![allow(array_into_iter)] -#![allow(invalid_atomic_ordering)] -#![allow(invalid_null_arguments)] -#![allow(invalid_value)] -#![allow(invalid_from_utf8_unchecked)] -#![allow(let_underscore_drop)] -#![allow(clippy::overly_complex_bool_expr)] -#![allow(unexpected_cfgs)] -#![allow(enum_intrinsics_non_enums)] -#![allow(clippy::new_without_default)] -#![allow(clippy::bind_instead_of_map)] -#![allow(clippy::expect_used)] -#![allow(clippy::map_unwrap_or)] -#![allow(clippy::unwrap_used)] -#![allow(clippy::panicking_overflow_checks)] -#![allow(non_fmt_panics)] -#![allow(named_arguments_used_positionally)] -#![allow(clippy::needless_borrow)] -#![allow(clippy::reversed_empty_ranges)] -#![allow(clippy::single_char_add_str)] -#![allow(clippy::module_name_repetitions)] -#![allow(dangling_pointers_from_temporaries)] -#![allow(clippy::missing_const_for_thread_local)] -#![allow(clippy::recursive_format_impl)] -#![allow(unnecessary_transmutes)] -#![allow(undropped_manually_drops)] -#![allow(unknown_lints)] -#![allow(unused_labels)] -#![allow(clippy::unwrap_or_default)] -#![allow(ambiguous_wide_pointer_comparisons)] -#![allow(clippy::invisible_characters)] #![warn(clippy::almost_complete_letter_range)] //~ ERROR: lint `clippy::almost_complete_letter_range` #![warn(clippy::blacklisted_name)] //~ ERROR: lint `clippy::blacklisted_name` #![warn(clippy::block_in_if_condition_expr)] //~ ERROR: lint `clippy::block_in_if_condition_expr` diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index 2487dfc8eba4..579a76db2cf6 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -1,5 +1,5 @@ error: lint `clippy::almost_complete_letter_range` has been renamed to `clippy::almost_complete_range` - --> tests/ui/rename.rs:67:9 + --> tests/ui/rename.rs:6:9 | LL | #![warn(clippy::almost_complete_letter_range)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::almost_complete_range` @@ -8,433 +8,433 @@ LL | #![warn(clippy::almost_complete_letter_range)] = help: to override `-D warnings` add `#[allow(renamed_and_removed_lints)]` error: lint `clippy::blacklisted_name` has been renamed to `clippy::disallowed_names` - --> tests/ui/rename.rs:68:9 + --> tests/ui/rename.rs:7:9 | LL | #![warn(clippy::blacklisted_name)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_names` error: lint `clippy::block_in_if_condition_expr` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:69:9 + --> tests/ui/rename.rs:8:9 | LL | #![warn(clippy::block_in_if_condition_expr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::block_in_if_condition_stmt` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:70:9 + --> tests/ui/rename.rs:9:9 | LL | #![warn(clippy::block_in_if_condition_stmt)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::blocks_in_if_conditions` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:71:9 + --> tests/ui/rename.rs:10:9 | LL | #![warn(clippy::blocks_in_if_conditions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::box_vec` has been renamed to `clippy::box_collection` - --> tests/ui/rename.rs:72:9 + --> tests/ui/rename.rs:11:9 | LL | #![warn(clippy::box_vec)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::box_collection` error: lint `clippy::cast_ref_to_mut` has been renamed to `invalid_reference_casting` - --> tests/ui/rename.rs:73:9 + --> tests/ui/rename.rs:12:9 | LL | #![warn(clippy::cast_ref_to_mut)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_reference_casting` error: lint `clippy::clone_double_ref` has been renamed to `suspicious_double_ref_op` - --> tests/ui/rename.rs:74:9 + --> tests/ui/rename.rs:13:9 | LL | #![warn(clippy::clone_double_ref)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `suspicious_double_ref_op` error: lint `clippy::cmp_nan` has been renamed to `invalid_nan_comparisons` - --> tests/ui/rename.rs:75:9 + --> tests/ui/rename.rs:14:9 | LL | #![warn(clippy::cmp_nan)] | ^^^^^^^^^^^^^^^ help: use the new name: `invalid_nan_comparisons` error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes` - --> tests/ui/rename.rs:76:9 + --> tests/ui/rename.rs:15:9 | LL | #![warn(clippy::const_static_lifetime)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes` error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` - --> tests/ui/rename.rs:77:9 + --> tests/ui/rename.rs:16:9 | LL | #![warn(clippy::cyclomatic_complexity)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` error: lint `clippy::derive_hash_xor_eq` has been renamed to `clippy::derived_hash_with_manual_eq` - --> tests/ui/rename.rs:78:9 + --> tests/ui/rename.rs:17:9 | LL | #![warn(clippy::derive_hash_xor_eq)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::derived_hash_with_manual_eq` error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_methods` - --> tests/ui/rename.rs:79:9 + --> tests/ui/rename.rs:18:9 | LL | #![warn(clippy::disallowed_method)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_methods` error: lint `clippy::disallowed_type` has been renamed to `clippy::disallowed_types` - --> tests/ui/rename.rs:80:9 + --> tests/ui/rename.rs:19:9 | LL | #![warn(clippy::disallowed_type)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_types` error: lint `clippy::double_neg` has been renamed to `double_negations` - --> tests/ui/rename.rs:81:9 + --> tests/ui/rename.rs:20:9 | LL | #![warn(clippy::double_neg)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `double_negations` error: lint `clippy::drop_bounds` has been renamed to `drop_bounds` - --> tests/ui/rename.rs:82:9 + --> tests/ui/rename.rs:21:9 | LL | #![warn(clippy::drop_bounds)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `drop_bounds` error: lint `clippy::drop_copy` has been renamed to `dropping_copy_types` - --> tests/ui/rename.rs:83:9 + --> tests/ui/rename.rs:22:9 | LL | #![warn(clippy::drop_copy)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `dropping_copy_types` error: lint `clippy::drop_ref` has been renamed to `dropping_references` - --> tests/ui/rename.rs:84:9 + --> tests/ui/rename.rs:23:9 | LL | #![warn(clippy::drop_ref)] | ^^^^^^^^^^^^^^^^ help: use the new name: `dropping_references` error: lint `clippy::eval_order_dependence` has been renamed to `clippy::mixed_read_write_in_expression` - --> tests/ui/rename.rs:85:9 + --> tests/ui/rename.rs:24:9 | LL | #![warn(clippy::eval_order_dependence)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::mixed_read_write_in_expression` error: lint `clippy::filter_map` has been renamed to `clippy::manual_filter_map` - --> tests/ui/rename.rs:86:9 + --> tests/ui/rename.rs:25:9 | LL | #![warn(clippy::filter_map)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::manual_filter_map` error: lint `clippy::find_map` has been renamed to `clippy::manual_find_map` - --> tests/ui/rename.rs:87:9 + --> tests/ui/rename.rs:26:9 | LL | #![warn(clippy::find_map)] | ^^^^^^^^^^^^^^^^ help: use the new name: `clippy::manual_find_map` error: lint `clippy::fn_address_comparisons` has been renamed to `unpredictable_function_pointer_comparisons` - --> tests/ui/rename.rs:88:9 + --> tests/ui/rename.rs:27:9 | LL | #![warn(clippy::fn_address_comparisons)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unpredictable_function_pointer_comparisons` error: lint `clippy::fn_null_check` has been renamed to `useless_ptr_null_checks` - --> tests/ui/rename.rs:89:9 + --> tests/ui/rename.rs:28:9 | LL | #![warn(clippy::fn_null_check)] | ^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `useless_ptr_null_checks` error: lint `clippy::for_loop_over_option` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:90:9 + --> tests/ui/rename.rs:29:9 | LL | #![warn(clippy::for_loop_over_option)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loop_over_result` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:91:9 + --> tests/ui/rename.rs:30:9 | LL | #![warn(clippy::for_loop_over_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loops_over_fallibles` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:92:9 + --> tests/ui/rename.rs:31:9 | LL | #![warn(clippy::for_loops_over_fallibles)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::forget_copy` has been renamed to `forgetting_copy_types` - --> tests/ui/rename.rs:93:9 + --> tests/ui/rename.rs:32:9 | LL | #![warn(clippy::forget_copy)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_copy_types` error: lint `clippy::forget_ref` has been renamed to `forgetting_references` - --> tests/ui/rename.rs:94:9 + --> tests/ui/rename.rs:33:9 | LL | #![warn(clippy::forget_ref)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_references` error: lint `clippy::identity_conversion` has been renamed to `clippy::useless_conversion` - --> tests/ui/rename.rs:95:9 + --> tests/ui/rename.rs:34:9 | LL | #![warn(clippy::identity_conversion)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::useless_conversion` error: lint `clippy::if_let_redundant_pattern_matching` has been renamed to `clippy::redundant_pattern_matching` - --> tests/ui/rename.rs:96:9 + --> tests/ui/rename.rs:35:9 | LL | #![warn(clippy::if_let_redundant_pattern_matching)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_pattern_matching` error: lint `clippy::if_let_some_result` has been renamed to `clippy::match_result_ok` - --> tests/ui/rename.rs:97:9 + --> tests/ui/rename.rs:36:9 | LL | #![warn(clippy::if_let_some_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::match_result_ok` error: lint `clippy::incorrect_clone_impl_on_copy_type` has been renamed to `clippy::non_canonical_clone_impl` - --> tests/ui/rename.rs:98:9 + --> tests/ui/rename.rs:37:9 | LL | #![warn(clippy::incorrect_clone_impl_on_copy_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_clone_impl` error: lint `clippy::incorrect_partial_ord_impl_on_ord_type` has been renamed to `clippy::non_canonical_partial_ord_impl` - --> tests/ui/rename.rs:99:9 + --> tests/ui/rename.rs:38:9 | LL | #![warn(clippy::incorrect_partial_ord_impl_on_ord_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_partial_ord_impl` error: lint `clippy::integer_arithmetic` has been renamed to `clippy::arithmetic_side_effects` - --> tests/ui/rename.rs:100:9 + --> tests/ui/rename.rs:39:9 | LL | #![warn(clippy::integer_arithmetic)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::arithmetic_side_effects` error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter` - --> tests/ui/rename.rs:101:9 + --> tests/ui/rename.rs:40:9 | LL | #![warn(clippy::into_iter_on_array)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `array_into_iter` error: lint `clippy::invalid_atomic_ordering` has been renamed to `invalid_atomic_ordering` - --> tests/ui/rename.rs:102:9 + --> tests/ui/rename.rs:41:9 | LL | #![warn(clippy::invalid_atomic_ordering)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_atomic_ordering` error: lint `clippy::invalid_null_ptr_usage` has been renamed to `invalid_null_arguments` - --> tests/ui/rename.rs:103:9 + --> tests/ui/rename.rs:42:9 | LL | #![warn(clippy::invalid_null_ptr_usage)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_null_arguments` error: lint `clippy::invalid_ref` has been renamed to `invalid_value` - --> tests/ui/rename.rs:104:9 + --> tests/ui/rename.rs:43:9 | LL | #![warn(clippy::invalid_ref)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_value` error: lint `clippy::invalid_utf8_in_unchecked` has been renamed to `invalid_from_utf8_unchecked` - --> tests/ui/rename.rs:105:9 + --> tests/ui/rename.rs:44:9 | LL | #![warn(clippy::invalid_utf8_in_unchecked)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_from_utf8_unchecked` error: lint `clippy::let_underscore_drop` has been renamed to `let_underscore_drop` - --> tests/ui/rename.rs:106:9 + --> tests/ui/rename.rs:45:9 | LL | #![warn(clippy::let_underscore_drop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `let_underscore_drop` error: lint `clippy::logic_bug` has been renamed to `clippy::overly_complex_bool_expr` - --> tests/ui/rename.rs:107:9 + --> tests/ui/rename.rs:46:9 | LL | #![warn(clippy::logic_bug)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::overly_complex_bool_expr` error: lint `clippy::maybe_misused_cfg` has been renamed to `unexpected_cfgs` - --> tests/ui/rename.rs:108:9 + --> tests/ui/rename.rs:47:9 | LL | #![warn(clippy::maybe_misused_cfg)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unexpected_cfgs` error: lint `clippy::mem_discriminant_non_enum` has been renamed to `enum_intrinsics_non_enums` - --> tests/ui/rename.rs:109:9 + --> tests/ui/rename.rs:48:9 | LL | #![warn(clippy::mem_discriminant_non_enum)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums` error: lint `clippy::mismatched_target_os` has been renamed to `unexpected_cfgs` - --> tests/ui/rename.rs:110:9 + --> tests/ui/rename.rs:49:9 | LL | #![warn(clippy::mismatched_target_os)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unexpected_cfgs` error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default` - --> tests/ui/rename.rs:111:9 + --> tests/ui/rename.rs:50:9 | LL | #![warn(clippy::new_without_default_derive)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default` error: lint `clippy::option_and_then_some` has been renamed to `clippy::bind_instead_of_map` - --> tests/ui/rename.rs:112:9 + --> tests/ui/rename.rs:51:9 | LL | #![warn(clippy::option_and_then_some)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::bind_instead_of_map` error: lint `clippy::option_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:113:9 + --> tests/ui/rename.rs:52:9 | LL | #![warn(clippy::option_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::option_map_unwrap_or` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:114:9 + --> tests/ui/rename.rs:53:9 | LL | #![warn(clippy::option_map_unwrap_or)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:115:9 + --> tests/ui/rename.rs:54:9 | LL | #![warn(clippy::option_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:116:9 + --> tests/ui/rename.rs:55:9 | LL | #![warn(clippy::option_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::overflow_check_conditional` has been renamed to `clippy::panicking_overflow_checks` - --> tests/ui/rename.rs:117:9 + --> tests/ui/rename.rs:56:9 | LL | #![warn(clippy::overflow_check_conditional)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::panicking_overflow_checks` error: lint `clippy::panic_params` has been renamed to `non_fmt_panics` - --> tests/ui/rename.rs:118:9 + --> tests/ui/rename.rs:57:9 | LL | #![warn(clippy::panic_params)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `non_fmt_panics` error: lint `clippy::positional_named_format_parameters` has been renamed to `named_arguments_used_positionally` - --> tests/ui/rename.rs:119:9 + --> tests/ui/rename.rs:58:9 | LL | #![warn(clippy::positional_named_format_parameters)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `named_arguments_used_positionally` error: lint `clippy::ref_in_deref` has been renamed to `clippy::needless_borrow` - --> tests/ui/rename.rs:120:9 + --> tests/ui/rename.rs:59:9 | LL | #![warn(clippy::ref_in_deref)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_borrow` error: lint `clippy::result_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:121:9 + --> tests/ui/rename.rs:60:9 | LL | #![warn(clippy::result_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::result_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:122:9 + --> tests/ui/rename.rs:61:9 | LL | #![warn(clippy::result_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::result_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:123:9 + --> tests/ui/rename.rs:62:9 | LL | #![warn(clippy::result_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::reverse_range_loop` has been renamed to `clippy::reversed_empty_ranges` - --> tests/ui/rename.rs:124:9 + --> tests/ui/rename.rs:63:9 | LL | #![warn(clippy::reverse_range_loop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::reversed_empty_ranges` error: lint `clippy::single_char_push_str` has been renamed to `clippy::single_char_add_str` - --> tests/ui/rename.rs:125:9 + --> tests/ui/rename.rs:64:9 | LL | #![warn(clippy::single_char_push_str)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::single_char_add_str` error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions` - --> tests/ui/rename.rs:126:9 + --> tests/ui/rename.rs:65:9 | LL | #![warn(clippy::stutter)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions` error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `dangling_pointers_from_temporaries` - --> tests/ui/rename.rs:127:9 + --> tests/ui/rename.rs:66:9 | LL | #![warn(clippy::temporary_cstring_as_ptr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `dangling_pointers_from_temporaries` error: lint `clippy::thread_local_initializer_can_be_made_const` has been renamed to `clippy::missing_const_for_thread_local` - --> tests/ui/rename.rs:128:9 + --> tests/ui/rename.rs:67:9 | LL | #![warn(clippy::thread_local_initializer_can_be_made_const)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::missing_const_for_thread_local` error: lint `clippy::to_string_in_display` has been renamed to `clippy::recursive_format_impl` - --> tests/ui/rename.rs:129:9 + --> tests/ui/rename.rs:68:9 | LL | #![warn(clippy::to_string_in_display)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::recursive_format_impl` error: lint `clippy::transmute_float_to_int` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:130:9 + --> tests/ui/rename.rs:69:9 | LL | #![warn(clippy::transmute_float_to_int)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_int_to_char` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:131:9 + --> tests/ui/rename.rs:70:9 | LL | #![warn(clippy::transmute_int_to_char)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_int_to_float` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:132:9 + --> tests/ui/rename.rs:71:9 | LL | #![warn(clippy::transmute_int_to_float)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::transmute_num_to_bytes` has been renamed to `unnecessary_transmutes` - --> tests/ui/rename.rs:133:9 + --> tests/ui/rename.rs:72:9 | LL | #![warn(clippy::transmute_num_to_bytes)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unnecessary_transmutes` error: lint `clippy::undropped_manually_drops` has been renamed to `undropped_manually_drops` - --> tests/ui/rename.rs:134:9 + --> tests/ui/rename.rs:73:9 | LL | #![warn(clippy::undropped_manually_drops)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `undropped_manually_drops` error: lint `clippy::unknown_clippy_lints` has been renamed to `unknown_lints` - --> tests/ui/rename.rs:135:9 + --> tests/ui/rename.rs:74:9 | LL | #![warn(clippy::unknown_clippy_lints)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unknown_lints` error: lint `clippy::unused_label` has been renamed to `unused_labels` - --> tests/ui/rename.rs:136:9 + --> tests/ui/rename.rs:75:9 | LL | #![warn(clippy::unused_label)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unused_labels` error: lint `clippy::unwrap_or_else_default` has been renamed to `clippy::unwrap_or_default` - --> tests/ui/rename.rs:137:9 + --> tests/ui/rename.rs:76:9 | LL | #![warn(clippy::unwrap_or_else_default)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_or_default` error: lint `clippy::vtable_address_comparisons` has been renamed to `ambiguous_wide_pointer_comparisons` - --> tests/ui/rename.rs:138:9 + --> tests/ui/rename.rs:77:9 | LL | #![warn(clippy::vtable_address_comparisons)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `ambiguous_wide_pointer_comparisons` error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_characters` - --> tests/ui/rename.rs:139:9 + --> tests/ui/rename.rs:78:9 | LL | #![warn(clippy::zero_width_space)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters` diff --git a/tests/ui/unused_enumerate_index.stderr b/tests/ui/unused_enumerate_index.stderr index 14d1d20a66e4..c742cc8a85ba 100644 --- a/tests/ui/unused_enumerate_index.stderr +++ b/tests/ui/unused_enumerate_index.stderr @@ -1,8 +1,8 @@ error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:12:19 + --> tests/ui/unused_enumerate_index.rs:12:27 | LL | for (_, x) in v.iter().enumerate() { - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | = note: `-D clippy::unused-enumerate-index` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unused_enumerate_index)]` @@ -13,10 +13,10 @@ LL + for x in v.iter() { | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:60:19 + --> tests/ui/unused_enumerate_index.rs:60:24 | LL | for (_, x) in dummy.enumerate() { - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -25,10 +25,10 @@ LL + for x in dummy { | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:65:39 + --> tests/ui/unused_enumerate_index.rs:65:38 | LL | let _ = vec![1, 2, 3].into_iter().enumerate().map(|(_, x)| println!("{x}")); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -37,10 +37,10 @@ LL + let _ = vec![1, 2, 3].into_iter().map(|x| println!("{x}")); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:68:39 + --> tests/ui/unused_enumerate_index.rs:68:38 | LL | let p = vec![1, 2, 3].into_iter().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -50,10 +50,10 @@ LL ~ p.map(|x| println!("{x}")); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:90:17 + --> tests/ui/unused_enumerate_index.rs:90:16 | LL | _ = mac2!().enumerate().map(|(_, _v)| {}); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -62,10 +62,10 @@ LL + _ = mac2!().map(|_v| {}); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:99:39 + --> tests/ui/unused_enumerate_index.rs:99:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -75,10 +75,10 @@ LL ~ let x = v.map(|x: i32| x).sum::(); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:105:39 + --> tests/ui/unused_enumerate_index.rs:105:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -88,10 +88,10 @@ LL ~ let x = v.map(|x: i32| x).sum::(); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:110:39 + --> tests/ui/unused_enumerate_index.rs:110:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call |