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..732f49392b31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,11 @@ path = "src/driver.rs" [dependencies] clippy_config = { path = "clippy_config" } clippy_lints = { path = "clippy_lints" } +clippy_lints_casts = { path = "clippy_lints_casts" } +clippy_lints_early = { path = "clippy_lints_early" } +clippy_lints_loops = { path = "clippy_lints_loops" } +clippy_lints_matches = { path = "clippy_lints_matches" } +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..b86bf2fbdfda 100644 --- a/clippy_dev/src/release.rs +++ b/clippy_dev/src/release.rs @@ -4,6 +4,11 @@ use std::fmt::Write; static CARGO_TOML_FILES: &[&str] = &[ "clippy_config/Cargo.toml", "clippy_lints/Cargo.toml", + "clippy_lints_casts/Cargo.toml", + "clippy_lints_early/Cargo.toml", + "clippy_lints_matches/Cargo.toml", + "clippy_lints_loops/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..d6fd23bda79b 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -22,6 +22,11 @@ pub fn run(port: u16, lint: Option) -> ! { let index_time = mtime("util/gh-pages/index.html"); let times = [ "clippy_lints/src", + "clippy_lints_casts/src", + "clippy_lints_early/src", + "clippy_lints_loops/src", + "clippy_lints_matches/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..3e907adce9a5 100644 --- a/clippy_dev/src/setup/intellij.rs +++ b/clippy_dev/src/setup/intellij.rs @@ -14,6 +14,31 @@ 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_casts", + "clippy_lints_casts/Cargo.toml", + "clippy_lints_casts/src/lib.rs", + ), + ClippyProjectInfo::new( + "clippy_lints_early", + "clippy_lints_early/Cargo.toml", + "clippy_lints_early/src/lib.rs", + ), + ClippyProjectInfo::new( + "clippy_lints_loops", + "clippy_lints_loops/Cargo.toml", + "clippy_lints_loops/src/lib.rs", + ), + ClippyProjectInfo::new( + "clippy_lints_matches", + "clippy_lints_matches/Cargo.toml", + "clippy_lints_matches/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/Cargo.toml b/clippy_lints/Cargo.toml index c03cc99b581f..e9dfd047452b 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -20,7 +20,6 @@ regex-syntax = "0.8" serde = { version = "1.0", features = ["derive"] } toml = "0.7.3" unicode-normalization = "0.1" -unicode-script = { version = "0.5", default-features = false } semver = "1.0" url = "2.2" diff --git a/clippy_lints/src/casts/as_pointer_underscore.rs b/clippy_lints/src/casts/as_pointer_underscore.rs deleted file mode 100644 index 3ab6693756f5..000000000000 --- a/clippy_lints/src/casts/as_pointer_underscore.rs +++ /dev/null @@ -1,19 +0,0 @@ -use rustc_errors::Applicability; -use rustc_lint::LateContext; -use rustc_middle::ty::Ty; - -pub fn check<'tcx>(cx: &LateContext<'tcx>, ty_into: Ty<'_>, cast_to_hir: &'tcx rustc_hir::Ty<'tcx>) { - if let rustc_hir::TyKind::Ptr(rustc_hir::MutTy { ty, .. }) = cast_to_hir.kind - && matches!(ty.kind, rustc_hir::TyKind::Infer(())) - { - clippy_utils::diagnostics::span_lint_and_sugg( - cx, - super::AS_POINTER_UNDERSCORE, - cast_to_hir.span, - "using inferred pointer cast", - "use explicit type", - ty_into.to_string(), - Applicability::MachineApplicable, - ); - } -} diff --git a/clippy_lints/src/casts/fn_to_numeric_cast_any.rs b/clippy_lints/src/casts/fn_to_numeric_cast_any.rs deleted file mode 100644 index b22e8f4ee891..000000000000 --- a/clippy_lints/src/casts/fn_to_numeric_cast_any.rs +++ /dev/null @@ -1,36 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_with_applicability; -use rustc_errors::Applicability; -use rustc_hir::Expr; -use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty}; - -use super::FN_TO_NUMERIC_CAST_ANY; - -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { - // We allow casts from any function type to any function type. - match cast_to.kind() { - ty::FnDef(..) | ty::FnPtr(..) => return, - _ => { /* continue to checks */ }, - } - - if let ty::FnDef(..) | ty::FnPtr(..) = cast_from.kind() { - let mut applicability = Applicability::MaybeIncorrect; - let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability); - - span_lint_and_then( - cx, - FN_TO_NUMERIC_CAST_ANY, - expr.span, - format!("casting function pointer `{from_snippet}` to `{cast_to}`"), - |diag| { - diag.span_suggestion_verbose( - expr.span, - "did you mean to invoke the function?", - format!("{from_snippet}() as {cast_to}"), - applicability, - ); - }, - ); - } -} diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs deleted file mode 100644 index 37accff5eaa8..000000000000 --- a/clippy_lints/src/casts/mod.rs +++ /dev/null @@ -1,919 +0,0 @@ -mod as_pointer_underscore; -mod as_ptr_cast_mut; -mod as_underscore; -mod borrow_as_ptr; -mod cast_abs_to_unsigned; -mod cast_enum_constructor; -mod cast_lossless; -mod cast_nan_to_int; -mod cast_possible_truncation; -mod cast_possible_wrap; -mod cast_precision_loss; -mod cast_ptr_alignment; -mod cast_sign_loss; -mod cast_slice_different_sizes; -mod cast_slice_from_raw_parts; -mod char_lit_as_u8; -mod confusing_method_to_numeric_cast; -mod fn_to_numeric_cast; -mod fn_to_numeric_cast_any; -mod fn_to_numeric_cast_with_truncation; -mod manual_dangling_ptr; -mod ptr_as_ptr; -mod ptr_cast_constness; -mod ref_as_ptr; -mod unnecessary_cast; -mod utils; -mod zero_ptr; - -use clippy_config::Conf; -use clippy_utils::is_hir_ty_cfg_dependant; -use clippy_utils::msrvs::{self, Msrv}; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_session::impl_lint_pass; - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts from any numeric type to a float type where - /// the receiving type cannot store all values from the original type without - /// rounding errors. This possible rounding is to be expected, so this lint is - /// `Allow` by default. - /// - /// Basically, this warns on casting any integer with 32 or more bits to `f32` - /// or any 64-bit integer to `f64`. - /// - /// ### Why is this bad? - /// It's not bad at all. But in some applications it can be - /// helpful to know where precision loss can take place. This lint can help find - /// those places in the code. - /// - /// ### Example - /// ```no_run - /// let x = u64::MAX; - /// x as f64; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CAST_PRECISION_LOSS, - pedantic, - "casts that cause loss of precision, e.g., `x as f32` where `x: u64`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts from a signed to an unsigned numeric - /// type. In this case, negative values wrap around to large positive values, - /// which can be quite surprising in practice. However, since the cast works as - /// defined, this lint is `Allow` by default. - /// - /// ### Why is this bad? - /// Possibly surprising results. You can activate this lint - /// as a one-time check to see where numeric wrapping can arise. - /// - /// ### Example - /// ```no_run - /// let y: i8 = -1; - /// y as u64; // will return 18446744073709551615 - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CAST_SIGN_LOSS, - pedantic, - "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts between numeric types that may - /// truncate large values. This is expected behavior, so the cast is `Allow` by - /// default. It suggests user either explicitly ignore the lint, - /// or use `try_from()` and handle the truncation, default, or panic explicitly. - /// - /// ### Why is this bad? - /// In some problem domains, it is good practice to avoid - /// truncation. This lint can be activated to help assess where additional - /// checks could be beneficial. - /// - /// ### Example - /// ```no_run - /// fn as_u8(x: u64) -> u8 { - /// x as u8 - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn as_u8(x: u64) -> u8 { - /// if let Ok(x) = u8::try_from(x) { - /// x - /// } else { - /// todo!(); - /// } - /// } - /// // Or - /// #[allow(clippy::cast_possible_truncation)] - /// fn as_u16(x: u64) -> u16 { - /// x as u16 - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CAST_POSSIBLE_TRUNCATION, - pedantic, - "casts that may cause truncation of the value, e.g., `x as u8` where `x: u32`, or `x as i32` where `x: f32`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts from an unsigned type to a signed type of - /// the same size, or possibly smaller due to target-dependent integers. - /// Performing such a cast is a no-op for the compiler (that is, nothing is - /// changed at the bit level), and the binary representation of the value is - /// reinterpreted. This can cause wrapping if the value is too big - /// for the target signed type. However, the cast works as defined, so this lint - /// is `Allow` by default. - /// - /// ### Why is this bad? - /// While such a cast is not bad in itself, the results can - /// be surprising when this is not the intended behavior: - /// - /// ### Example - /// ```no_run - /// let _ = u32::MAX as i32; // will yield a value of `-1` - /// ``` - /// - /// Use instead: - /// ```no_run - /// let _ = i32::try_from(u32::MAX).ok(); - /// ``` - /// - #[clippy::version = "pre 1.29.0"] - pub CAST_POSSIBLE_WRAP, - pedantic, - "casts that may cause wrapping around the value, e.g., `x as i32` where `x: u32` and `x > i32::MAX`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts between numeric types that can be replaced by safe - /// conversion functions. - /// - /// ### Why is this bad? - /// Rust's `as` keyword will perform many kinds of conversions, including - /// silently lossy conversions. Conversion functions such as `i32::from` - /// will only perform lossless conversions. Using the conversion functions - /// prevents conversions from becoming silently lossy if the input types - /// ever change, and makes it clear for people reading the code that the - /// conversion is lossless. - /// - /// ### Example - /// ```no_run - /// fn as_u64(x: u8) -> u64 { - /// x as u64 - /// } - /// ``` - /// - /// Using `::from` would look like this: - /// - /// ```no_run - /// fn as_u64(x: u8) -> u64 { - /// u64::from(x) - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CAST_LOSSLESS, - pedantic, - "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts to the same type, casts of int literals to integer - /// types, casts of float literals to float types, and casts between raw - /// pointers that don't change type or constness. - /// - /// ### Why is this bad? - /// It's just unnecessary. - /// - /// ### Known problems - /// When the expression on the left is a function call, the lint considers - /// the return type to be a type alias if it's aliased through a `use` - /// statement (like `use std::io::Result as IoResult`). It will not lint - /// such cases. - /// - /// This check will only work on primitive types without any intermediate - /// references: raw pointers and trait objects may or may not work. - /// - /// ### Example - /// ```no_run - /// let _ = 2i32 as i32; - /// let _ = 0.5 as f32; - /// ``` - /// - /// Better: - /// - /// ```no_run - /// let _ = 2_i32; - /// let _ = 0.5_f32; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub UNNECESSARY_CAST, - complexity, - "cast to the same type, e.g., `x as i32` where `x: i32`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts, using `as` or `pointer::cast`, from a - /// less strictly aligned pointer to a more strictly aligned pointer. - /// - /// ### Why is this bad? - /// Dereferencing the resulting pointer may be undefined behavior. - /// - /// ### Known problems - /// Using [`std::ptr::read_unaligned`](https://doc.rust-lang.org/std/ptr/fn.read_unaligned.html) and [`std::ptr::write_unaligned`](https://doc.rust-lang.org/std/ptr/fn.write_unaligned.html) or - /// similar on the resulting pointer is fine. Is over-zealous: casts with - /// manual alignment checks or casts like `u64` -> `u8` -> `u16` can be - /// fine. Miri is able to do a more in-depth analysis. - /// - /// ### Example - /// ```no_run - /// let _ = (&1u8 as *const u8) as *const u16; - /// let _ = (&mut 1u8 as *mut u8) as *mut u16; - /// - /// (&1u8 as *const u8).cast::(); - /// (&mut 1u8 as *mut u8).cast::(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CAST_PTR_ALIGNMENT, - pedantic, - "cast from a pointer to a more strictly aligned pointer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of function pointers to something other than `usize`. - /// - /// ### Why is this bad? - /// Casting a function pointer to anything other than `usize`/`isize` is - /// not portable across architectures. If the target type is too small the - /// address would be truncated, and target types larger than `usize` are - /// unnecessary. - /// - /// Casting to `isize` also doesn't make sense, since addresses are never - /// signed. - /// - /// ### Example - /// ```no_run - /// fn fun() -> i32 { 1 } - /// let _ = fun as i64; - /// ``` - /// - /// Use instead: - /// ```no_run - /// # fn fun() -> i32 { 1 } - /// let _ = fun as usize; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub FN_TO_NUMERIC_CAST, - style, - "casting a function pointer to a numeric type other than `usize`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of a function pointer to a numeric type not wide enough to - /// store an address. - /// - /// ### Why is this bad? - /// Such a cast discards some bits of the function's address. If this is intended, it would be more - /// clearly expressed by casting to `usize` first, then casting the `usize` to the intended type (with - /// a comment) to perform the truncation. - /// - /// ### Example - /// ```no_run - /// fn fn1() -> i16 { - /// 1 - /// }; - /// let _ = fn1 as i32; - /// ``` - /// - /// Use instead: - /// ```no_run - /// // Cast to usize first, then comment with the reason for the truncation - /// fn fn1() -> i16 { - /// 1 - /// }; - /// let fn_ptr = fn1 as usize; - /// let fn_ptr_truncated = fn_ptr as i32; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION, - style, - "casting a function pointer to a numeric type not wide enough to store the address" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of a function pointer to any integer type. - /// - /// ### Why restrict this? - /// Casting a function pointer to an integer can have surprising results and can occur - /// accidentally if parentheses are omitted from a function call. If you aren't doing anything - /// low-level with function pointers then you can opt out of casting functions to integers in - /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function - /// pointer casts in your code. - /// - /// ### Example - /// ```no_run - /// // fn1 is cast as `usize` - /// fn fn1() -> u16 { - /// 1 - /// }; - /// let _ = fn1 as usize; - /// ``` - /// - /// Use instead: - /// ```no_run - /// // maybe you intended to call the function? - /// fn fn2() -> u16 { - /// 1 - /// }; - /// let _ = fn2() as usize; - /// - /// // or - /// - /// // maybe you intended to cast it to a function type? - /// fn fn3() -> u16 { - /// 1 - /// } - /// let _ = fn3 as fn() -> u16; - /// ``` - #[clippy::version = "1.58.0"] - pub FN_TO_NUMERIC_CAST_ANY, - restriction, - "casting a function pointer to any integer type" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for expressions where a character literal is cast - /// to `u8` and suggests using a byte literal instead. - /// - /// ### Why is this bad? - /// In general, casting values to smaller types is - /// error-prone and should be avoided where possible. In the particular case of - /// converting a character literal to `u8`, it is easy to avoid by just using a - /// byte literal instead. As an added bonus, `b'a'` is also slightly shorter - /// than `'a' as u8`. - /// - /// ### Example - /// ```rust,ignore - /// 'x' as u8 - /// ``` - /// - /// A better version, using the byte literal: - /// - /// ```rust,ignore - /// b'x' - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CHAR_LIT_AS_U8, - complexity, - "casting a character literal to `u8` truncates" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `as` casts between raw pointers that don't change their - /// constness, namely `*const T` to `*const U` and `*mut T` to `*mut U`. - /// - /// ### Why is this bad? - /// Though `as` casts between raw pointers are not terrible, - /// `pointer::cast` is safer because it cannot accidentally change the - /// pointer's mutability, nor cast the pointer to other types like `usize`. - /// - /// ### Example - /// ```no_run - /// let ptr: *const u32 = &42_u32; - /// let mut_ptr: *mut u32 = &mut 42_u32; - /// let _ = ptr as *const i32; - /// let _ = mut_ptr as *mut i32; - /// ``` - /// Use instead: - /// ```no_run - /// let ptr: *const u32 = &42_u32; - /// let mut_ptr: *mut u32 = &mut 42_u32; - /// let _ = ptr.cast::(); - /// let _ = mut_ptr.cast::(); - /// ``` - #[clippy::version = "1.51.0"] - pub PTR_AS_PTR, - pedantic, - "casting using `as` between raw pointers that doesn't change their constness, where `pointer::cast` could take the place of `as`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `as` casts between raw pointers that change their constness, namely `*const T` to - /// `*mut T` and `*mut T` to `*const T`. - /// - /// ### Why is this bad? - /// Though `as` casts between raw pointers are not terrible, `pointer::cast_mut` and - /// `pointer::cast_const` are safer because they cannot accidentally cast the pointer to another - /// type. Or, when null pointers are involved, `null()` and `null_mut()` can be used directly. - /// - /// ### Example - /// ```no_run - /// let ptr: *const u32 = &42_u32; - /// let mut_ptr = ptr as *mut u32; - /// let ptr = mut_ptr as *const u32; - /// let ptr1 = std::ptr::null::() as *mut u32; - /// let ptr2 = std::ptr::null_mut::() as *const u32; - /// let ptr3 = std::ptr::null::().cast_mut(); - /// let ptr4 = std::ptr::null_mut::().cast_const(); - /// ``` - /// Use instead: - /// ```no_run - /// let ptr: *const u32 = &42_u32; - /// let mut_ptr = ptr.cast_mut(); - /// let ptr = mut_ptr.cast_const(); - /// let ptr1 = std::ptr::null_mut::(); - /// let ptr2 = std::ptr::null::(); - /// let ptr3 = std::ptr::null_mut::(); - /// let ptr4 = std::ptr::null::(); - /// ``` - #[clippy::version = "1.72.0"] - pub PTR_CAST_CONSTNESS, - pedantic, - "casting using `as` on raw pointers to change constness when specialized methods apply" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts from an enum type to an integral type that will definitely truncate the - /// value. - /// - /// ### Why is this bad? - /// The resulting integral value will not match the value of the variant it came from. - /// - /// ### Example - /// ```no_run - /// enum E { X = 256 }; - /// let _ = E::X as u8; - /// ``` - #[clippy::version = "1.61.0"] - pub CAST_ENUM_TRUNCATION, - suspicious, - "casts from an enum type to an integral type that will truncate the value" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `as` casts between raw pointers to slices with differently sized elements. - /// - /// ### Why is this bad? - /// The produced raw pointer to a slice does not update its length metadata. The produced - /// pointer will point to a different number of bytes than the original pointer because the - /// length metadata of a raw slice pointer is in elements rather than bytes. - /// Producing a slice reference from the raw pointer will either create a slice with - /// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior. - /// - /// ### Example - /// // Missing data - /// ```no_run - /// let a = [1_i32, 2, 3, 4]; - /// let p = &a as *const [i32] as *const [u8]; - /// unsafe { - /// println!("{:?}", &*p); - /// } - /// ``` - /// // Undefined Behavior (note: also potential alignment issues) - /// ```no_run - /// let a = [1_u8, 2, 3, 4]; - /// let p = &a as *const [u8] as *const [u32]; - /// unsafe { - /// println!("{:?}", &*p); - /// } - /// ``` - /// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length - /// ```no_run - /// let a = [1_i32, 2, 3, 4]; - /// let old_ptr = &a as *const [i32]; - /// // The data pointer is cast to a pointer to the target `u8` not `[u8]` - /// // The length comes from the known length of 4 i32s times the 4 bytes per i32 - /// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16); - /// unsafe { - /// println!("{:?}", &*new_ptr); - /// } - /// ``` - #[clippy::version = "1.61.0"] - pub CAST_SLICE_DIFFERENT_SIZES, - correctness, - "casting using `as` between raw pointers to slices of types with different sizes" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts from an enum tuple constructor to an integer. - /// - /// ### Why is this bad? - /// The cast is easily confused with casting a c-like enum value to an integer. - /// - /// ### Example - /// ```no_run - /// enum E { X(i32) }; - /// let _ = E::X as usize; - /// ``` - #[clippy::version = "1.61.0"] - pub CAST_ENUM_CONSTRUCTOR, - suspicious, - "casts from an enum tuple constructor to an integer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of the `abs()` method that cast the result to unsigned. - /// - /// ### Why is this bad? - /// The `unsigned_abs()` method avoids panic when called on the MIN value. - /// - /// ### Example - /// ```no_run - /// let x: i32 = -42; - /// let y: u32 = x.abs() as u32; - /// ``` - /// Use instead: - /// ```no_run - /// let x: i32 = -42; - /// let y: u32 = x.unsigned_abs(); - /// ``` - #[clippy::version = "1.62.0"] - pub CAST_ABS_TO_UNSIGNED, - suspicious, - "casting the result of `abs()` to an unsigned integer can panic" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `as _` conversion using inferred type. - /// - /// ### Why restrict this? - /// The conversion might include lossy conversion or a dangerous cast that might go - /// undetected due to the type being inferred. - /// - /// The lint is allowed by default as using `_` is less wordy than always specifying the type. - /// - /// ### Example - /// ```no_run - /// fn foo(n: usize) {} - /// let n: u16 = 256; - /// foo(n as _); - /// ``` - /// Use instead: - /// ```no_run - /// fn foo(n: usize) {} - /// let n: u16 = 256; - /// foo(n as usize); - /// ``` - #[clippy::version = "1.63.0"] - pub AS_UNDERSCORE, - restriction, - "detects `as _` conversion" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `&expr as *const T` or - /// `&mut expr as *mut T`, and suggest using `&raw const` or - /// `&raw mut` instead. - /// - /// ### Why is this bad? - /// This would improve readability and avoid creating a reference - /// that points to an uninitialized value or unaligned place. - /// Read the `&raw` explanation in the Reference for more information. - /// - /// ### Example - /// ```no_run - /// let val = 1; - /// let p = &val as *const i32; - /// - /// let mut val_mut = 1; - /// let p_mut = &mut val_mut as *mut i32; - /// ``` - /// Use instead: - /// ```no_run - /// let val = 1; - /// let p = &raw const val; - /// - /// let mut val_mut = 1; - /// let p_mut = &raw mut val_mut; - /// ``` - #[clippy::version = "1.60.0"] - pub BORROW_AS_PTR, - pedantic, - "borrowing just to cast to a raw pointer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for a raw slice being cast to a slice pointer - /// - /// ### Why is this bad? - /// This can result in multiple `&mut` references to the same location when only a pointer is - /// required. - /// `ptr::slice_from_raw_parts` is a safe alternative that doesn't require - /// the same [safety requirements] to be upheld. - /// - /// ### Example - /// ```rust,ignore - /// let _: *const [u8] = std::slice::from_raw_parts(ptr, len) as *const _; - /// let _: *mut [u8] = std::slice::from_raw_parts_mut(ptr, len) as *mut _; - /// ``` - /// Use instead: - /// ```rust,ignore - /// let _: *const [u8] = std::ptr::slice_from_raw_parts(ptr, len); - /// let _: *mut [u8] = std::ptr::slice_from_raw_parts_mut(ptr, len); - /// ``` - /// [safety requirements]: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety - #[clippy::version = "1.65.0"] - pub CAST_SLICE_FROM_RAW_PARTS, - suspicious, - "casting a slice created from a pointer and length to a slice pointer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the result of a `&self`-taking `as_ptr` being cast to a mutable pointer. - /// - /// ### Why is this bad? - /// Since `as_ptr` takes a `&self`, the pointer won't have write permissions unless interior - /// mutability is used, making it unlikely that having it as a mutable pointer is correct. - /// - /// ### Example - /// ```no_run - /// let mut vec = Vec::::with_capacity(1); - /// let ptr = vec.as_ptr() as *mut u8; - /// unsafe { ptr.write(4) }; // UNDEFINED BEHAVIOUR - /// ``` - /// Use instead: - /// ```no_run - /// let mut vec = Vec::::with_capacity(1); - /// let ptr = vec.as_mut_ptr(); - /// unsafe { ptr.write(4) }; - /// ``` - #[clippy::version = "1.66.0"] - pub AS_PTR_CAST_MUT, - nursery, - "casting the result of the `&self`-taking `as_ptr` to a mutable pointer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for a known NaN float being cast to an integer - /// - /// ### Why is this bad? - /// NaNs are cast into zero, so one could simply use this and make the - /// code more readable. The lint could also hint at a programmer error. - /// - /// ### Example - /// ```rust,ignore - /// let _ = (0.0_f32 / 0.0) as u64; - /// ``` - /// Use instead: - /// ```rust,ignore - /// let _ = 0_u64; - /// ``` - #[clippy::version = "1.66.0"] - pub CAST_NAN_TO_INT, - suspicious, - "casting a known floating-point NaN into an integer" -} - -declare_clippy_lint! { - /// ### What it does - /// Catch casts from `0` to some pointer type - /// - /// ### Why is this bad? - /// This generally means `null` and is better expressed as - /// {`std`, `core`}`::ptr::`{`null`, `null_mut`}. - /// - /// ### Example - /// ```no_run - /// let a = 0 as *const u32; - /// ``` - /// - /// Use instead: - /// ```no_run - /// let a = std::ptr::null::(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub ZERO_PTR, - style, - "using `0 as *{const, mut} T`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of references to pointer using `as` - /// and suggests `std::ptr::from_ref` and `std::ptr::from_mut` instead. - /// - /// ### Why is this bad? - /// Using `as` casts may result in silently changing mutability or type. - /// - /// ### Example - /// ```no_run - /// let a_ref = &1; - /// let a_ptr = a_ref as *const _; - /// ``` - /// Use instead: - /// ```no_run - /// let a_ref = &1; - /// let a_ptr = std::ptr::from_ref(a_ref); - /// ``` - #[clippy::version = "1.78.0"] - pub REF_AS_PTR, - pedantic, - "using `as` to cast a reference to pointer" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for the usage of `as *const _` or `as *mut _` conversion using inferred type. - /// - /// ### Why restrict this? - /// The conversion might include a dangerous cast that might go undetected due to the type being inferred. - /// - /// ### Example - /// ```no_run - /// fn as_usize(t: &T) -> usize { - /// // BUG: `t` is already a reference, so we will here - /// // return a dangling pointer to a temporary value instead - /// &t as *const _ as usize - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn as_usize(t: &T) -> usize { - /// t as *const T as usize - /// } - /// ``` - #[clippy::version = "1.85.0"] - pub AS_POINTER_UNDERSCORE, - restriction, - "detects `as *mut _` and `as *const _` conversion" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of small constant literals or `mem::align_of` results to raw pointers. - /// - /// ### Why is this bad? - /// This creates a dangling pointer and is better expressed as - /// {`std`, `core`}`::ptr::`{`dangling`, `dangling_mut`}. - /// - /// ### Example - /// ```no_run - /// let ptr = 4 as *const u32; - /// let aligned = std::mem::align_of::() as *const u32; - /// let mut_ptr: *mut i64 = 8 as *mut _; - /// ``` - /// Use instead: - /// ```no_run - /// let ptr = std::ptr::dangling::(); - /// let aligned = std::ptr::dangling::(); - /// let mut_ptr: *mut i64 = std::ptr::dangling_mut(); - /// ``` - #[clippy::version = "1.88.0"] - pub MANUAL_DANGLING_PTR, - style, - "casting small constant literals to pointers to create dangling pointers" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for casts of a primitive method pointer like `max`/`min` to any integer type. - /// - /// ### Why restrict this? - /// Casting a function pointer to an integer can have surprising results and can occur - /// accidentally if parentheses are omitted from a function call. If you aren't doing anything - /// low-level with function pointers then you can opt out of casting functions to integers in - /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function - /// pointer casts in your code. - /// - /// ### Example - /// ```no_run - /// let _ = u16::max as usize; - /// ``` - /// - /// Use instead: - /// ```no_run - /// let _ = u16::MAX as usize; - /// ``` - #[clippy::version = "1.86.0"] - pub CONFUSING_METHOD_TO_NUMERIC_CAST, - suspicious, - "casting a primitive method pointer to any integer type" -} - -pub struct Casts { - msrv: Msrv, -} - -impl Casts { - pub fn new(conf: &'static Conf) -> Self { - Self { msrv: conf.msrv } - } -} - -impl_lint_pass!(Casts => [ - CAST_PRECISION_LOSS, - CAST_SIGN_LOSS, - CAST_POSSIBLE_TRUNCATION, - CAST_POSSIBLE_WRAP, - CAST_LOSSLESS, - CAST_PTR_ALIGNMENT, - CAST_SLICE_DIFFERENT_SIZES, - UNNECESSARY_CAST, - FN_TO_NUMERIC_CAST_ANY, - FN_TO_NUMERIC_CAST, - FN_TO_NUMERIC_CAST_WITH_TRUNCATION, - CHAR_LIT_AS_U8, - PTR_AS_PTR, - PTR_CAST_CONSTNESS, - CAST_ENUM_TRUNCATION, - CAST_ENUM_CONSTRUCTOR, - CAST_ABS_TO_UNSIGNED, - AS_UNDERSCORE, - BORROW_AS_PTR, - CAST_SLICE_FROM_RAW_PARTS, - AS_PTR_CAST_MUT, - CAST_NAN_TO_INT, - ZERO_PTR, - REF_AS_PTR, - AS_POINTER_UNDERSCORE, - MANUAL_DANGLING_PTR, - CONFUSING_METHOD_TO_NUMERIC_CAST, -]); - -impl<'tcx> LateLintPass<'tcx> for Casts { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if expr.span.in_external_macro(cx.sess().source_map()) { - return; - } - - if let ExprKind::Cast(cast_from_expr, cast_to_hir) = expr.kind { - if is_hir_ty_cfg_dependant(cx, cast_to_hir) { - return; - } - let (cast_from, cast_to) = ( - cx.typeck_results().expr_ty(cast_from_expr), - cx.typeck_results().expr_ty(expr), - ); - - if !expr.span.from_expansion() && unnecessary_cast::check(cx, expr, cast_from_expr, cast_from, cast_to) { - return; - } - cast_slice_from_raw_parts::check(cx, expr, cast_from_expr, cast_to, self.msrv); - ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); - as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to); - fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to); - confusing_method_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to); - fn_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to); - fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to); - zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir, self.msrv); - - if self.msrv.meets(cx, msrvs::MANUAL_DANGLING_PTR) { - manual_dangling_ptr::check(cx, expr, cast_from_expr, cast_to_hir); - } - - if cast_to.is_numeric() { - cast_possible_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span); - if cast_from.is_numeric() { - cast_possible_wrap::check(cx, expr, cast_from, cast_to); - cast_precision_loss::check(cx, expr, cast_from, cast_to); - cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to); - cast_abs_to_unsigned::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); - cast_nan_to_int::check(cx, expr, cast_from_expr, cast_from, cast_to); - } - cast_lossless::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir, self.msrv); - cast_enum_constructor::check(cx, expr, cast_from_expr, cast_from); - } - - as_underscore::check(cx, expr, cast_to_hir); - as_pointer_underscore::check(cx, cast_to, cast_to_hir); - - let was_borrow_as_ptr_emitted = self.msrv.meets(cx, msrvs::BORROW_AS_PTR) - && borrow_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir, self.msrv); - if !was_borrow_as_ptr_emitted && self.msrv.meets(cx, msrvs::PTR_FROM_REF) { - ref_as_ptr::check(cx, expr, cast_from_expr, cast_to_hir); - } - } - - if self.msrv.meets(cx, msrvs::RAW_REF_OP) { - borrow_as_ptr::check_implicit_cast(cx, expr); - } - cast_ptr_alignment::check(cx, expr); - char_lit_as_u8::check(cx, expr); - ptr_as_ptr::check(cx, expr, self.msrv); - cast_slice_different_sizes::check(cx, expr, self.msrv); - ptr_cast_constness::check_null_ptr_cast_method(cx, expr); - } -} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index c3f8e02b4c06..79e70ea5f98c 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -4,13 +4,10 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::absolute_paths::ABSOLUTE_PATHS_INFO, - crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO, crate::approx_const::APPROX_CONSTANT_INFO, crate::arbitrary_source_item_ordering::ARBITRARY_SOURCE_ITEM_ORDERING_INFO, crate::arc_with_non_send_sync::ARC_WITH_NON_SEND_SYNC_INFO, crate::as_conversions::AS_CONVERSIONS_INFO, - crate::asm_syntax::INLINE_ASM_X86_ATT_SYNTAX_INFO, - crate::asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX_INFO, crate::assertions_on_constants::ASSERTIONS_ON_CONSTANTS_INFO, crate::assertions_on_result_states::ASSERTIONS_ON_RESULT_STATES_INFO, crate::assigning_clones::ASSIGNING_CLONES_INFO, @@ -40,41 +37,12 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::booleans::OVERLY_COMPLEX_BOOL_EXPR_INFO, crate::borrow_deref_ref::BORROW_DEREF_REF_INFO, crate::box_default::BOX_DEFAULT_INFO, - crate::byte_char_slices::BYTE_CHAR_SLICES_INFO, crate::cargo::CARGO_COMMON_METADATA_INFO, crate::cargo::LINT_GROUPS_PRIORITY_INFO, crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO, crate::cargo::NEGATIVE_FEATURE_NAMES_INFO, crate::cargo::REDUNDANT_FEATURE_NAMES_INFO, crate::cargo::WILDCARD_DEPENDENCIES_INFO, - crate::casts::AS_POINTER_UNDERSCORE_INFO, - crate::casts::AS_PTR_CAST_MUT_INFO, - crate::casts::AS_UNDERSCORE_INFO, - crate::casts::BORROW_AS_PTR_INFO, - crate::casts::CAST_ABS_TO_UNSIGNED_INFO, - crate::casts::CAST_ENUM_CONSTRUCTOR_INFO, - crate::casts::CAST_ENUM_TRUNCATION_INFO, - crate::casts::CAST_LOSSLESS_INFO, - crate::casts::CAST_NAN_TO_INT_INFO, - crate::casts::CAST_POSSIBLE_TRUNCATION_INFO, - crate::casts::CAST_POSSIBLE_WRAP_INFO, - crate::casts::CAST_PRECISION_LOSS_INFO, - crate::casts::CAST_PTR_ALIGNMENT_INFO, - crate::casts::CAST_SIGN_LOSS_INFO, - crate::casts::CAST_SLICE_DIFFERENT_SIZES_INFO, - crate::casts::CAST_SLICE_FROM_RAW_PARTS_INFO, - crate::casts::CHAR_LIT_AS_U8_INFO, - crate::casts::CONFUSING_METHOD_TO_NUMERIC_CAST_INFO, - crate::casts::FN_TO_NUMERIC_CAST_INFO, - crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO, - crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO, - crate::casts::MANUAL_DANGLING_PTR_INFO, - crate::casts::PTR_AS_PTR_INFO, - crate::casts::PTR_CAST_CONSTNESS_INFO, - crate::casts::REF_AS_PTR_INFO, - crate::casts::UNNECESSARY_CAST_INFO, - crate::casts::ZERO_PTR_INFO, - crate::cfg_not_test::CFG_NOT_TEST_INFO, crate::checked_conversions::CHECKED_CONVERSIONS_INFO, crate::cloned_ref_to_slice_refs::CLONED_REF_TO_SLICE_REFS_INFO, crate::coerce_container_to_any::COERCE_CONTAINER_TO_ANY_INFO, @@ -84,11 +52,10 @@ 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, crate::create_dir::CREATE_DIR_INFO, crate::dbg_macro::DBG_MACRO_INFO, crate::default::DEFAULT_TRAIT_ACCESS_INFO, @@ -102,15 +69,14 @@ 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, crate::disallowed_methods::DISALLOWED_METHODS_INFO, crate::disallowed_names::DISALLOWED_NAMES_INFO, - crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO, crate::disallowed_types::DISALLOWED_TYPES_INFO, crate::doc::DOC_BROKEN_LINK_INFO, crate::doc::DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS_INFO, @@ -131,16 +97,11 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::doc::TEST_ATTR_IN_DOCTEST_INFO, crate::doc::TOO_LONG_FIRST_DOC_PARAGRAPH_INFO, crate::doc::UNNECESSARY_SAFETY_DOC_INFO, - crate::double_parens::DOUBLE_PARENS_INFO, crate::drop_forget_ref::DROP_NON_DROP_INFO, crate::drop_forget_ref::FORGET_NON_DROP_INFO, crate::drop_forget_ref::MEM_FORGET_INFO, - crate::duplicate_mod::DUPLICATE_MOD_INFO, - crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO, crate::empty_drop::EMPTY_DROP_INFO, crate::empty_enum::EMPTY_ENUM_INFO, - crate::empty_line_after::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO, - crate::empty_line_after::EMPTY_LINE_AFTER_OUTER_ATTR_INFO, crate::empty_with_brackets::EMPTY_ENUM_VARIANTS_WITH_BRACKETS_INFO, crate::empty_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS_INFO, crate::endian_bytes::BIG_ENDIAN_BYTES_INFO, @@ -155,14 +116,12 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS_INFO, crate::excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS_INFO, crate::excessive_bools::STRUCT_EXCESSIVE_BOOLS_INFO, - crate::excessive_nesting::EXCESSIVE_NESTING_INFO, crate::exhaustive_items::EXHAUSTIVE_ENUMS_INFO, crate::exhaustive_items::EXHAUSTIVE_STRUCTS_INFO, crate::exit::EXIT_INFO, crate::explicit_write::EXPLICIT_WRITE_INFO, crate::extra_unused_type_parameters::EXTRA_UNUSED_TYPE_PARAMETERS_INFO, crate::fallible_impl_from::FALLIBLE_IMPL_FROM_INFO, - crate::field_scoped_visibility_modifiers::FIELD_SCOPED_VISIBILITY_MODIFIERS_INFO, crate::float_literal::EXCESSIVE_PRECISION_INFO, crate::float_literal::LOSSY_FLOAT_LITERAL_INFO, crate::floating_point_arithmetic::IMPRECISE_FLOPS_INFO, @@ -177,10 +136,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::format_impl::PRINT_IN_FORMAT_IMPL_INFO, crate::format_impl::RECURSIVE_FORMAT_IMPL_INFO, crate::format_push_string::FORMAT_PUSH_STRING_INFO, - crate::formatting::POSSIBLE_MISSING_COMMA_INFO, - crate::formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING_INFO, - crate::formatting::SUSPICIOUS_ELSE_FORMATTING_INFO, - crate::formatting::SUSPICIOUS_UNARY_OP_FORMATTING_INFO, crate::four_forward_slashes::FOUR_FORWARD_SLASHES_INFO, crate::from_over_into::FROM_OVER_INTO_INFO, crate::from_raw_with_void_ptr::FROM_RAW_WITH_VOID_PTR_INFO, @@ -225,7 +180,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::inline_fn_without_body::INLINE_FN_WITHOUT_BODY_INFO, crate::instant_subtraction::MANUAL_INSTANT_ELAPSED_INFO, crate::instant_subtraction::UNCHECKED_DURATION_SUBTRACTION_INFO, - crate::int_plus_one::INT_PLUS_ONE_INFO, crate::integer_division_remainder_used::INTEGER_DIVISION_REMAINDER_USED_INFO, crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO, crate::item_name_repetitions::ENUM_VARIANT_NAMES_INFO, @@ -258,37 +212,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO, crate::lifetimes::NEEDLESS_LIFETIMES_INFO, crate::lines_filter_map_ok::LINES_FILTER_MAP_OK_INFO, - crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO, - crate::literal_representation::INCONSISTENT_DIGIT_GROUPING_INFO, - crate::literal_representation::LARGE_DIGIT_GROUPS_INFO, - crate::literal_representation::MISTYPED_LITERAL_SUFFIXES_INFO, - crate::literal_representation::UNREADABLE_LITERAL_INFO, - crate::literal_representation::UNUSUAL_BYTE_GROUPINGS_INFO, crate::literal_string_with_formatting_args::LITERAL_STRING_WITH_FORMATTING_ARGS_INFO, - crate::loops::CHAR_INDICES_AS_BYTE_INDICES_INFO, - crate::loops::EMPTY_LOOP_INFO, - crate::loops::EXPLICIT_COUNTER_LOOP_INFO, - crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO, - crate::loops::EXPLICIT_ITER_LOOP_INFO, - crate::loops::FOR_KV_MAP_INFO, - crate::loops::INFINITE_LOOP_INFO, - crate::loops::ITER_NEXT_LOOP_INFO, - crate::loops::MANUAL_FIND_INFO, - crate::loops::MANUAL_FLATTEN_INFO, - crate::loops::MANUAL_MEMCPY_INFO, - crate::loops::MANUAL_SLICE_FILL_INFO, - crate::loops::MANUAL_WHILE_LET_SOME_INFO, - crate::loops::MISSING_SPIN_LOOP_INFO, - crate::loops::MUT_RANGE_BOUND_INFO, - crate::loops::NEEDLESS_RANGE_LOOP_INFO, - crate::loops::NEVER_LOOP_INFO, - crate::loops::SAME_ITEM_PUSH_INFO, - crate::loops::SINGLE_ELEMENT_LOOP_INFO, - crate::loops::UNUSED_ENUMERATE_INDEX_INFO, - crate::loops::WHILE_FLOAT_INFO, - crate::loops::WHILE_IMMUTABLE_CONDITION_INFO, - crate::loops::WHILE_LET_LOOP_INFO, - crate::loops::WHILE_LET_ON_ITERATOR_INFO, crate::macro_metavars_in_unsafe::MACRO_METAVARS_IN_UNSAFE_INFO, crate::macro_use::MACRO_USE_IMPORTS_INFO, crate::main_recursion::MAIN_RECURSION_INFO, @@ -315,204 +239,17 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO, crate::manual_string_new::MANUAL_STRING_NEW_INFO, crate::manual_strip::MANUAL_STRIP_INFO, - crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO, - crate::map_unit_fn::RESULT_MAP_UNIT_FN_INFO, crate::match_result_ok::MATCH_RESULT_OK_INFO, - crate::matches::COLLAPSIBLE_MATCH_INFO, - crate::matches::INFALLIBLE_DESTRUCTURING_MATCH_INFO, - crate::matches::MANUAL_FILTER_INFO, - crate::matches::MANUAL_MAP_INFO, - crate::matches::MANUAL_OK_ERR_INFO, - crate::matches::MANUAL_UNWRAP_OR_INFO, - crate::matches::MANUAL_UNWRAP_OR_DEFAULT_INFO, - crate::matches::MATCH_AS_REF_INFO, - crate::matches::MATCH_BOOL_INFO, - crate::matches::MATCH_LIKE_MATCHES_MACRO_INFO, - crate::matches::MATCH_OVERLAPPING_ARM_INFO, - crate::matches::MATCH_REF_PATS_INFO, - 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::NEEDLESS_MATCH_INFO, - crate::matches::REDUNDANT_GUARDS_INFO, - crate::matches::REDUNDANT_PATTERN_MATCHING_INFO, - crate::matches::REST_PAT_IN_FULLY_BOUND_STRUCTS_INFO, - crate::matches::SIGNIFICANT_DROP_IN_SCRUTINEE_INFO, - crate::matches::SINGLE_MATCH_INFO, - crate::matches::SINGLE_MATCH_ELSE_INFO, - crate::matches::TRY_ERR_INFO, - crate::matches::WILDCARD_ENUM_MATCH_ARM_INFO, - crate::matches::WILDCARD_IN_OR_PATTERNS_INFO, crate::mem_replace::MEM_REPLACE_OPTION_WITH_NONE_INFO, 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, crate::misc::TOPLEVEL_REF_ARG_INFO, crate::misc::USED_UNDERSCORE_BINDING_INFO, crate::misc::USED_UNDERSCORE_ITEMS_INFO, - crate::misc_early::BUILTIN_TYPE_SHADOW_INFO, - crate::misc_early::DUPLICATE_UNDERSCORE_ARGUMENT_INFO, - crate::misc_early::MIXED_CASE_HEX_LITERALS_INFO, - crate::misc_early::REDUNDANT_AT_REST_PATTERN_INFO, - crate::misc_early::REDUNDANT_PATTERN_INFO, - crate::misc_early::SEPARATED_LITERAL_SUFFIX_INFO, - crate::misc_early::UNNEEDED_FIELD_PATTERN_INFO, - crate::misc_early::UNNEEDED_WILDCARD_PATTERN_INFO, - crate::misc_early::UNSEPARATED_LITERAL_SUFFIX_INFO, - crate::misc_early::ZERO_PREFIXED_LITERAL_INFO, crate::mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER_INFO, crate::missing_assert_message::MISSING_ASSERT_MESSAGE_INFO, crate::missing_asserts_for_indexing::MISSING_ASSERTS_FOR_INDEXING_INFO, @@ -525,10 +262,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::missing_trait_methods::MISSING_TRAIT_METHODS_INFO, crate::mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION_INFO, crate::mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION_INFO, - crate::module_style::MOD_MODULE_FILES_INFO, - crate::module_style::SELF_NAMED_MODULE_FILES_INFO, - crate::multi_assignments::MULTI_ASSIGNMENTS_INFO, - crate::multiple_bound_locations::MULTIPLE_BOUND_LOCATIONS_INFO, crate::multiple_unsafe_ops_per_block::MULTIPLE_UNSAFE_OPS_PER_BLOCK_INFO, crate::mut_key::MUTABLE_KEY_TYPE_INFO, crate::mut_mut::MUT_MUT_INFO, @@ -536,14 +269,11 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL_INFO, crate::mutex_atomic::MUTEX_ATOMIC_INFO, crate::mutex_atomic::MUTEX_INTEGER_INFO, - crate::needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE_INFO, crate::needless_bool::BOOL_COMPARISON_INFO, crate::needless_bool::NEEDLESS_BOOL_INFO, crate::needless_bool::NEEDLESS_BOOL_ASSIGN_INFO, crate::needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE_INFO, crate::needless_borrows_for_generic_args::NEEDLESS_BORROWS_FOR_GENERIC_ARGS_INFO, - crate::needless_continue::NEEDLESS_CONTINUE_INFO, - crate::needless_else::NEEDLESS_ELSE_INFO, crate::needless_for_each::NEEDLESS_FOR_EACH_INFO, crate::needless_if::NEEDLESS_IF_INFO, crate::needless_late_init::NEEDLESS_LATE_INIT_INFO, @@ -564,15 +294,10 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::non_canonical_impls::NON_CANONICAL_PARTIAL_ORD_IMPL_INFO, crate::non_copy_const::BORROW_INTERIOR_MUTABLE_CONST_INFO, crate::non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST_INFO, - crate::non_expressive_names::JUST_UNDERSCORES_AND_DIGITS_INFO, - crate::non_expressive_names::MANY_SINGLE_CHAR_NAMES_INFO, - crate::non_expressive_names::SIMILAR_NAMES_INFO, crate::non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS_INFO, crate::non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY_INFO, crate::non_std_lazy_statics::NON_STD_LAZY_STATICS_INFO, crate::non_zero_suggestions::NON_ZERO_SUGGESTIONS_INFO, - crate::nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES_INFO, - crate::octal_escapes::OCTAL_ESCAPES_INFO, crate::only_used_in_recursion::ONLY_USED_IN_RECURSION_INFO, crate::operators::ABSURD_EXTREME_COMPARISONS_INFO, crate::operators::ARITHMETIC_SIDE_EFFECTS_INFO, @@ -601,7 +326,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::operators::REDUNDANT_COMPARISONS_INFO, crate::operators::SELF_ASSIGNMENT_INFO, crate::operators::VERBOSE_BIT_MASK_INFO, - crate::option_env_unwrap::OPTION_ENV_UNWRAP_INFO, crate::option_if_let_else::OPTION_IF_LET_ELSE_INFO, crate::panic_in_result_fn::PANIC_IN_RESULT_FN_INFO, crate::panic_unimplemented::PANIC_INFO, @@ -609,7 +333,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::panic_unimplemented::UNIMPLEMENTED_INFO, crate::panic_unimplemented::UNREACHABLE_INFO, crate::panicking_overflow_checks::PANICKING_OVERFLOW_CHECKS_INFO, - crate::partial_pub_fields::PARTIAL_PUB_FIELDS_INFO, crate::partialeq_ne_impl::PARTIALEQ_NE_IMPL_INFO, crate::partialeq_to_none::PARTIALEQ_TO_NONE_INFO, crate::pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE_INFO, @@ -618,35 +341,27 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::pattern_type_mismatch::PATTERN_TYPE_MISMATCH_INFO, crate::permissions_set_readonly_false::PERMISSIONS_SET_READONLY_FALSE_INFO, crate::pointers_in_nomem_asm_block::POINTERS_IN_NOMEM_ASM_BLOCK_INFO, - crate::precedence::PRECEDENCE_INFO, - crate::precedence::PRECEDENCE_BITS_INFO, crate::ptr::CMP_NULL_INFO, crate::ptr::MUT_FROM_REF_INFO, crate::ptr::PTR_ARG_INFO, crate::ptr::PTR_EQ_INFO, crate::ptr_offset_with_cast::PTR_OFFSET_WITH_CAST_INFO, crate::pub_underscore_fields::PUB_UNDERSCORE_FIELDS_INFO, - crate::pub_use::PUB_USE_INFO, crate::question_mark::QUESTION_MARK_INFO, crate::question_mark_used::QUESTION_MARK_USED_INFO, crate::ranges::MANUAL_RANGE_CONTAINS_INFO, 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::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, crate::redundant_clone::REDUNDANT_CLONE_INFO, crate::redundant_closure_call::REDUNDANT_CLOSURE_CALL_INFO, - crate::redundant_else::REDUNDANT_ELSE_INFO, - crate::redundant_field_names::REDUNDANT_FIELD_NAMES_INFO, crate::redundant_locals::REDUNDANT_LOCALS_INFO, crate::redundant_pub_crate::REDUNDANT_PUB_CRATE_INFO, crate::redundant_slicing::DEREF_BY_SLICING_INFO, crate::redundant_slicing::REDUNDANT_SLICING_INFO, - crate::redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES_INFO, crate::redundant_test_prefix::REDUNDANT_TEST_PREFIX_INFO, crate::redundant_type_annotations::REDUNDANT_TYPE_ANNOTATIONS_INFO, crate::ref_option_ref::REF_OPTION_REF_INFO, @@ -673,8 +388,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::shadow::SHADOW_UNRELATED_INFO, crate::significant_drop_tightening::SIGNIFICANT_DROP_TIGHTENING_INFO, crate::single_call_fn::SINGLE_CALL_FN_INFO, - crate::single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES_INFO, - crate::single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS_INFO, crate::single_option_map::SINGLE_OPTION_MAP_INFO, crate::single_range_in_vec_init::SINGLE_RANGE_IN_VEC_INIT_INFO, crate::size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT_INFO, @@ -685,23 +398,21 @@ 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, crate::suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL_INFO, crate::suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL_INFO, crate::suspicious_xor_used_as_pow::SUSPICIOUS_XOR_USED_AS_POW_INFO, crate::swap::ALMOST_SWAPPED_INFO, crate::swap::MANUAL_SWAP_INFO, crate::swap_ptr_to_ref::SWAP_PTR_TO_REF_INFO, - crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO, crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO, crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO, crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO, @@ -712,7 +423,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 +430,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, @@ -751,18 +462,14 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::unnecessary_literal_bound::UNNECESSARY_LITERAL_BOUND_INFO, crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO, crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO, - crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO, crate::unnecessary_semicolon::UNNECESSARY_SEMICOLON_INFO, crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO, crate::unnecessary_wraps::UNNECESSARY_WRAPS_INFO, crate::unneeded_struct_pattern::UNNEEDED_STRUCT_PATTERN_INFO, - crate::unnested_or_patterns::UNNESTED_OR_PATTERNS_INFO, - crate::unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME_INFO, crate::unused_async::UNUSED_ASYNC_INFO, crate::unused_io_amount::UNUSED_IO_AMOUNT_INFO, crate::unused_peekable::UNUSED_PEEKABLE_INFO, crate::unused_result_ok::UNUSED_RESULT_OK_INFO, - crate::unused_rounding::UNUSED_ROUNDING_INFO, crate::unused_self::UNUSED_SELF_INFO, crate::unused_trait_names::UNUSED_TRAIT_NAMES_INFO, crate::unused_unit::UNUSED_UNIT_INFO, @@ -775,20 +482,17 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::useless_conversion::USELESS_CONVERSION_INFO, 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::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..5728283da571 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -32,9 +32,7 @@ // (Currently there is no way to opt into sysroot crates without `extern crate`.) extern crate pulldown_cmark; extern crate rustc_abi; -extern crate rustc_arena; extern crate rustc_ast; -extern crate rustc_ast_pretty; extern crate rustc_attr_data_structures; extern crate rustc_data_structures; extern crate rustc_driver; @@ -53,10 +51,7 @@ extern crate rustc_parse_format; extern crate rustc_resolve; 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] extern crate clippy_utils; @@ -71,12 +66,10 @@ pub mod deprecated_lints; // begin lints modules, do not remove this comment, it's used in `update_lints` mod absolute_paths; -mod almost_complete_range; mod approx_const; mod arbitrary_source_item_ordering; mod arc_with_non_send_sync; mod as_conversions; -mod asm_syntax; mod assertions_on_constants; mod assertions_on_result_states; mod assigning_clones; @@ -89,10 +82,7 @@ mod bool_to_int_with_if; mod booleans; mod borrow_deref_ref; mod box_default; -mod byte_char_slices; mod cargo; -mod casts; -mod cfg_not_test; mod checked_conversions; mod cloned_ref_to_slice_refs; mod coerce_container_to_any; @@ -102,7 +92,6 @@ mod collection_is_never_read; mod comparison_chain; mod copies; mod copy_iterator; -mod crate_in_macro_def; mod create_dir; mod dbg_macro; mod default; @@ -116,16 +105,11 @@ mod derive; mod disallowed_macros; mod disallowed_methods; mod disallowed_names; -mod disallowed_script_idents; mod disallowed_types; mod doc; -mod double_parens; mod drop_forget_ref; -mod duplicate_mod; -mod else_if_without_else; mod empty_drop; mod empty_enum; -mod empty_line_after; mod empty_with_brackets; mod endian_bytes; mod entry; @@ -135,20 +119,17 @@ mod error_impl_error; mod escape; mod eta_reduction; mod excessive_bools; -mod excessive_nesting; mod exhaustive_items; mod exit; mod explicit_write; mod extra_unused_type_parameters; mod fallible_impl_from; -mod field_scoped_visibility_modifiers; mod float_literal; mod floating_point_arithmetic; mod format; mod format_args; mod format_impl; mod format_push_string; -mod formatting; mod four_forward_slashes; mod from_over_into; mod from_raw_with_void_ptr; @@ -177,7 +158,6 @@ mod inherent_to_string; mod init_numbered_fields; mod inline_fn_without_body; mod instant_subtraction; -mod int_plus_one; mod integer_division_remainder_used; mod invalid_upcast_comparisons; mod item_name_repetitions; @@ -199,9 +179,7 @@ mod let_underscore; mod let_with_type_underscore; mod lifetimes; mod lines_filter_map_ok; -mod literal_representation; mod literal_string_with_formatting_args; -mod loops; mod macro_metavars_in_unsafe; mod macro_use; mod main_recursion; @@ -227,15 +205,11 @@ mod manual_rotate; mod manual_slice_size_calculation; mod manual_string_new; mod manual_strip; -mod map_unit_fn; mod match_result_ok; -mod matches; mod mem_replace; -mod methods; mod min_ident_chars; mod minmax; mod misc; -mod misc_early; mod mismatching_type_param_order; mod missing_assert_message; mod missing_asserts_for_indexing; @@ -247,21 +221,15 @@ mod missing_fields_in_debug; mod missing_inline; mod missing_trait_methods; mod mixed_read_write_in_expression; -mod module_style; -mod multi_assignments; -mod multiple_bound_locations; mod multiple_unsafe_ops_per_block; mod mut_key; mod mut_mut; mod mut_reference; mod mutable_debug_assertion; mod mutex_atomic; -mod needless_arbitrary_self_type; mod needless_bool; mod needless_borrowed_ref; mod needless_borrows_for_generic_args; -mod needless_continue; -mod needless_else; mod needless_for_each; mod needless_if; mod needless_late_init; @@ -278,21 +246,16 @@ mod no_effect; mod no_mangle_with_rust_abi; mod non_canonical_impls; mod non_copy_const; -mod non_expressive_names; mod non_octal_unix_permissions; mod non_send_fields_in_send_ty; mod non_std_lazy_statics; mod non_zero_suggestions; -mod nonstandard_macro_braces; -mod octal_escapes; mod only_used_in_recursion; mod operators; -mod option_env_unwrap; mod option_if_let_else; mod panic_in_result_fn; mod panic_unimplemented; mod panicking_overflow_checks; -mod partial_pub_fields; mod partialeq_ne_impl; mod partialeq_to_none; mod pass_by_ref_or_value; @@ -300,26 +263,20 @@ mod pathbuf_init_then_push; mod pattern_type_mismatch; mod permissions_set_readonly_false; mod pointers_in_nomem_asm_block; -mod precedence; mod ptr; mod ptr_offset_with_cast; mod pub_underscore_fields; -mod pub_use; mod question_mark; mod question_mark_used; mod ranges; -mod raw_strings; mod rc_clone_in_vec_init; mod read_zero_byte_vec; mod redundant_async_block; mod redundant_clone; mod redundant_closure_call; -mod redundant_else; -mod redundant_field_names; mod redundant_locals; mod redundant_pub_crate; mod redundant_slicing; -mod redundant_static_lifetimes; mod redundant_test_prefix; mod redundant_type_annotations; mod ref_option_ref; @@ -339,8 +296,6 @@ mod set_contains_or_insert; mod shadow; mod significant_drop_tightening; mod single_call_fn; -mod single_char_lifetime_names; -mod single_component_path_imports; mod single_option_map; mod single_range_in_vec_init; mod size_of_in_element_count; @@ -350,12 +305,10 @@ mod std_instead_of_core; mod string_patterns; mod strings; mod strlen_on_c_strings; -mod suspicious_operation_groupings; mod suspicious_trait_impl; mod suspicious_xor_used_as_pow; mod swap; mod swap_ptr_to_ref; -mod tabs_in_doc_comments; mod temporary_assignment; mod tests_outside_test_module; mod to_digit_is_some; @@ -376,18 +329,14 @@ mod unnecessary_box_returns; mod unnecessary_literal_bound; mod unnecessary_map_on_constructor; mod unnecessary_owned_empty_strings; -mod unnecessary_self_imports; mod unnecessary_semicolon; mod unnecessary_struct_initialization; mod unnecessary_wraps; mod unneeded_struct_pattern; -mod unnested_or_patterns; -mod unsafe_removed_from_name; mod unused_async; mod unused_io_amount; mod unused_peekable; mod unused_result_ok; -mod unused_rounding; mod unused_self; mod unused_trait_names; mod unused_unit; @@ -399,7 +348,6 @@ mod useless_concat; mod useless_conversion; mod vec; mod vec_init_then_push; -mod visibility; mod wildcard_imports; mod write; mod zero_div_zero; @@ -410,7 +358,6 @@ mod zombie_processes; use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation}; use clippy_utils::macros::FormatArgsStorage; -use rustc_data_structures::fx::FxHashSet; use rustc_lint::Lint; use utils::attr_collector::{AttrCollector, AttrStorage}; @@ -440,7 +387,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); } @@ -501,15 +448,9 @@ 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))); - store.register_early_pass(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(conf))); - store.register_early_pass(move || Box::new(redundant_field_names::RedundantFieldNames::new(conf))); store.register_late_pass(move |_| Box::new(checked_conversions::CheckedConversions::new(conf))); store.register_late_pass(move |_| Box::new(mem_replace::MemReplace::new(conf))); store.register_late_pass(move |_| Box::new(ranges::Ranges::new(conf))); @@ -517,14 +458,11 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(move |_| Box::new(use_self::UseSelf::new(conf))); store.register_late_pass(move |_| Box::new(missing_const_for_fn::MissingConstForFn::new(conf))); store.register_late_pass(move |_| Box::new(needless_question_mark::NeedlessQuestionMark)); - store.register_late_pass(move |_| Box::new(casts::Casts::new(conf))); - store.register_early_pass(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(conf))); store.register_late_pass(|_| Box::new(size_of_in_element_count::SizeOfInElementCount)); store.register_late_pass(|_| Box::new(same_name_method::SameNameMethod)); store.register_late_pass(move |_| Box::new(index_refutable_slice::IndexRefutableSlice::new(conf))); store.register_late_pass(|_| Box::::default()); store.register_late_pass(|_| Box::new(unit_types::UnitTypes)); - store.register_late_pass(move |_| Box::new(loops::Loops::new(conf))); store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |_| Box::new(lifetimes::Lifetimes::new(conf))); store.register_late_pass(|_| Box::new(entry::HashMapPass)); @@ -582,9 +520,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(fallible_impl_from::FallibleImplFrom)); store.register_late_pass(move |_| Box::new(question_mark::QuestionMark::new(conf))); store.register_late_pass(|_| Box::new(question_mark_used::QuestionMarkUsed)); - store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings)); store.register_late_pass(|_| Box::new(suspicious_trait_impl::SuspiciousImpl)); - store.register_late_pass(|_| Box::new(map_unit_fn::MapUnit)); store.register_late_pass(|_| Box::new(inherent_impl::MultipleInherentImpl)); store.register_late_pass(|_| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd)); store.register_late_pass(|_| Box::new(unwrap::Unwrap)); @@ -601,30 +537,17 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain)); store.register_late_pass(move |tcx| Box::new(mut_key::MutableKeyType::new(tcx, conf))); store.register_early_pass(|| Box::new(reference::DerefAddrOf)); - store.register_early_pass(|| Box::new(double_parens::DoubleParens)); let format_args = format_args_storage.clone(); store.register_late_pass(move |_| Box::new(format_impl::FormatImpl::new(format_args.clone()))); - store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval)); - store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse)); - store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne)); - store.register_early_pass(|| Box::new(formatting::Formatting)); - store.register_early_pass(|| Box::new(misc_early::MiscEarlyLints)); store.register_late_pass(|_| Box::new(redundant_closure_call::RedundantClosureCall)); store.register_early_pass(|| Box::new(unused_unit::UnusedUnit)); store.register_late_pass(|_| Box::new(unused_unit::UnusedUnit)); store.register_late_pass(|_| Box::new(returns::Return)); store.register_late_pass(move |_| Box::new(collapsible_if::CollapsibleIf::new(conf))); store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements)); - store.register_early_pass(|| Box::new(precedence::Precedence)); store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals)); - store.register_early_pass(|| Box::new(needless_continue::NeedlessContinue)); - store.register_early_pass(|| Box::new(redundant_else::RedundantElse)); store.register_late_pass(|_| Box::new(create_dir::CreateDir)); - store.register_early_pass(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType)); - store.register_early_pass(move || Box::new(literal_representation::LiteralDigitGrouping::new(conf))); - store.register_early_pass(move || Box::new(literal_representation::DecimalLiteralRepresentation::new(conf))); store.register_late_pass(move |_| Box::new(item_name_repetitions::ItemNameRepetitions::new(conf))); - store.register_early_pass(|| Box::new(tabs_in_doc_comments::TabsInDocComments)); store.register_late_pass(move |_| Box::new(upper_case_acronyms::UpperCaseAcronyms::new(conf))); store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |_| Box::new(unused_self::UnusedSelf::new(conf))); @@ -636,9 +559,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic)); store.register_late_pass(|_| Box::new(as_conversions::AsConversions)); store.register_late_pass(|_| Box::new(let_underscore::LetUnderscore)); - store.register_early_pass(|| Box::::default()); store.register_late_pass(move |_| Box::new(excessive_bools::ExcessiveBools::new(conf))); - store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap)); store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(conf))); store.register_late_pass(|_| Box::::default()); store.register_late_pass(|_| Box::>::default()); @@ -650,8 +571,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(equatable_if_let::PatternEquality)); store.register_late_pass(|_| Box::new(manual_async_fn::ManualAsyncFn)); store.register_late_pass(|_| Box::new(panic_in_result_fn::PanicInResultFn)); - store.register_early_pass(move || Box::new(non_expressive_names::NonExpressiveNames::new(conf))); - store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(conf))); store.register_late_pass(|_| Box::::default()); store.register_late_pass(|_| Box::new(pattern_type_mismatch::PatternTypeMismatch)); store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult)); @@ -660,8 +579,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co let attrs = attr_storage.clone(); store.register_late_pass(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf, attrs.clone()))); store.register_late_pass(move |tcx| Box::new(disallowed_methods::DisallowedMethods::new(tcx, conf))); - store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax)); - store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax)); store.register_late_pass(|_| Box::new(empty_drop::EmptyDrop)); store.register_late_pass(|_| Box::new(strings::StrToString)); store.register_late_pass(|_| Box::new(strings::StringToString)); @@ -671,11 +588,9 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(|_| Box::new(from_str_radix_10::FromStrRadix10)); store.register_late_pass(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(conf))); store.register_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison)); - store.register_early_pass(move || Box::new(module_style::ModStyle)); store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |tcx| Box::new(disallowed_types::DisallowedTypes::new(tcx, conf))); store.register_late_pass(move |tcx| Box::new(missing_enforced_import_rename::ImportRename::new(tcx, conf))); - store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(conf))); store.register_late_pass(|_| Box::new(strlen_on_c_strings::StrlenOnCStrings)); store.register_late_pass(move |_| Box::new(self_named_constructors::SelfNamedConstructors)); store.register_late_pass(move |_| Box::new(iter_not_returning_iterator::IterNotReturningIterator)); @@ -685,11 +600,9 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co let format_args = format_args_storage.clone(); store.register_late_pass(move |tcx| Box::new(format_args::FormatArgs::new(tcx, conf, format_args.clone()))); store.register_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray)); - store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes)); store.register_late_pass(|_| Box::new(needless_late_init::NeedlessLateInit)); store.register_late_pass(|_| Box::new(return_self_not_must_use::ReturnSelfNotMustUse)); store.register_late_pass(|_| Box::new(init_numbered_fields::NumberedFields)); - store.register_early_pass(|| Box::new(single_char_lifetime_names::SingleCharLifetimeNames)); store.register_late_pass(move |_| Box::new(manual_bits::ManualBits::new(conf))); store.register_late_pass(|_| Box::new(default_union_representation::DefaultUnionRepresentation)); store.register_late_pass(|_| Box::::default()); @@ -697,18 +610,13 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co let format_args = format_args_storage.clone(); store.register_late_pass(move |_| Box::new(write::Write::new(conf, format_args.clone()))); store.register_late_pass(move |_| Box::new(cargo::Cargo::new(conf))); - store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef)); store.register_late_pass(|_| Box::new(empty_with_brackets::EmptyWithBrackets::default())); store.register_late_pass(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings)); - store.register_early_pass(|| Box::new(pub_use::PubUse)); store.register_late_pass(|_| Box::new(format_push_string::FormatPushString)); store.register_late_pass(move |_| Box::new(large_include_file::LargeIncludeFile::new(conf))); store.register_early_pass(move || Box::new(large_include_file::LargeIncludeFile::new(conf))); store.register_late_pass(|_| Box::new(strings::TrimSplitWhitespace)); store.register_late_pass(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit)); - store.register_early_pass(|| Box::::default()); - store.register_early_pass(|| Box::new(unused_rounding::UnusedRounding)); - store.register_early_pass(move || Box::new(almost_complete_range::AlmostCompleteRange::new(conf))); store.register_late_pass(|_| Box::new(swap_ptr_to_ref::SwapPtrToRef)); store.register_late_pass(|_| Box::new(mismatching_type_param_order::TypeParamMismatch)); store.register_late_pass(|_| Box::new(read_zero_byte_vec::ReadZeroByteVec)); @@ -724,11 +632,9 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf))); store.register_late_pass(|_| Box::new(manual_string_new::ManualStringNew)); store.register_late_pass(|_| Box::new(unused_peekable::UnusedPeekable)); - store.register_early_pass(|| Box::new(multi_assignments::MultiAssignments)); store.register_late_pass(|_| Box::new(bool_to_int_with_if::BoolToIntWithIf)); store.register_late_pass(|_| Box::new(box_default::BoxDefault)); store.register_late_pass(|_| Box::new(implicit_saturating_add::ImplicitSaturatingAdd)); - store.register_early_pass(|| Box::new(partial_pub_fields::PartialPubFields)); store.register_late_pass(|_| Box::new(missing_trait_methods::MissingTraitMethods)); store.register_late_pass(|_| Box::new(from_raw_with_void_ptr::FromRawWithVoidPtr)); store.register_late_pass(|_| Box::new(suspicious_xor_used_as_pow::ConfusingXorAndPow)); @@ -750,11 +656,9 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(move |_| Box::new(lines_filter_map_ok::LinesFilterMapOk::new(conf))); store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule)); store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation::new(conf))); - store.register_early_pass(move || Box::new(excessive_nesting::ExcessiveNesting::new(conf))); store.register_late_pass(|_| Box::new(items_after_test_module::ItemsAfterTestModule)); store.register_early_pass(|| Box::new(ref_patterns::RefPatterns)); store.register_late_pass(|_| Box::new(default_constructed_unit_structs::DefaultConstructedUnitStructs)); - store.register_early_pass(|| Box::new(needless_else::NeedlessElse)); store.register_late_pass(|_| Box::new(missing_fields_in_debug::MissingFieldsInDebug)); store.register_late_pass(|_| Box::new(endian_bytes::EndianBytes)); store.register_late_pass(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations)); @@ -766,10 +670,8 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(move |_| Box::new(needless_pass_by_ref_mut::NeedlessPassByRefMut::new(conf))); store.register_late_pass(|_| Box::new(non_canonical_impls::NonCanonicalImpls)); store.register_late_pass(move |_| Box::new(single_call_fn::SingleCallFn::new(conf))); - store.register_early_pass(move || Box::new(raw_strings::RawStrings::new(conf))); store.register_late_pass(move |_| Box::new(legacy_numeric_constants::LegacyNumericConstants::new(conf))); store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns)); - store.register_early_pass(|| Box::new(visibility::Visibility)); store.register_late_pass(move |_| Box::new(tuple_array_conversions::TupleArrayConversions::new(conf))); store.register_late_pass(move |_| Box::new(manual_float_methods::ManualFloatMethods::new(conf))); store.register_late_pass(|_| Box::new(four_forward_slashes::FourForwardSlashes)); @@ -799,16 +701,12 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(move |_| Box::new(missing_const_for_thread_local::MissingConstForThreadLocal::new(conf))); store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(conf))); store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl)); - store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations)); store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(conf))); store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects)); store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed)); store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf))); store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf))); - store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); store.register_late_pass(|_| Box::new(set_contains_or_insert::SetContainsOrInsert)); - store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice)); - store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest)); store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses)); store.register_late_pass(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock)); store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf))); @@ -818,7 +716,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf))); store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp)); store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound)); - store.register_early_pass(|| Box::new(empty_line_after::EmptyLineAfter::new())); store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf))); store.register_late_pass(|_| Box::new(useless_concat::UselessConcat)); store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern)); @@ -831,4 +728,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/empty_loop.rs b/clippy_lints/src/loops/empty_loop.rs deleted file mode 100644 index e809987d75a0..000000000000 --- a/clippy_lints/src/loops/empty_loop.rs +++ /dev/null @@ -1,29 +0,0 @@ -use super::EMPTY_LOOP; -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::{is_in_panic_handler, is_no_std_crate, sym}; - -use rustc_hir::{Block, Expr, ItemKind, Node}; -use rustc_lint::LateContext; - -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, loop_block: &Block<'_>) { - let parent_hir_id = cx.tcx.parent_hir_id(expr.hir_id); - if let Node::Item(parent_node) = cx.tcx.hir_node(parent_hir_id) - && matches!(parent_node.kind, ItemKind::Fn { .. }) - && let attrs = cx.tcx.hir_attrs(parent_hir_id) - && attrs.iter().any(|attr| attr.has_name(sym::rustc_intrinsic)) - { - // Intrinsic functions are expanded into an empty loop when lowering the AST - // to simplify the job of later passes which might expect any function to have a body. - return; - } - - if loop_block.stmts.is_empty() && loop_block.expr.is_none() && !is_in_panic_handler(cx, expr) { - let msg = "empty `loop {}` wastes CPU cycles"; - let help = if is_no_std_crate(cx) { - "you should either use `panic!()` or add a call pausing or sleeping the thread to the loop body" - } else { - "you should either use `panic!()` or add `std::thread::sleep(..);` to the loop body" - }; - span_lint_and_help(cx, EMPTY_LOOP, expr.span, msg, None, help); - } -} diff --git a/clippy_lints/src/loops/iter_next_loop.rs b/clippy_lints/src/loops/iter_next_loop.rs deleted file mode 100644 index b8a263817d29..000000000000 --- a/clippy_lints/src/loops/iter_next_loop.rs +++ /dev/null @@ -1,18 +0,0 @@ -use super::ITER_NEXT_LOOP; -use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_trait_method; -use rustc_hir::Expr; -use rustc_lint::LateContext; -use rustc_span::sym; - -pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>) { - if is_trait_method(cx, arg, sym::Iterator) { - span_lint( - cx, - ITER_NEXT_LOOP, - arg.span, - "you are iterating over `Iterator::next()` which is an Option; this will compile but is \ - probably not what you want", - ); - } -} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs deleted file mode 100644 index 01c36b8cb12f..000000000000 --- a/clippy_lints/src/loops/mod.rs +++ /dev/null @@ -1,929 +0,0 @@ -mod char_indices_as_byte_indices; -mod empty_loop; -mod explicit_counter_loop; -mod explicit_into_iter_loop; -mod explicit_iter_loop; -mod for_kv_map; -mod infinite_loop; -mod iter_next_loop; -mod manual_find; -mod manual_flatten; -mod manual_memcpy; -mod manual_slice_fill; -mod manual_while_let_some; -mod missing_spin_loop; -mod mut_range_bound; -mod needless_range_loop; -mod never_loop; -mod same_item_push; -mod single_element_loop; -mod unused_enumerate_index; -mod utils; -mod while_float; -mod while_immutable_condition; -mod while_let_loop; -mod while_let_on_iterator; - -use clippy_config::Conf; -use clippy_utils::msrvs::Msrv; -use clippy_utils::{higher, sym}; -use rustc_ast::Label; -use rustc_hir::{Expr, ExprKind, LoopSource, Pat}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::impl_lint_pass; -use rustc_span::Span; -use utils::{IncrementVisitor, InitializeVisitor, make_iterator_snippet}; - -declare_clippy_lint! { - /// ### What it does - /// Checks for for-loops that manually copy items between - /// slices that could be optimized by having a memcpy. - /// - /// ### Why is this bad? - /// It is not as fast as a memcpy. - /// - /// ### Example - /// ```no_run - /// # let src = vec![1]; - /// # let mut dst = vec![0; 65]; - /// for i in 0..src.len() { - /// dst[i + 64] = src[i]; - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let src = vec![1]; - /// # let mut dst = vec![0; 65]; - /// dst[64..(src.len() + 64)].clone_from_slice(&src[..]); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MANUAL_MEMCPY, - perf, - "manually copying items between slices" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for looping over the range of `0..len` of some - /// collection just to get the values by index. - /// - /// ### Why is this bad? - /// Just iterating the collection itself makes the intent - /// more clear and is probably faster because it eliminates - /// the bounds check that is done when indexing. - /// - /// ### Example - /// ```no_run - /// let vec = vec!['a', 'b', 'c']; - /// for i in 0..vec.len() { - /// println!("{}", vec[i]); - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// let vec = vec!['a', 'b', 'c']; - /// for i in vec { - /// println!("{}", i); - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub NEEDLESS_RANGE_LOOP, - style, - "for-looping over a range of indices where an iterator over items would do" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for loops on `x.iter()` where `&x` will do, and - /// suggests the latter. - /// - /// ### Why is this bad? - /// Readability. - /// - /// ### Known problems - /// False negatives. We currently only warn on some known - /// types. - /// - /// ### Example - /// ```no_run - /// // with `y` a `Vec` or slice: - /// # let y = vec![1]; - /// for x in y.iter() { - /// // .. - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let y = vec![1]; - /// for x in &y { - /// // .. - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub EXPLICIT_ITER_LOOP, - pedantic, - "for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for loops on `y.into_iter()` where `y` will do, and - /// suggests the latter. - /// - /// ### Why is this bad? - /// Readability. - /// - /// ### Example - /// ```no_run - /// # let y = vec![1]; - /// // with `y` a `Vec` or slice: - /// for x in y.into_iter() { - /// // .. - /// } - /// ``` - /// can be rewritten to - /// ```no_run - /// # let y = vec![1]; - /// for x in y { - /// // .. - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub EXPLICIT_INTO_ITER_LOOP, - pedantic, - "for-looping over `_.into_iter()` when `_` would do" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for loops on `x.next()`. - /// - /// ### Why is this bad? - /// `next()` returns either `Some(value)` if there was a - /// value, or `None` otherwise. The insidious thing is that `Option<_>` - /// implements `IntoIterator`, so that possibly one value will be iterated, - /// leading to some hard to find bugs. No one will want to write such code - /// [except to win an Underhanded Rust - /// Contest](https://www.reddit.com/r/rust/comments/3hb0wm/underhanded_rust_contest/cu5yuhr). - /// - /// ### Example - /// ```ignore - /// for x in y.next() { - /// .. - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub ITER_NEXT_LOOP, - correctness, - "for-looping over `_.next()` which is probably not intended" -} - -declare_clippy_lint! { - /// ### What it does - /// Detects `loop + match` combinations that are easier - /// written as a `while let` loop. - /// - /// ### Why is this bad? - /// The `while let` loop is usually shorter and more - /// readable. - /// - /// ### Example - /// ```rust,no_run - /// let y = Some(1); - /// loop { - /// let x = match y { - /// Some(x) => x, - /// None => break, - /// }; - /// // .. - /// } - /// ``` - /// Use instead: - /// ```rust,no_run - /// let y = Some(1); - /// while let Some(x) = y { - /// // .. - /// }; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub WHILE_LET_LOOP, - complexity, - "`loop { if let { ... } else break }`, which can be written as a `while let` loop" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks `for` loops over slices with an explicit counter - /// and suggests the use of `.enumerate()`. - /// - /// ### Why is this bad? - /// Using `.enumerate()` makes the intent more clear, - /// declutters the code and may be faster in some instances. - /// - /// ### Example - /// ```no_run - /// # let v = vec![1]; - /// # fn bar(bar: usize, baz: usize) {} - /// let mut i = 0; - /// for item in &v { - /// bar(i, *item); - /// i += 1; - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let v = vec![1]; - /// # fn bar(bar: usize, baz: usize) {} - /// for (i, item) in v.iter().enumerate() { bar(i, *item); } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub EXPLICIT_COUNTER_LOOP, - complexity, - "for-looping with an explicit counter when `_.enumerate()` would do" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for empty `loop` expressions. - /// - /// ### Why is this bad? - /// These busy loops burn CPU cycles without doing - /// anything. It is _almost always_ a better idea to `panic!` than to have - /// a busy loop. - /// - /// If panicking isn't possible, think of the environment and either: - /// - block on something - /// - sleep the thread for some microseconds - /// - yield or pause the thread - /// - /// For `std` targets, this can be done with - /// [`std::thread::sleep`](https://doc.rust-lang.org/std/thread/fn.sleep.html) - /// or [`std::thread::yield_now`](https://doc.rust-lang.org/std/thread/fn.yield_now.html). - /// - /// For `no_std` targets, doing this is more complicated, especially because - /// `#[panic_handler]`s can't panic. To stop/pause the thread, you will - /// probably need to invoke some target-specific intrinsic. Examples include: - /// - [`x86_64::instructions::hlt`](https://docs.rs/x86_64/0.12.2/x86_64/instructions/fn.hlt.html) - /// - [`cortex_m::asm::wfi`](https://docs.rs/cortex-m/0.6.3/cortex_m/asm/fn.wfi.html) - /// - /// ### Example - /// ```no_run - /// loop {} - /// ``` - #[clippy::version = "pre 1.29.0"] - pub EMPTY_LOOP, - suspicious, - "empty `loop {}`, which should block or sleep" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `while let` expressions on iterators. - /// - /// ### Why is this bad? - /// Readability. A simple `for` loop is shorter and conveys - /// the intent better. - /// - /// ### Example - /// ```ignore - /// while let Some(val) = iter.next() { - /// .. - /// } - /// ``` - /// - /// Use instead: - /// ```ignore - /// for val in &mut iter { - /// .. - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub WHILE_LET_ON_ITERATOR, - style, - "using a `while let` loop instead of a for loop on an iterator" -} - -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 don't need the values or keys. - /// - /// ### Example - /// ```ignore - /// for (k, _) in &map { - /// .. - /// } - /// ``` - /// - /// could be replaced by - /// - /// ```ignore - /// for k in map.keys() { - /// .. - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub FOR_KV_MAP, - style, - "looping on a map using `iter` when `keys` or `values` would do" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for loops that will always `break`, `return` or - /// `continue` an outer loop. - /// - /// ### Why is this bad? - /// This loop never loops, all it does is obfuscating the - /// code. - /// - /// ### Example - /// ```no_run - /// loop { - /// ..; - /// break; - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub NEVER_LOOP, - correctness, - "any loop that will always `break` or `return`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for loops with a range bound that is a mutable variable. - /// - /// ### Why is this bad? - /// One might think that modifying the mutable variable changes the loop bounds. It doesn't. - /// - /// ### Known problems - /// False positive when mutation is followed by a `break`, but the `break` is not immediately - /// after the mutation: - /// - /// ```no_run - /// let mut x = 5; - /// for _ in 0..x { - /// x += 1; // x is a range bound that is mutated - /// ..; // some other expression - /// break; // leaves the loop, so mutation is not an issue - /// } - /// ``` - /// - /// False positive on nested loops ([#6072](https://github.com/rust-lang/rust-clippy/issues/6072)) - /// - /// ### Example - /// ```no_run - /// let mut foo = 42; - /// for i in 0..foo { - /// foo -= 1; - /// println!("{i}"); // prints numbers from 0 to 41, not 0 to 21 - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MUT_RANGE_BOUND, - suspicious, - "for loop over a range where one of the bounds is a mutable variable" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks whether variables used within while loop condition - /// can be (and are) mutated in the body. - /// - /// ### Why is this bad? - /// If the condition is unchanged, entering the body of the loop - /// will lead to an infinite loop. - /// - /// ### Known problems - /// If the `while`-loop is in a closure, the check for mutation of the - /// condition variables in the body can cause false negatives. For example when only `Upvar` `a` is - /// in the condition and only `Upvar` `b` gets mutated in the body, the lint will not trigger. - /// - /// ### Example - /// ```no_run - /// let i = 0; - /// while i > 10 { - /// println!("let me loop forever!"); - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub WHILE_IMMUTABLE_CONDITION, - correctness, - "variables used within while expression are not mutated in the body" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for while loops comparing floating point values. - /// - /// ### Why is this bad? - /// If you increment floating point values, errors can compound, - /// so, use integers instead if possible. - /// - /// ### Known problems - /// The lint will catch all while loops comparing floating point - /// values without regarding the increment. - /// - /// ### Example - /// ```no_run - /// let mut x = 0.0; - /// while x < 42.0 { - /// x += 1.0; - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// let mut x = 0; - /// while x < 42 { - /// x += 1; - /// } - /// ``` - #[clippy::version = "1.80.0"] - pub WHILE_FLOAT, - nursery, - "while loops comparing floating point values" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks whether a for loop is being used to push a constant - /// value into a Vec. - /// - /// ### Why is this bad? - /// This kind of operation can be expressed more succinctly with - /// `vec![item; SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also - /// have better performance. - /// - /// ### Example - /// ```no_run - /// let item1 = 2; - /// let item2 = 3; - /// let mut vec: Vec = Vec::new(); - /// for _ in 0..20 { - /// vec.push(item1); - /// } - /// for _ in 0..30 { - /// vec.push(item2); - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// let item1 = 2; - /// let item2 = 3; - /// let mut vec: Vec = vec![item1; 20]; - /// vec.resize(20 + 30, item2); - /// ``` - #[clippy::version = "1.47.0"] - pub SAME_ITEM_PUSH, - style, - "the same item is pushed inside of a for loop" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks whether a for loop has a single element. - /// - /// ### Why is this bad? - /// There is no reason to have a loop of a - /// single element. - /// - /// ### Example - /// ```no_run - /// let item1 = 2; - /// for item in &[item1] { - /// println!("{}", item); - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// let item1 = 2; - /// let item = &item1; - /// println!("{}", item); - /// ``` - #[clippy::version = "1.49.0"] - pub SINGLE_ELEMENT_LOOP, - complexity, - "there is no reason to have a single element loop" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for unnecessary `if let` usage in a for loop - /// where only the `Some` or `Ok` variant of the iterator element is used. - /// - /// ### Why is this bad? - /// It is verbose and can be simplified - /// by first calling the `flatten` method on the `Iterator`. - /// - /// ### Example - /// - /// ```no_run - /// let x = vec![Some(1), Some(2), Some(3)]; - /// for n in x { - /// if let Some(n) = n { - /// println!("{}", n); - /// } - /// } - /// ``` - /// Use instead: - /// ```no_run - /// let x = vec![Some(1), Some(2), Some(3)]; - /// for n in x.into_iter().flatten() { - /// println!("{}", n); - /// } - /// ``` - #[clippy::version = "1.52.0"] - pub MANUAL_FLATTEN, - complexity, - "for loops over `Option`s or `Result`s with a single expression can be simplified" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for empty spin loops - /// - /// ### Why is this bad? - /// The loop body should have something like `thread::park()` or at least - /// `std::hint::spin_loop()` to avoid needlessly burning cycles and conserve - /// energy. Perhaps even better use an actual lock, if possible. - /// - /// ### Known problems - /// This lint doesn't currently trigger on `while let` or - /// `loop { match .. { .. } }` loops, which would be considered idiomatic in - /// combination with e.g. `AtomicBool::compare_exchange_weak`. - /// - /// ### Example - /// - /// ```ignore - /// use core::sync::atomic::{AtomicBool, Ordering}; - /// let b = AtomicBool::new(true); - /// // give a ref to `b` to another thread,wait for it to become false - /// while b.load(Ordering::Acquire) {}; - /// ``` - /// Use instead: - /// ```rust,no_run - ///# use core::sync::atomic::{AtomicBool, Ordering}; - ///# let b = AtomicBool::new(true); - /// while b.load(Ordering::Acquire) { - /// std::hint::spin_loop() - /// } - /// ``` - #[clippy::version = "1.61.0"] - pub MISSING_SPIN_LOOP, - perf, - "An empty busy waiting loop" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for manual implementations of Iterator::find - /// - /// ### Why is this bad? - /// It doesn't affect performance, but using `find` is shorter and easier to read. - /// - /// ### Example - /// - /// ```no_run - /// fn example(arr: Vec) -> Option { - /// for el in arr { - /// if el == 1 { - /// return Some(el); - /// } - /// } - /// None - /// } - /// ``` - /// Use instead: - /// ```no_run - /// fn example(arr: Vec) -> Option { - /// arr.into_iter().find(|&el| el == 1) - /// } - /// ``` - #[clippy::version = "1.64.0"] - pub MANUAL_FIND, - complexity, - "manual implementation of `Iterator::find`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for uses of the `enumerate` method where the index is unused (`_`) - /// - /// ### Why is this bad? - /// The index from `.enumerate()` is immediately dropped. - /// - /// ### Example - /// ```rust - /// let v = vec![1, 2, 3, 4]; - /// for (_, x) in v.iter().enumerate() { - /// println!("{x}"); - /// } - /// ``` - /// Use instead: - /// ```rust - /// let v = vec![1, 2, 3, 4]; - /// for x in v.iter() { - /// println!("{x}"); - /// } - /// ``` - #[clippy::version = "1.75.0"] - pub UNUSED_ENUMERATE_INDEX, - style, - "using `.enumerate()` and immediately dropping the index" -} - -declare_clippy_lint! { - /// ### What it does - /// Looks for loops that check for emptiness of a `Vec` in the condition and pop an element - /// in the body as a separate operation. - /// - /// ### Why is this bad? - /// Such loops can be written in a more idiomatic way by using a while-let loop and directly - /// pattern matching on the return value of `Vec::pop()`. - /// - /// ### Example - /// ```no_run - /// let mut numbers = vec![1, 2, 3, 4, 5]; - /// while !numbers.is_empty() { - /// let number = numbers.pop().unwrap(); - /// // use `number` - /// } - /// ``` - /// Use instead: - /// ```no_run - /// let mut numbers = vec![1, 2, 3, 4, 5]; - /// while let Some(number) = numbers.pop() { - /// // use `number` - /// } - /// ``` - #[clippy::version = "1.71.0"] - pub MANUAL_WHILE_LET_SOME, - style, - "checking for emptiness of a `Vec` in the loop condition and popping an element in the body" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for infinite loops in a function where the return type is not `!` - /// and lint accordingly. - /// - /// ### Why restrict this? - /// Making the return type `!` serves as documentation that the function does not return. - /// If the function is not intended to loop infinitely, then this lint may detect a bug. - /// - /// ### Example - /// ```no_run,ignore - /// fn run_forever() { - /// loop { - /// // do something - /// } - /// } - /// ``` - /// If infinite loops are as intended: - /// ```no_run,ignore - /// fn run_forever() -> ! { - /// loop { - /// // do something - /// } - /// } - /// ``` - /// Otherwise add a `break` or `return` condition: - /// ```no_run,ignore - /// fn run_forever() { - /// loop { - /// // do something - /// if condition { - /// break; - /// } - /// } - /// } - /// ``` - #[clippy::version = "1.76.0"] - pub INFINITE_LOOP, - restriction, - "possibly unintended infinite loop" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for manually filling a slice with a value. - /// - /// ### Why is this bad? - /// Using the `fill` method is more idiomatic and concise. - /// - /// ### Example - /// ```no_run - /// let mut some_slice = [1, 2, 3, 4, 5]; - /// for i in 0..some_slice.len() { - /// some_slice[i] = 0; - /// } - /// ``` - /// Use instead: - /// ```no_run - /// let mut some_slice = [1, 2, 3, 4, 5]; - /// some_slice.fill(0); - /// ``` - #[clippy::version = "1.86.0"] - pub MANUAL_SLICE_FILL, - style, - "manually filling a slice with a value" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of a character position yielded by `.chars().enumerate()` in a context where a **byte index** is expected, - /// such as an argument to a specific `str` method or indexing into a `str` or `String`. - /// - /// ### Why is this bad? - /// A character (more specifically, a Unicode scalar value) that is yielded by `str::chars` can take up multiple bytes, - /// so a character position does not necessarily have the same byte index at which the character is stored. - /// Thus, using the character position where a byte index is expected can unexpectedly return wrong values - /// or panic when the string consists of multibyte characters. - /// - /// For example, the character `a` in `äa` is stored at byte index 2 but has the character position 1. - /// Using the character position 1 to index into the string will lead to a panic as it is in the middle of the first character. - /// - /// Instead of `.chars().enumerate()`, the correct iterator to use is `.char_indices()`, which yields byte indices. - /// - /// This pattern is technically fine if the strings are known to only use the ASCII subset, - /// though in those cases it would be better to use `bytes()` directly to make the intent clearer, - /// but there is also no downside to just using `.char_indices()` directly and supporting non-ASCII strings. - /// - /// You may also want to read the [chapter on strings in the Rust Book](https://doc.rust-lang.org/book/ch08-02-strings.html) - /// which goes into this in more detail. - /// - /// ### Example - /// ```no_run - /// # let s = "..."; - /// for (idx, c) in s.chars().enumerate() { - /// let _ = s[idx..]; // ⚠️ Panics for strings consisting of multibyte characters - /// } - /// ``` - /// Use instead: - /// ```no_run - /// # let s = "..."; - /// for (idx, c) in s.char_indices() { - /// let _ = s[idx..]; - /// } - /// ``` - #[clippy::version = "1.88.0"] - pub CHAR_INDICES_AS_BYTE_INDICES, - correctness, - "using the character position yielded by `.chars().enumerate()` in a context where a byte index is expected" -} - -pub struct Loops { - msrv: Msrv, - enforce_iter_loop_reborrow: bool, -} -impl Loops { - pub fn new(conf: &'static Conf) -> Self { - Self { - msrv: conf.msrv, - enforce_iter_loop_reborrow: conf.enforce_iter_loop_reborrow, - } - } -} - -impl_lint_pass!(Loops => [ - MANUAL_MEMCPY, - MANUAL_FLATTEN, - NEEDLESS_RANGE_LOOP, - EXPLICIT_ITER_LOOP, - EXPLICIT_INTO_ITER_LOOP, - ITER_NEXT_LOOP, - WHILE_LET_LOOP, - EXPLICIT_COUNTER_LOOP, - EMPTY_LOOP, - WHILE_LET_ON_ITERATOR, - FOR_KV_MAP, - NEVER_LOOP, - MUT_RANGE_BOUND, - WHILE_IMMUTABLE_CONDITION, - WHILE_FLOAT, - SAME_ITEM_PUSH, - SINGLE_ELEMENT_LOOP, - MISSING_SPIN_LOOP, - MANUAL_FIND, - MANUAL_WHILE_LET_SOME, - UNUSED_ENUMERATE_INDEX, - INFINITE_LOOP, - MANUAL_SLICE_FILL, - CHAR_INDICES_AS_BYTE_INDICES, -]); - -impl<'tcx> LateLintPass<'tcx> for Loops { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let for_loop = higher::ForLoop::hir(expr); - if let Some(higher::ForLoop { - pat, - arg, - body, - loop_id, - span, - label, - }) = for_loop - { - // we don't want to check expanded macros - // this check is not at the top of the function - // since higher::for_loop expressions are marked as expansions - if body.span.from_expansion() { - return; - } - self.check_for_loop(cx, pat, arg, body, expr, span, label); - if let ExprKind::Block(block, _) = body.kind { - never_loop::check(cx, block, loop_id, span, for_loop.as_ref()); - } - } - - // we don't want to check expanded macros - if expr.span.from_expansion() { - return; - } - - // check for never_loop - if let ExprKind::Loop(block, ..) = expr.kind { - never_loop::check(cx, block, expr.hir_id, expr.span, None); - } - - // check for `loop { if let {} else break }` that could be `while let` - // (also matches an explicit "match" instead of "if let") - // (even if the "match" or "if let" is used for declaration) - if let ExprKind::Loop(block, label, LoopSource::Loop, _) = expr.kind { - // also check for empty `loop {}` statements, skipping those in #[panic_handler] - empty_loop::check(cx, expr, block); - while_let_loop::check(cx, expr, block); - infinite_loop::check(cx, expr, block, label); - } - - while_let_on_iterator::check(cx, expr); - - if let Some(higher::While { condition, body, span }) = higher::While::hir(expr) { - while_immutable_condition::check(cx, condition, body); - while_float::check(cx, condition); - missing_spin_loop::check(cx, condition, body); - manual_while_let_some::check(cx, condition, body, span); - } - } -} - -impl Loops { - #[allow(clippy::too_many_arguments)] - fn check_for_loop<'tcx>( - &self, - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - expr: &'tcx Expr<'_>, - span: Span, - label: Option