diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 41099f94b044..58a644bd6114 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -577,7 +577,12 @@ define_Conf! { #[lints(inconsistent_struct_constructor)] check_inconsistent_struct_field_initializers: bool = false, /// Whether to also run the listed lints on private items. - #[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)] + #[lints( + missing_errors_doc, + missing_panics_doc, + missing_safety_doc, + unnecessary_safety_doc, + )] check_private_items: bool = false, /// The maximum cognitive complexity a function can have #[lints(cognitive_complexity)] @@ -684,7 +689,12 @@ define_Conf! { #[lints(large_futures)] future_size_threshold: u64 = 16 * 1024, /// A list of paths to types that should be treated as if they do not contain interior mutability - #[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)] + #[lints( + borrow_interior_mutable_const, + declare_interior_mutable_const, + ifs_same_cond, + mutable_key_type, + )] ignore_interior_mutability: Vec = Vec::from(["bytes::Bytes".into()]), /// Sets the scope ("crate", "file", or "module") in which duplicate inherent `impl` blocks for the same type are linted. #[lints(multiple_inherent_impl)] diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index 238465210ee2..d09336b02a1c 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -5,10 +5,13 @@ version = "0.0.1" edition = "2024" [dependencies] +annotate-snippets = { version = "0.12.10", features = ["simd"] } +anstream = "0.6.20" 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.6" opener = "0.7" rustc-literal-escaper = "0.0.7" walkdir = "2.3" diff --git a/clippy_dev/src/diag.rs b/clippy_dev/src/diag.rs new file mode 100644 index 000000000000..003d427d11e0 --- /dev/null +++ b/clippy_dev/src/diag.rs @@ -0,0 +1,153 @@ +use crate::Span; +use annotate_snippets::renderer::{DEFAULT_TERM_WIDTH, Renderer}; +use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Origin, Snippet}; +use core::panic::Location; +use std::borrow::Cow; +use std::io::Write as _; +use std::process; + +pub struct DiagCx { + out: anstream::Stdout, + renderer: Renderer, + has_err: bool, +} +impl Default for DiagCx { + fn default() -> Self { + let width = termize::dimensions().map_or(DEFAULT_TERM_WIDTH, |(w, _)| w); + Self { + out: anstream::stdout(), + renderer: Renderer::styled().term_width(width), + has_err: false, + } + } +} +impl Drop for DiagCx { + fn drop(&mut self) { + if self.has_err { + self.render(&[ + Group::with_title( + Level::ERROR + .with_name("internal error") + .primary_title("errors were found, but it was assumed none occurred"), + ), + Group::with_title(Level::NOTE.secondary_title("any produced results may be incorrect")), + ]); + process::exit(1); + } + } +} +impl DiagCx { + pub fn exit_on_err(&self) { + if self.has_err { + process::exit(1); + } + } + + #[track_caller] + pub fn exit_assume_err(&mut self) -> ! { + if !self.has_err { + self.render(&[ + Group::with_title( + Level::ERROR + .with_name("internal error") + .primary_title("errors were expected, but is was assumed one would occur"), + ), + mk_loc_group(), + ]); + } + process::exit(1); + } +} + +fn sp_to_snip(kind: AnnotationKind, sp: Span<'_>) -> Snippet<'_, Annotation<'_>> { + let line_starts = sp.file.line_starts(); + let first_line = match line_starts.binary_search(&sp.range.start) { + Ok(x) => x, + // Note: `Err(0)` isn't possible since `0` is always the first start. + Err(x) => x - 1, + }; + let start = line_starts[first_line] as usize; + let last_line = match line_starts.binary_search(&sp.range.end) { + Ok(x) => x, + Err(x) => x - 1, + }; + let end = line_starts + .get(last_line + 1) + .map_or(sp.file.contents.len(), |&x| x as usize); + Snippet::source(&sp.file.contents[start..end]) + .line_start(first_line + 1) + .path(sp.file.path.get()) + .annotation(kind.span((sp.range.start as usize - start..sp.range.end as usize - start).into())) +} + +fn mk_spanned_primary<'a>(level: Level<'a>, sp: Span<'a>, msg: impl Into>) -> Group<'a> { + level + .primary_title(msg.into()) + .element(sp_to_snip(AnnotationKind::Primary, sp)) +} + +fn mk_spanned_secondary<'a>(level: Level<'a>, sp: Span<'a>, msg: impl Into>) -> Group<'a> { + level + .secondary_title(msg.into()) + .element(sp_to_snip(AnnotationKind::Context, sp)) +} + +#[track_caller] +fn mk_loc_group() -> Group<'static> { + let loc = Location::caller(); + Level::INFO.secondary_title("error created here").element( + Origin::path(loc.file()) + .line(loc.line() as usize) + .char_column(loc.column() as usize), + ) +} + +impl DiagCx { + fn render(&mut self, groups: &[Group<'_>]) { + let mut s = self.renderer.render(groups); + s.push('\n'); + self.out.write_all(s.as_bytes()).unwrap(); + self.has_err = true; + } + + #[track_caller] + pub fn emit_spanned_err<'a>(&mut self, sp: Span<'a>, msg: impl Into>) { + self.render(&[mk_spanned_primary(Level::ERROR, sp, msg.into()), mk_loc_group()]); + } + + #[track_caller] + pub fn emit_spanless_err<'a>(&mut self, msg: impl Into>) { + self.render(&[ + Group::with_title(Level::ERROR.primary_title(msg.into())), + mk_loc_group(), + ]); + } + + #[track_caller] + pub fn emit_already_deprecated(&mut self, name: &str) { + self.emit_spanless_err(format!("lint `{name}` is already deprecated")); + } + + #[track_caller] + pub fn emit_duplicate_lint(&mut self, sp: Span<'_>, first_sp: Span<'_>) { + self.render(&[ + mk_spanned_primary(Level::ERROR, sp, "duplicate lint name declared"), + mk_spanned_secondary(Level::NOTE, first_sp, "previous declaration here"), + mk_loc_group(), + ]); + } + + #[track_caller] + pub fn emit_not_clippy_lint_name(&mut self, sp: Span<'_>) { + self.render(&[ + mk_spanned_primary(Level::ERROR, sp, "not a clippy lint name"), + Group::with_title(Level::HELP.secondary_title("add the `clippy::` tool prefix")), + mk_loc_group(), + ]); + } + + #[track_caller] + pub fn emit_unknown_lint(&mut self, name: &str) { + self.emit_spanless_err(format!("unknown lint `{name}`")); + } +} diff --git a/clippy_dev/src/edit_lints.rs b/clippy_dev/src/edit_lints.rs index 411bf530b22b..cd1ae61f88e5 100644 --- a/clippy_dev/src/edit_lints.rs +++ b/clippy_dev/src/edit_lints.rs @@ -1,16 +1,16 @@ use crate::parse::cursor::{self, Capture, Cursor}; -use crate::parse::{ActiveLint, DeprecatedLint, Lint, LintData, LintName, ParseCx, RenamedLint}; -use crate::update_lints::generate_lint_files; +use crate::parse::{ActiveLint, DeprecatedLint, Lint, LintData, LintName, ParseCx, ParsedLints, RenamedLint}; use crate::utils::{ 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 crate::{SourceFile, Span}; use core::mem; use rustc_lexer::TokenKind; use std::collections::hash_map::Entry; use std::ffi::OsString; -use std::fs; use std::path::Path; +use std::{fs, path}; /// Runs the `deprecate` command /// @@ -23,50 +23,63 @@ use std::path::Path; /// If a file path could not read from or written to pub fn deprecate<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, name: &'env str, reason: &'env str) { let mut data = cx.parse_lint_decls(); - let Entry::Occupied(mut lint) = data.lints.entry(name) else { - eprintln!("error: failed to find lint `{name}`"); - return; + cx.dcx.emit_unknown_lint(name); + cx.dcx.exit_assume_err(); }; - let Lint::Active(prev_lint) = mem::replace( + let prev_lint = mem::replace( lint.get_mut(), - Lint::Deprecated(DeprecatedLint { - reason, - version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), - }), - ) else { - eprintln!("error: `{name}` is already deprecated"); - return; + Lint { + name_sp: Span::new(data.deprecated_file, 0..0), + data: LintData::Deprecated(DeprecatedLint { + reason, + version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), + }), + }, + ); + let LintData::Active(prev_lint_data) = prev_lint.data else { + cx.dcx.emit_already_deprecated(name); + cx.dcx.exit_assume_err(); }; - - remove_lint_declaration(name, &prev_lint, &data, &mut FileUpdater::default()); - generate_lint_files(UpdateMode::Change, &data); + cx.dcx.exit_on_err(); + + remove_lint_declaration( + name, + prev_lint.name_sp.file, + &prev_lint_data, + &data, + &mut FileUpdater::default(), + ); + data.gen_decls(UpdateMode::Change); println!("info: `{name}` has successfully been deprecated"); println!("note: you must run `cargo uitest` to update the test results"); } pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'env str, new_name: &'env str) { let mut data = cx.parse_lint_decls(); - - update_rename_targets(&mut data, old_name, LintName::new_rustc(new_name)); - let Entry::Occupied(mut lint) = data.lints.entry(old_name) else { - eprintln!("error: failed to find lint `{old_name}`"); - return; + cx.dcx.emit_unknown_lint(old_name); + cx.dcx.exit_assume_err(); }; - let Lint::Active(prev_lint) = mem::replace( + let prev_lint = mem::replace( lint.get_mut(), - Lint::Renamed(RenamedLint { - new_name: LintName::new_rustc(new_name), - version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), - }), - ) else { - eprintln!("error: `{old_name}` is already deprecated"); - return; + Lint { + name_sp: Span::new(data.deprecated_file, 0..0), + data: LintData::Renamed(RenamedLint { + new_name: LintName::new_rustc(new_name), + version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), + }), + }, + ); + let LintData::Active(prev_lint_data) = prev_lint.data else { + cx.dcx.emit_already_deprecated(old_name); + cx.dcx.exit_assume_err(); }; + cx.dcx.exit_on_err(); + update_rename_targets(&mut data, old_name, LintName::new_rustc(new_name)); let mut updater = FileUpdater::default(); - let remove_mod = remove_lint_declaration(old_name, &prev_lint, &data, &mut updater); + let remove_mod = remove_lint_declaration(old_name, prev_lint.name_sp.file, &prev_lint_data, &data, &mut updater); let mut update_fn = uplift_update_fn(old_name, new_name, remove_mod); for e in walk_dir_no_dot_or_target(".") { let e = expect_action(e, ErrAction::Read, "."); @@ -74,7 +87,7 @@ pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam updater.update_file(e.path(), &mut update_fn); } } - generate_lint_files(UpdateMode::Change, &data); + data.gen_decls(UpdateMode::Change); println!("info: `{old_name}` has successfully been uplifted as `{new_name}`"); println!("note: you must run `cargo uitest` to update the test results"); } @@ -95,48 +108,44 @@ pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam /// * If `old_name` doesn't name an existing lint. /// * If `old_name` names a deprecated or renamed lint. pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'env str, new_name: &'env str) { - let mut updater = FileUpdater::default(); let mut data = cx.parse_lint_decls(); - - update_rename_targets(&mut data, old_name, LintName::new_clippy(new_name)); - let Entry::Occupied(mut lint) = data.lints.entry(old_name) else { - eprintln!("error: failed to find lint `{old_name}`"); - return; + cx.dcx.emit_unknown_lint(old_name); + cx.dcx.exit_assume_err(); }; - let Lint::Active(mut prev_lint) = mem::replace( + let prev_lint = mem::replace( lint.get_mut(), - Lint::Renamed(RenamedLint { - new_name: LintName::new_clippy(new_name), - version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), - }), - ) else { - eprintln!("error: `{old_name}` is already deprecated"); - return; - }; + Lint { + name_sp: Span::new(data.deprecated_file, 0..0), + data: LintData::Renamed(RenamedLint { + new_name: LintName::new_clippy(new_name), + version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()), + }), + }, + ); + if !matches!(prev_lint.data, LintData::Active(_)) { + cx.dcx.emit_already_deprecated(old_name); + } + cx.dcx.exit_on_err(); + update_rename_targets(&mut data, old_name, LintName::new_clippy(new_name)); + let mut updater = FileUpdater::default(); + let prev_file = prev_lint.name_sp.file; let mut rename_mod = false; if let Entry::Vacant(e) = data.lints.entry(new_name) { - if prev_lint.module.ends_with(old_name) - && prev_lint - .path - .file_stem() - .is_some_and(|x| x.as_encoded_bytes() == old_name.as_bytes()) + if let Some((path, file)) = prev_file.path.get().rsplit_once(path::MAIN_SEPARATOR) + && let Some(file) = file.strip_suffix(".rs") + && file == old_name { - let mut new_path = prev_lint.path.with_file_name(new_name).into_os_string(); - new_path.push(".rs"); - if try_rename_file(prev_lint.path.as_ref(), new_path.as_ref()) { + let new_path = cx + .str_buf + .alloc_display(cx.arena, format_args!("{path}{}{new_name}.rs", path::MAIN_SEPARATOR)); + if try_rename_file(prev_file.path.get(), new_path) { rename_mod = true; + prev_file.path.set(new_path); } - - prev_lint.module = cx.str_buf.with(|buf| { - buf.push_str(&prev_lint.module[..prev_lint.module.len() - old_name.len()]); - buf.push_str(new_name); - cx.arena.alloc_str(buf) - }); } - e.insert(Lint::Active(prev_lint)); - + e.insert(prev_lint); rename_test_files(old_name, new_name, &create_ignored_prefixes(old_name, &data)); } else { println!("Renamed `{old_name}` to `{new_name}`"); @@ -151,7 +160,7 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam updater.update_file(e.path(), &mut update_fn); } } - generate_lint_files(UpdateMode::Change, &data); + data.gen_decls(UpdateMode::Change); println!("Renamed `{old_name}` to `{new_name}`"); println!("All code referencing the old name has been updated"); @@ -161,22 +170,22 @@ pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_nam /// Removes a lint's declaration and test files. Returns whether the module containing the /// lint was deleted. -fn remove_lint_declaration(name: &str, lint: &ActiveLint<'_>, data: &LintData<'_>, updater: &mut FileUpdater) -> bool { - let delete_mod = if data.lints.iter().all(|(_, l)| { - if let Lint::Active(l) = l { - l.module != lint.module - } else { - true - } - }) { - delete_file_if_exists(lint.path.as_ref()) +fn remove_lint_declaration( + name: &str, + lint_file: &SourceFile<'_>, + lint_data: &ActiveLint<'_>, + data: &ParsedLints<'_>, + updater: &mut FileUpdater, +) -> bool { + let delete_mod = if data.lints.iter().all(|(_, l)| l.name_sp.file != lint_file) { + delete_file_if_exists(lint_file.path.get()) } else { - updater.update_file(&lint.path, &mut |_, src, dst| -> UpdateStatus { - let mut start = &src[..lint.declaration_range.start]; + updater.update_file(lint_file.path.get(), &mut |_, src, dst| -> UpdateStatus { + let mut start = &src[..lint_data.decl_range.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_data.decl_range.end as usize..]; if end.starts_with("\n\n") { end = &end[1..]; } @@ -195,10 +204,10 @@ fn remove_lint_declaration(name: &str, lint: &ActiveLint<'_>, data: &LintData<'_ /// /// This is needed because rustc doesn't allow a lint to be renamed to a lint that has /// also been renamed. -fn update_rename_targets<'cx>(data: &mut LintData<'cx>, old_name: &str, new_name: LintName<'cx>) { +fn update_rename_targets<'cx>(data: &mut ParsedLints<'cx>, old_name: &str, new_name: LintName<'cx>) { let old_name = LintName::new_clippy(old_name); for lint in data.lints.values_mut() { - if let Lint::Renamed(lint) = lint + if let LintData::Renamed(lint) = &mut lint.data && lint.new_name == old_name { lint.new_name = new_name; @@ -207,7 +216,7 @@ fn update_rename_targets<'cx>(data: &mut LintData<'cx>, old_name: &str, new_name } /// Creates a list of prefixes to ignore when -fn create_ignored_prefixes<'cx>(name: &str, data: &LintData<'cx>) -> Vec<&'cx str> { +fn create_ignored_prefixes<'cx>(name: &str, data: &ParsedLints<'cx>) -> Vec<&'cx str> { data.lints .keys() .copied() @@ -258,9 +267,9 @@ fn rename_test_files(old_name: &str, new_name: &str, ignored_prefixes: &[&str]) old_buf.push(name); new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]); if is_file { - try_rename_file(old_buf.as_ref(), new_buf.as_ref()); + try_rename_file(&old_buf, &new_buf); } else { - try_rename_dir(old_buf.as_ref(), new_buf.as_ref()); + try_rename_dir(&old_buf, &new_buf); } old_buf.truncate("tests/ui/".len()); new_buf.truncate("tests/ui/".len()); @@ -275,7 +284,7 @@ fn rename_test_files(old_name: &str, new_name: &str, ignored_prefixes: &[&str]) for (name, _) in &tests { old_buf.push(name); new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]); - try_rename_dir(old_buf.as_ref(), new_buf.as_ref()); + try_rename_dir(&old_buf, &new_buf); old_buf.truncate("tests/ui/".len()); new_buf.truncate("tests/ui/".len()); } @@ -291,9 +300,9 @@ fn delete_test_files(lint: &str, ignored_prefixes: &[&str]) { for &(ref name, is_file) in &tests { buf.push(name); if is_file { - delete_file_if_exists(buf.as_ref()); + delete_file_if_exists(&buf); } else { - delete_dir_if_exists(buf.as_ref()); + delete_dir_if_exists(&buf); } buf.truncate("tests/ui/".len()); } @@ -305,7 +314,7 @@ fn delete_test_files(lint: &str, ignored_prefixes: &[&str]) { collect_ui_toml_test_names(lint, ignored_prefixes, &mut tests); for (name, _) in &tests { buf.push(name); - delete_dir_if_exists(buf.as_ref()); + delete_dir_if_exists(&buf); buf.truncate("tests/ui/".len()); } } @@ -338,11 +347,9 @@ fn uplift_update_fn<'a>( let mut copy_pos = 0u32; let mut changed = false; let mut cursor = Cursor::new(src); - while let Some(ident) = cursor.find_any_ident() { + while let Some(ident) = cursor.find_capture_ident() { match cursor.get_text(ident) { - "mod" - if remove_mod && cursor.match_all(&[cursor::Pat::Ident(old_name), cursor::Pat::Semi], &mut []) => - { + "mod" if remove_mod && cursor.eat_ident(old_name) && cursor.eat_semi() => { dst.push_str(&src[copy_pos as usize..ident.pos as usize]); dst.push_str(new_name); copy_pos = cursor.pos(); @@ -351,7 +358,7 @@ fn uplift_update_fn<'a>( } changed = true; }, - "clippy" if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::Ident(old_name)], &mut []) => { + "clippy" if cursor.eat_double_colon() && cursor.eat_ident(old_name) => { dst.push_str(&src[copy_pos as usize..ident.pos as usize]); dst.push_str(new_name); copy_pos = cursor.pos(); @@ -390,7 +397,9 @@ fn rename_update_fn<'a>( match text { // clippy::line_name or clippy::lint-name "clippy" => { - if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures) + if cursor + .match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures) + .is_ok() && cursor.get_text(captures[0]) == old_name { dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); @@ -401,8 +410,11 @@ fn rename_update_fn<'a>( }, // mod lint_name "mod" => { - if rename_mod && let Some(pos) = cursor.match_ident(old_name) { - dst.push_str(&src[copy_pos as usize..pos as usize]); + if rename_mod + && let Some(mod_name) = cursor.capture_ident() + && cursor.get_text(mod_name) == old_name + { + dst.push_str(&src[copy_pos as usize..mod_name.pos as usize]); dst.push_str(new_name); copy_pos = cursor.pos(); changed = true; @@ -411,7 +423,7 @@ fn rename_update_fn<'a>( // lint_name:: name if rename_mod && name == old_name => { let name_end = cursor.pos(); - if cursor.match_pat(cursor::Pat::DoubleColon) { + if cursor.eat_double_colon() { dst.push_str(&src[copy_pos as usize..match_start as usize]); dst.push_str(new_name); copy_pos = name_end; @@ -450,7 +462,9 @@ fn rename_update_fn<'a>( }, // ::lint_name TokenKind::Colon - if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures) + if cursor + .match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures) + .is_ok() && cursor.get_text(captures[0]) == old_name => { dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]); diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index 781e37e6144e..77c53b11d03d 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -1,233 +1,13 @@ +use crate::generate::gen_sorted_lints_file; +use crate::new_parse_cx; use crate::utils::{ - ErrAction, FileUpdater, UpdateMode, UpdateStatus, expect_action, run_with_output, split_args_for_threads, + ErrAction, FileUpdater, UpdateMode, UpdateStatus, VecBuf, expect_action, run_with_output, split_args_for_threads, walk_dir_no_dot_or_target, }; -use itertools::Itertools; -use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize}; use std::fmt::Write; -use std::fs; -use std::io::{self, Read}; -use std::ops::ControlFlow; -use std::path::PathBuf; +use std::io::Read; use std::process::{self, Command, Stdio}; -pub enum Error { - Io(io::Error), - Parse(PathBuf, usize, String), - CheckFailed, -} - -impl From for Error { - fn from(error: io::Error) -> Self { - Self::Io(error) - } -} - -impl Error { - fn display(&self) { - match self { - Self::CheckFailed => { - eprintln!("Formatting check failed!\nRun `cargo dev fmt` to update."); - }, - Self::Io(err) => { - eprintln!("error: {err}"); - }, - Self::Parse(path, line, msg) => { - eprintln!("error parsing `{}:{line}`: {msg}", path.display()); - }, - } - } -} - -struct ClippyConf<'a> { - name: &'a str, - attrs: &'a str, - lints: Vec<&'a str>, - field: &'a str, -} - -fn offset_to_line(text: &str, offset: usize) -> usize { - match text.split('\n').try_fold((1usize, 0usize), |(line, pos), s| { - let pos = pos + s.len() + 1; - if pos > offset { - ControlFlow::Break(line) - } else { - ControlFlow::Continue((line + 1, pos)) - } - }) { - ControlFlow::Break(x) | ControlFlow::Continue((x, _)) => x, - } -} - -/// Formats the configuration list in `clippy_config/src/conf.rs` -#[expect(clippy::too_many_lines)] -fn fmt_conf(check: bool) -> Result<(), Error> { - #[derive(Clone, Copy)] - enum State { - Start, - Docs, - Pound, - OpenBracket, - Attr(u32), - Lints, - EndLints, - Field, - } - - let path = "clippy_config/src/conf.rs"; - let text = fs::read_to_string(path)?; - - let (pre, conf) = text - .split_once("define_Conf! {\n") - .expect("can't find config definition"); - let (conf, post) = conf.split_once("\n}\n").expect("can't find config definition"); - let conf_offset = pre.len() + 15; - - let mut pos = 0u32; - let mut attrs_start = 0; - let mut attrs_end = 0; - let mut field_start = 0; - let mut lints = Vec::new(); - let mut name = ""; - let mut fields = Vec::new(); - let mut state = State::Start; - - for (i, t) in tokenize(conf, FrontmatterAllowed::No) - .map(|x| { - let start = pos; - pos += x.len; - (start as usize, x) - }) - .filter(|(_, t)| !matches!(t.kind, TokenKind::Whitespace)) - { - match (state, t.kind) { - (State::Start, TokenKind::LineComment { doc_style: Some(_) }) => { - attrs_start = i; - attrs_end = i + t.len as usize; - state = State::Docs; - }, - (State::Start, TokenKind::Pound) => { - attrs_start = i; - attrs_end = i; - state = State::Pound; - }, - (State::Docs, TokenKind::LineComment { doc_style: Some(_) }) => attrs_end = i + t.len as usize, - (State::Docs, TokenKind::Pound) => state = State::Pound, - (State::Pound, TokenKind::OpenBracket) => state = State::OpenBracket, - (State::OpenBracket, TokenKind::Ident) => { - state = if conf[i..i + t.len as usize] == *"lints" { - State::Lints - } else { - State::Attr(0) - }; - }, - (State::Attr(0), TokenKind::CloseBracket) => { - attrs_end = i + 1; - state = State::Docs; - }, - (State::Attr(x), TokenKind::OpenParen | TokenKind::OpenBracket | TokenKind::OpenBrace) => { - state = State::Attr(x + 1); - }, - (State::Attr(x), TokenKind::CloseParen | TokenKind::CloseBracket | TokenKind::CloseBrace) => { - state = State::Attr(x - 1); - }, - (State::Lints, TokenKind::Ident) => lints.push(&conf[i..i + t.len as usize]), - (State::Lints, TokenKind::CloseBracket) => state = State::EndLints, - (State::EndLints | State::Docs, TokenKind::Ident) => { - field_start = i; - name = &conf[i..i + t.len as usize]; - state = State::Field; - }, - (State::Field, TokenKind::LineComment { doc_style: Some(_) }) => { - #[expect(clippy::drain_collect)] - fields.push(ClippyConf { - name, - attrs: &conf[attrs_start..attrs_end], - lints: lints.drain(..).collect(), - field: conf[field_start..i].trim_end(), - }); - attrs_start = i; - attrs_end = i + t.len as usize; - state = State::Docs; - }, - (State::Field, TokenKind::Pound) => { - #[expect(clippy::drain_collect)] - fields.push(ClippyConf { - name, - attrs: &conf[attrs_start..attrs_end], - lints: lints.drain(..).collect(), - field: conf[field_start..i].trim_end(), - }); - attrs_start = i; - attrs_end = i; - state = State::Pound; - }, - (State::Field | State::Attr(_), _) - | (State::Lints, TokenKind::Comma | TokenKind::OpenParen | TokenKind::CloseParen) => {}, - _ => { - return Err(Error::Parse( - PathBuf::from(path), - offset_to_line(&text, conf_offset + i), - format!("unexpected token `{}`", &conf[i..i + t.len as usize]), - )); - }, - } - } - - if !matches!(state, State::Field) { - return Err(Error::Parse( - PathBuf::from(path), - offset_to_line(&text, conf_offset + conf.len()), - "incomplete field".into(), - )); - } - fields.push(ClippyConf { - name, - attrs: &conf[attrs_start..attrs_end], - lints, - field: conf[field_start..].trim_end(), - }); - - for field in &mut fields { - field.lints.sort_unstable(); - } - fields.sort_by_key(|x| x.name); - - let new_text = format!( - "{pre}define_Conf! {{\n{}}}\n{post}", - fields.iter().format_with("", |field, f| { - if field.lints.is_empty() { - f(&format_args!(" {}\n {}\n", field.attrs, field.field)) - } else if field.lints.iter().map(|x| x.len() + 2).sum::() < 120 - 14 { - f(&format_args!( - " {}\n #[lints({})]\n {}\n", - field.attrs, - field.lints.iter().join(", "), - field.field, - )) - } else { - f(&format_args!( - " {}\n #[lints({}\n )]\n {}\n", - field.attrs, - field - .lints - .iter() - .format_with("", |x, f| f(&format_args!("\n {x},"))), - field.field, - )) - } - }) - ); - - if text != new_text { - if check { - return Err(Error::CheckFailed); - } - fs::write(path, new_text)?; - } - Ok(()) -} - /// Format the symbols list fn fmt_syms(update_mode: UpdateMode) { FileUpdater::default().update_file_checked( @@ -326,10 +106,45 @@ fn run_rustfmt(update_mode: UpdateMode) { // the "main" function of cargo dev fmt pub fn run(update_mode: UpdateMode) { - run_rustfmt(update_mode); fmt_syms(update_mode); - if let Err(e) = fmt_conf(update_mode.is_check()) { - e.display(); - process::exit(1); - } + new_parse_cx(|cx| { + let mut lint_data = cx.parse_lint_decls(); + let mut conf_data = cx.parse_conf_mac(); + cx.dcx.exit_on_err(); + + let mut updater = FileUpdater::default(); + + #[expect(clippy::mutable_key_type)] + let mut lints = lint_data.mk_file_to_lint_decl_map(); + let mut ranges = VecBuf::with_capacity(256); + for passes in lint_data.iter_passes_by_file_mut() { + let file = passes[0].decl_sp.file; + let mut lints = lints.remove(file); + let lints = lints.as_deref_mut().unwrap_or_default(); + updater.update_loaded_file_checked("cargo dev fmt", update_mode, file, &mut |_, src, dst| { + gen_sorted_lints_file(src, dst, lints, passes, &mut ranges); + UpdateStatus::from_changed(src != dst) + }); + } + for (&file, lints) in &mut lints { + updater.update_loaded_file_checked("cargo dev fmt", update_mode, file, &mut |_, src, dst| { + gen_sorted_lints_file(src, dst, lints, &mut [], &mut ranges); + UpdateStatus::from_changed(src != dst) + }); + } + + updater.update_loaded_file_checked( + "cargo dev fmt", + update_mode, + conf_data.decl_sp.file, + &mut |_, src, dst| { + dst.push_str(&src[..conf_data.decl_sp.range.start as usize]); + conf_data.gen_mac(src, dst); + dst.push_str(&src[conf_data.decl_sp.range.end as usize..]); + UpdateStatus::from_changed(src != dst) + }, + ); + }); + + run_rustfmt(update_mode); } diff --git a/clippy_dev/src/generate.rs b/clippy_dev/src/generate.rs new file mode 100644 index 000000000000..3f42fcfbc4ae --- /dev/null +++ b/clippy_dev/src/generate.rs @@ -0,0 +1,342 @@ +use crate::parse::cursor::Cursor; +use crate::parse::{ConfDef, LintData, LintPass, ParsedLints}; +use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, VecBuf, slice_groups, update_text_region_fn}; +use core::range::Range; +use itertools::Itertools; +use std::collections::HashSet; +use std::fmt::Write; +use std::path::{self, Path}; + +const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ + // Use that command to update this file and do not edit by hand.\n\ + // Manual edits will be overwritten.\n\n"; + +const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html"; + +impl ParsedLints<'_> { + #[expect(clippy::too_many_lines)] + pub fn gen_decls(&self, update_mode: UpdateMode) { + let mut updater = FileUpdater::default(); + + let mut lints: Vec<_> = self.lints.iter().map(|(&x, y)| (x, y)).collect(); + lints.sort_by_key(|&(x, _)| x); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "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(lints.len()); + let mut deprecated = Vec::with_capacity(lints.len() / 8); + let mut renamed = Vec::with_capacity(lints.len() / 8); + for &(name, lint) in &lints { + match &lint.data { + LintData::Active(_) => active.push((name, lint.name_sp.file.path_as_krate_mod())), + LintData::Deprecated(lint) => deprecated.push((name, lint)), + LintData::Renamed(lint) => renamed.push((name, lint)), + } + } + active.sort_by_key(|&(_, path)| path); + + // Round to avoid updating the readme every time a lint is added/deprecated. + let lint_count = active.len() / 50 * 50; + 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, "{lint_count}").unwrap(); + }), + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "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_loaded_file_checked( + "cargo dev update_lints", + update_mode, + self.deprecated_file, + &mut |_, src, dst| { + let mut cursor = Cursor::new(src); + assert!( + cursor.find_ident("declare_with_version") && cursor.find_ident("declare_with_version"), + "error reading deprecated lints" + ); + dst.push_str(&src[..cursor.pos() as usize]); + dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n"); + for &(name, data) in &deprecated { + write!( + dst, + " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", + data.version, data.reason, + ) + .unwrap(); + } + dst.push_str( + "]}\n\n\ + #[rustfmt::skip]\n\ + declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\ + ", + ); + for &(name, data) in &renamed { + write!( + dst, + " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", + data.version, data.new_name, + ) + .unwrap(); + } + dst.push_str("]}\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + "tests/ui/deprecated.rs", + &mut |_, src, dst| { + dst.push_str(GENERATED_FILE_COMMENT); + for &(lint, _) in &deprecated { + writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); + } + dst.push_str("\nfn main() {}\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + updater.update_file_checked( + "cargo dev update_lints", + 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(); + } + } + for &(lint, _) in &renamed { + writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); + } + dst.push_str("\nfn main() {}\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + + for lints in slice_groups(&active, |(_, (head, _)), tail| { + tail.iter().take_while(|(_, (x, _))| head == x).count() + }) { + let (_, (krate, _)) = lints[0]; + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + Path::new(krate).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| { + let mut prev = ""; + for &(_, (_, mod_path)) in lints { + let module = mod_path.split_once(path::MAIN_SEPARATOR).map_or(mod_path, |(x, _)| x); + if module != prev { + writeln!(dst, "mod {module};").unwrap(); + prev = module; + } + } + }, + ), + ); + updater.update_file_checked( + "cargo dev update_lints", + update_mode, + Path::new(krate).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"); + let mut buf = String::new(); + for &(name, (_, mod_path)) in lints { + dst.push_str(" crate::"); + for part in mod_path.split(path::MAIN_SEPARATOR) { + dst.push_str(part); + dst.push_str("::"); + } + buf.clear(); + buf.push_str(name); + buf.make_ascii_uppercase(); + let _ = writeln!(dst, "{buf}_INFO,"); + } + dst.push_str("];\n"); + UpdateStatus::from_changed(src != dst) + }, + ); + } + } +} + +impl LintPass<'_> { + pub fn gen_mac(&self, dst: &mut String) { + let mut line_start = dst.len(); + dst.extend([self.mac.name(), "!("]); + let has_docs = write_comment_lines(self.docs, "\n ", dst); + let (list_indent, list_multi_end, end) = if has_docs { + dst.push_str("\n "); + line_start = dst.len() - 4; + (" ", "\n ", "]\n);") + } else { + (" ", "\n", "]);") + }; + dst.push_str(self.name); + if let Some(lt) = self.lt { + dst.extend(["<", lt, ">"]); + } + dst.push_str(" => ["); + let fmt = write_list( + self.lints.iter().copied(), + 80usize.saturating_sub(dst.len() - line_start), + list_indent, + dst, + ); + if matches!(fmt, ListFmt::MultiLine) { + dst.push_str(list_multi_end); + } + dst.push_str(end); + } +} + +impl ConfDef<'_> { + pub fn gen_mac(&mut self, src: &str, dst: &mut String) { + self.opts.sort_unstable_by_key(|o| o.name); + dst.push_str("define_Conf! {"); + for opt in &mut self.opts { + let pre_lints = src[opt.decl_range.start as usize..opt.lints_range.start as usize].trim_end(); + if !pre_lints.is_empty() { + dst.push_str("\n "); + dst.push_str(pre_lints); + } + let pre_name_text = if opt.lints.is_empty() { + "\n " + } else { + opt.lints.sort_unstable(); + dst.push_str("\n #[lints("); + let fmt = write_list(opt.lints.iter().copied(), 80 - 14, " ", dst); + match fmt { + ListFmt::SingleLine => ")]\n ", + ListFmt::MultiLine => "\n )]\n ", + } + }; + dst.push_str(pre_name_text); + dst.push_str(src[opt.lints_range.end as usize..opt.decl_range.end as usize].trim()); + dst.push(','); + } + dst.push_str("\n}"); + } +} + +fn write_comment_lines(s: &str, prefix: &str, dst: &mut String) -> bool { + let mut has_doc = false; + for line in s.split('\n') { + let line = line.trim_start(); + if !line.is_empty() { + has_doc = true; + dst.extend([prefix, line]); + } + } + has_doc +} + +#[derive(Clone, Copy)] +enum ListFmt { + SingleLine, + MultiLine, +} + +fn write_list<'a>( + items: impl Iterator + Clone, + single_line_limit: usize, + indent: &str, + dst: &mut String, +) -> ListFmt { + let len = items.clone().map(str::len).sum::(); + if len > single_line_limit { + for item in items { + dst.extend(["\n", indent, item, ","]); + } + ListFmt::MultiLine + } else { + let _ = write!(dst, "{}", items.format(", ")); + ListFmt::SingleLine + } +} + +/// Generates the contents of a lint's source file with all the lint and lint pass +/// declarations sorted. +pub fn gen_sorted_lints_file( + src: &str, + dst: &mut String, + lints: &mut [(&str, Range)], + passes: &mut [LintPass<'_>], + ranges: &mut VecBuf>, +) { + ranges.with(|ranges| { + ranges.extend(lints.iter().map(|&(_, x)| x)); + ranges.extend(passes.iter().map(|x| x.decl_sp.range)); + ranges.sort_unstable_by_key(|x| x.start); + + lints.sort_unstable_by_key(|&(x, _)| x); + passes.sort_by_key(|x| x.name); + + let mut ranges = ranges.iter(); + let pos = if let Some(range) = ranges.next() { + dst.push_str(&src[..range.start as usize]); + for &(_, range) in &*lints { + dst.push_str(&src[range.start as usize..range.end as usize]); + dst.push_str("\n\n"); + } + for pass in passes { + pass.lints.sort_unstable(); + pass.gen_mac(dst); + dst.push_str("\n\n"); + } + range.end + } else { + dst.push_str(src); + return; + }; + + let pos = ranges.fold(pos, |start, range| { + let s = &src[start as usize..range.start as usize]; + dst.push_str(if s.trim_start().is_empty() { + // Only whitespace between this and the previous item. No need to keep that. + "" + } else if src[..pos as usize].ends_with("\n\n") + && let Some(s) = s.strip_prefix("\n\n") + { + // Empty line before and after. Remove one of them. + s + } else { + // Remove only full lines unless something is in the way. + s.strip_prefix('\n').unwrap_or(s) + }); + range.end + }); + + // Since we always generate an empty line at the end, make sure to always skip it. + let s = &src[pos as usize..]; + dst.push_str(s.strip_prefix('\n').map_or(s, |s| s.strip_prefix('\n').unwrap_or(s))); + }); +} diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index cff51d34c30e..ad3aa19dcd07 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,6 +1,9 @@ #![feature( + bool_to_result, exit_status_error, + hash_set_entry, if_let_guard, + macro_metavar_expr_concat, new_range, new_range_api, os_str_slice, @@ -16,13 +19,14 @@ unused_lifetimes, unused_qualifications )] -#![allow(clippy::missing_panics_doc)] +#![allow(clippy::case_sensitive_file_extension_comparisons, clippy::missing_panics_doc)] extern crate rustc_arena; extern crate rustc_data_structures; #[expect(unused_extern_crates, reason = "required to link to rustc crates")] extern crate rustc_driver; extern crate rustc_lexer; +extern crate termize; pub mod dogfood; pub mod edit_lints; @@ -33,10 +37,12 @@ pub mod release; pub mod serve; pub mod setup; pub mod sync; -pub mod update_lints; +mod diag; +mod generate; mod parse; mod utils; +pub use self::diag::DiagCx; pub use self::parse::{ParseCx, new_parse_cx}; -pub use self::utils::{ClippyInfo, UpdateMode}; +pub use self::utils::{ClippyInfo, SourceFile, Span, UpdateMode}; diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 8dc2290df8e4..9e0f2fa14cb0 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -5,7 +5,6 @@ use clap::{Args, Parser, Subcommand}; use clippy_dev::{ ClippyInfo, UpdateMode, dogfood, edit_lints, fmt, lint, new_lint, new_parse_cx, release, serve, setup, sync, - update_lints, }; use std::env; @@ -27,7 +26,11 @@ fn main() { allow_no_vcs, } => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs), DevCommand::Fmt { check } => fmt::run(UpdateMode::from_check(check)), - DevCommand::UpdateLints { check } => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::from_check(check))), + DevCommand::UpdateLints { check } => new_parse_cx(|cx| { + let data = cx.parse_lint_decls(); + cx.dcx.exit_on_err(); + data.gen_decls(UpdateMode::from_check(check)); + }), DevCommand::NewLint { pass, name, @@ -35,7 +38,11 @@ fn main() { r#type, msrv, } => match new_lint::create(clippy.version, pass, &name, &category, r#type.as_deref(), msrv) { - Ok(()) => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::Change)), + Ok(()) => new_parse_cx(|cx| { + let data = cx.parse_lint_decls(); + cx.dcx.exit_on_err(); + data.gen_decls(UpdateMode::Change); + }), Err(e) => eprintln!("Unable to create lint: {e}"), }, DevCommand::Setup(SetupCommand { subcommand }) => match subcommand { diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 2abe471bed2b..04153c9679d3 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -524,12 +524,18 @@ fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) { let mut decl_end = None; let mut cursor = Cursor::new(contents); let mut captures = [Capture::EMPTY]; - while let Some(name) = cursor.find_any_ident() { + while let Some(name) = cursor.find_capture_ident() { match cursor.get_text(name) { - "declare_clippy_lint" if cursor.match_all(&[Bang, OpenBrace], &mut []) && cursor.find_pat(CloseBrace) => { + "declare_clippy_lint" + if cursor.match_all(&[Bang, OpenBrace], &mut []).is_ok() && cursor.find_close_brace() => + { decl_end = Some(cursor.pos()); }, - "impl" if cursor.match_all(&[Lt, Lifetime, Gt, CaptureIdent], &mut captures) => { + "impl" + if cursor + .match_all(&[Lt, Lifetime, Gt, CaptureIdent], &mut captures) + .is_ok() => + { match cursor.get_text(captures[0]) { "LateLintPass" => context = Some("LateContext"), "EarlyLintPass" => context = Some("EarlyContext"), diff --git a/clippy_dev/src/parse.rs b/clippy_dev/src/parse.rs index ffb50784a9a7..9692c6a7d2ba 100644 --- a/clippy_dev/src/parse.rs +++ b/clippy_dev/src/parse.rs @@ -1,87 +1,37 @@ pub mod cursor; -use self::cursor::{Capture, Cursor}; -use crate::utils::{ErrAction, File, Scoped, expect_action, walk_dir_no_dot_or_target}; -use core::fmt::{self, Display, Write as _}; +use self::cursor::{Capture, Cursor, IdentPat}; +use crate::utils::{ErrAction, Scoped, StrBuf, VecBuf, expect_action, slice_groups_mut, walk_dir_no_dot_or_target}; +use crate::{DiagCx, SourceFile, Span}; +use core::fmt::{self, Display}; use core::range::Range; -use rustc_arena::DroplessArena; +use rustc_arena::{DroplessArena, TypedArena}; use rustc_data_structures::fx::FxHashMap; -use std::fs; -use std::path::{self, Path, PathBuf}; -use std::str::pattern::Pattern; +use std::collections::hash_map::{Entry, VacantEntry}; +use std::{fs, path}; pub struct ParseCxImpl<'cx> { pub arena: &'cx DroplessArena, + pub source_files: &'cx TypedArena>, pub str_buf: StrBuf, + pub str_list_buf: VecBuf<&'cx str>, + pub dcx: DiagCx, } pub type ParseCx<'cx> = &'cx mut ParseCxImpl<'cx>; /// Calls the given function inside a newly created parsing context. pub fn new_parse_cx<'env, T>(f: impl for<'cx> FnOnce(&'cx mut Scoped<'cx, 'env, ParseCxImpl<'cx>>) -> T) -> T { let arena = DroplessArena::default(); + let source_files = TypedArena::default(); f(&mut Scoped::new(ParseCxImpl { arena: &arena, + source_files: &source_files, str_buf: StrBuf::with_capacity(128), + str_list_buf: VecBuf::with_capacity(128), + dcx: DiagCx::default(), })) } -/// A string used as a temporary buffer used to avoid allocating for short lived strings. -pub struct StrBuf(String); -impl StrBuf { - /// Creates a new buffer with the specified initial capacity. - pub fn with_capacity(cap: usize) -> Self { - Self(String::with_capacity(cap)) - } - - /// Allocates the result of formatting the given value onto the arena. - pub fn alloc_display<'cx>(&mut self, arena: &'cx DroplessArena, value: impl Display) -> &'cx str { - self.0.clear(); - write!(self.0, "{value}").expect("`Display` impl returned an error"); - arena.alloc_str(&self.0) - } - - /// Allocates the string onto the arena with all ascii characters converted to - /// lowercase. - pub fn alloc_ascii_lower<'cx>(&mut self, arena: &'cx DroplessArena, s: &str) -> &'cx str { - self.0.clear(); - self.0.push_str(s); - self.0.make_ascii_lowercase(); - arena.alloc_str(&self.0) - } - - /// Allocates the result of replacing all instances the pattern with the given string - /// onto the arena. - pub fn alloc_replaced<'cx>( - &mut self, - arena: &'cx DroplessArena, - s: &str, - pat: impl Pattern, - replacement: &str, - ) -> &'cx str { - let mut parts = s.split(pat); - let Some(first) = parts.next() else { - return ""; - }; - self.0.clear(); - self.0.push_str(first); - for part in parts { - self.0.push_str(replacement); - self.0.push_str(part); - } - if self.0.is_empty() { - "" - } else { - arena.alloc_str(&self.0) - } - } - - /// Performs an operation with the freshly cleared buffer. - pub fn with(&mut self, f: impl FnOnce(&mut String) -> T) -> T { - self.0.clear(); - f(&mut self.0) - } -} - #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub enum LintTool { Rustc, @@ -95,25 +45,29 @@ impl LintTool { Self::Clippy => "clippy::", } } + + pub fn from_prefix(s: &str) -> Option { + (s == "clippy").then_some(Self::Clippy) + } } #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct LintName<'cx> { - pub name: &'cx str, pub tool: LintTool, + pub name: &'cx str, } impl<'cx> LintName<'cx> { pub fn new_rustc(name: &'cx str) -> Self { Self { - name, tool: LintTool::Rustc, + name, } } pub fn new_clippy(name: &'cx str) -> Self { Self { - name, tool: LintTool::Clippy, + name, } } } @@ -126,9 +80,7 @@ impl Display for LintName<'_> { pub struct ActiveLint<'cx> { pub group: &'cx str, - pub module: &'cx str, - pub path: PathBuf, - pub declaration_range: Range, + pub decl_range: Range, } pub struct DeprecatedLint<'cx> { @@ -141,26 +93,198 @@ pub struct RenamedLint<'cx> { pub version: &'cx str, } -pub enum Lint<'cx> { +pub enum LintData<'cx> { Active(ActiveLint<'cx>), Deprecated(DeprecatedLint<'cx>), Renamed(RenamedLint<'cx>), } -pub struct LintData<'cx> { +pub struct Lint<'cx> { + pub name_sp: Span<'cx>, + pub data: LintData<'cx>, +} + +#[derive(Clone, Copy)] +pub enum LintPassMac { + Declare, + Impl, +} +impl LintPassMac { + pub fn name(self) -> &'static str { + match self { + Self::Declare => "declare_lint_pass", + Self::Impl => "impl_lint_pass", + } + } +} + +pub struct LintPass<'cx> { + /// The raw text of the documentation comments. May include leading/trailing + /// whitespace and empty lines. + pub docs: &'cx str, + pub name: &'cx str, + pub lt: Option<&'cx str>, + pub mac: LintPassMac, + pub decl_sp: Span<'cx>, + pub lints: &'cx mut [&'cx str], +} + +pub struct ParsedLints<'cx> { pub lints: FxHashMap<&'cx str, Lint<'cx>>, + pub lint_passes: Vec>, + pub deprecated_file: &'cx SourceFile<'cx>, +} +impl<'cx> ParsedLints<'cx> { + #[expect(clippy::mutable_key_type)] + pub fn mk_file_to_lint_decl_map(&self) -> FxHashMap<&'cx SourceFile<'cx>, Vec<(&'cx str, Range)>> { + #[expect(clippy::default_trait_access)] + let mut lints = FxHashMap::with_capacity_and_hasher(500, Default::default()); + for (&name, lint) in &self.lints { + if let LintData::Active(lint_data) = &lint.data { + lints + .entry(lint.name_sp.file) + .or_insert_with(|| Vec::with_capacity(8)) + .push((name, lint_data.decl_range)); + } + } + lints + } + + pub fn iter_passes_by_file_mut<'s>(&'s mut self) -> impl Iterator]> { + slice_groups_mut(&mut self.lint_passes, |head, tail| { + tail.iter().take_while(|&x| x.decl_sp.file == head.decl_sp.file).count() + }) + } + + #[track_caller] + fn get_vacant_lint<'a>( + &'a mut self, + dcx: &mut DiagCx, + name: &'cx str, + name_sp: Span<'cx>, + ) -> Option>> { + match self.lints.entry(name) { + Entry::Vacant(e) => Some(e), + Entry::Occupied(e) => { + dcx.emit_duplicate_lint(name_sp, e.get().name_sp); + None + }, + } + } +} + +pub struct ConfOpt<'cx> { + pub name: &'cx str, + pub decl_range: Range, + pub lints: &'cx mut [&'cx str], + pub lints_range: Range, +} + +pub struct ConfDef<'cx> { + pub decl_sp: Span<'cx>, + pub opts: Vec>, } impl<'cx> ParseCxImpl<'cx> { + pub fn parse_conf_mac(&mut self) -> ConfDef<'cx> { + #[allow(clippy::enum_glob_use)] + use cursor::Pat::*; + + let file = &*self.source_files.alloc(SourceFile::load(self.str_buf.alloc_collect( + self.arena, + [ + "clippy_config", + path::MAIN_SEPARATOR_STR, + "src", + path::MAIN_SEPARATOR_STR, + "conf.rs", + ], + ))); + + let mut data = ConfDef { + decl_sp: Span::new(file, 0..0), + opts: Vec::with_capacity(100), + }; + let mut cursor = Cursor::new(&file.contents); + let mut captures = [Capture::EMPTY; 1]; + + if let Err(expected) = cursor + .find_mac_call("define_Conf") + .ok_or("`define_Conf!`") + .and_then(|name| { + data.decl_sp.range.start = name.pos; + cursor.eat_open_brace().ok_or("`{`") + }) + .and_then(|()| { + cursor.eat_list(|cursor| { + let docs = cursor.capture_doc_lines(); + let mut lints: &mut [_] = &mut []; + let mut lints_range = None; + let mut started = docs.len != 0; + while let Some((attr_start, name)) = cursor.capture_opt_attr_start()? { + started = true; + if cursor.get_text(name) == "lints" { + cursor + .eat_open_paren() + .ok_or("`(`") + .and_then(|()| { + cursor.capture_list(&mut self.str_list_buf, self.arena, |cursor| { + Ok(cursor.capture_ident().map(|x| cursor.get_text(x))) + }) + }) + .and_then(|res| { + lints = res; + cursor.match_all(&[CloseParen, CloseBracket], &mut []) + })?; + lints_range = Some(attr_start..cursor.pos()); + } else { + cursor.find_close_bracket().ok_or("`]`")?; + } + } + match cursor.opt_match_all(&[CaptureIdent, Colon], &mut captures) { + Ok(true) => {}, + Ok(false) if started => return Err("an identifier"), + Ok(false) => return Ok(false), + Err(e) => return Err(e), + } + cursor.find_eq().ok_or("`=`")?; + cursor.eat_list_item(); + data.opts.push(ConfOpt { + name: cursor.get_text(captures[0]), + decl_range: docs.pos..cursor.pos(), + lints, + lints_range: lints_range.unwrap_or(captures[0].pos..captures[0].pos), + }); + Ok(true) + }) + }) + .and_then(|()| cursor.eat_close_brace().ok_or("`}`")) + { + cursor.emit_unexpected(&mut self.dcx, file, expected); + } + + data.decl_sp.range.end = cursor.pos(); + data + } + /// Finds and parses all lint declarations. - #[must_use] - pub fn parse_lint_decls(&mut self) -> LintData<'cx> { - let mut data = LintData { + pub fn parse_lint_decls(&mut self) -> ParsedLints<'cx> { + let mut data = ParsedLints { #[expect(clippy::default_trait_access)] lints: FxHashMap::with_capacity_and_hasher(1000, Default::default()), + lint_passes: Vec::with_capacity(400), + deprecated_file: self.source_files.alloc(SourceFile::load(self.str_buf.alloc_collect( + self.arena, + [ + "clippy_lints", + path::MAIN_SEPARATOR_STR, + "src", + path::MAIN_SEPARATOR_STR, + "deprecated_lints.rs", + ], + ))), }; - let mut contents = String::new(); for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") { let e = expect_action(e, ErrAction::Read, "."); @@ -179,192 +303,301 @@ impl<'cx> ParseCxImpl<'cx> { 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..) + if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") + && let Some(file_path) = e.path().to_str() + && file_path != data.deprecated_file.path.get() { - let module = if path == "lib" { - "" - } else { - let path = path - .strip_suffix("mod") - .and_then(|x| x.strip_suffix(path::MAIN_SEPARATOR)) - .unwrap_or(path); - self.str_buf - .alloc_replaced(self.arena, path, path::MAIN_SEPARATOR, "::") - }; - self.parse_clippy_lint_decls( - e.path(), - File::open_read_to_cleared_string(e.path(), &mut contents), - module, - &mut data, - ); + let file = self + .source_files + .alloc(SourceFile::load(self.arena.alloc_str(file_path))); + self.parse_lint_src_file(&mut data, file); } } } - self.read_deprecated_lints(&mut data); + self.parse_deprecated_lints(&mut data); data } /// Parse a source file looking for `declare_clippy_lint` macro invocations. - fn parse_clippy_lint_decls(&mut self, path: &Path, contents: &str, module: &'cx str, data: &mut LintData<'cx>) { + fn parse_lint_src_file(&mut self, data: &mut ParsedLints<'cx>, file: &'cx SourceFile<'cx>) { #[allow(clippy::enum_glob_use)] use cursor::Pat::*; - #[rustfmt::skip] - static DECL_TOKENS: &[cursor::Pat<'_>] = &[ - // !{ /// 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 cursor = Cursor::new(contents); - let mut captures = [Capture::EMPTY; 2]; - while let Some(start) = cursor.find_ident("declare_clippy_lint") { - if cursor.match_all(DECL_TOKENS, &mut captures) && cursor.find_pat(CloseBrace) { - assert!( - data.lints - .insert( - self.str_buf.alloc_ascii_lower(self.arena, cursor.get_text(captures[0])), - Lint::Active(ActiveLint { - group: self.arena.alloc_str(cursor.get_text(captures[1])), - module, - path: path.into(), - declaration_range: start as usize..cursor.pos() as usize, + let mut cursor = Cursor::new(&file.contents); + let mut captures = [Capture::EMPTY; 3]; + while let Some(mac_name) = cursor.find_capture_ident() { + if !cursor.eat_bang() { + continue; + } + match cursor.get_text(mac_name) { + "declare_clippy_lint" => { + #[rustfmt::skip] + static DECL_START: &[cursor::Pat] = &[ + // { /// docs + OpenBrace, AnyComments, + // #[clippy::version = "version"] + Pound, OpenBracket, Ident(IdentPat::clippy), DoubleColon, + Ident(IdentPat::version), Eq, CaptureLitStr, CloseBracket, + // pub NAME, GROUP, "desc", + Ident(IdentPat::r#pub), CaptureIdent, Comma, + AnyComments, CaptureIdent, Comma, AnyComments, LitStr, + ]; + #[rustfmt::skip] + static OPTION: &[cursor::Pat] = &[ + // @option = value + AnyComments, At, AnyIdent, Eq, Lit, + ]; + + if let Err(expected) = cursor + .match_all(DECL_START, &mut captures) + .and_then(|()| { + (!cursor.eat_comma()).ok_or(()).or_else(|()| { + cursor.eat_list(|cursor| cursor.match_all(OPTION, &mut []).map(|()| true)) + }) + }) + .and_then(|()| cursor.eat_close_brace().ok_or("`}`")) + { + cursor.emit_unexpected(&mut self.dcx, file, expected); + } else if let name = self.str_buf.alloc_ascii_lower(self.arena, cursor.get_text(captures[1])) + && let name_sp = captures[1].mk_sp(file) + && let Some(e) = data.get_vacant_lint(&mut self.dcx, name, name_sp) + { + let _ = self.parse_version(cursor.get_text(captures[0]), captures[0].mk_sp(file)); + e.insert(Lint { + name_sp, + data: LintData::Active(ActiveLint { + group: cursor.get_text(captures[2]), + decl_range: mac_name.pos..cursor.pos(), }), - ) - .is_none() - ); + }); + } + }, + mac @ ("declare_lint_pass" | "impl_lint_pass") => { + let mut has_lt = false; + let mut lints: &mut [_] = &mut []; + if let Err(expected) = cursor + .match_all(&[OpenParen, CaptureDocLines, CaptureIdent], &mut captures) + .and_then(|()| cursor.opt_match_all(&[Lt, CaptureLifetime, Gt], &mut captures[2..])) + .and_then(|res| { + has_lt = res; + cursor.match_all(&[FatArrow, OpenBracket], &mut []) + }) + .and_then(|()| { + cursor.capture_list(&mut self.str_list_buf, self.arena, |cursor| { + cursor.capture_opt_path(&mut self.str_buf, self.arena) + }) + }) + .and_then(|res| { + lints = res; + cursor.match_all(&[CloseBracket, CloseParen, Semi], &mut []) + }) + { + cursor.emit_unexpected(&mut self.dcx, file, expected); + } else { + data.lint_passes.push(LintPass { + docs: cursor.get_text(captures[0]), + name: cursor.get_text(captures[1]), + lt: has_lt.then(|| cursor.get_text(captures[2])), + mac: if matches!(mac, "declare_lint_pass") { + LintPassMac::Declare + } else { + LintPassMac::Impl + }, + decl_sp: Span::new(file, mac_name.pos..cursor.pos()), + lints, + }); + } + }, + _ => {}, } } } - fn read_deprecated_lints(&mut self, data: &mut LintData<'cx>) { + fn parse_deprecated_lints(&mut self, data: &mut ParsedLints<'cx>) { #[allow(clippy::enum_glob_use)] use cursor::Pat::*; + #[rustfmt::skip] - static DECL_TOKENS: &[cursor::Pat<'_>] = &[ + static DECL_TOKENS: &[cursor::Pat] = &[ // #[clippy::version = "version"] - Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, CaptureLitStr, CloseBracket, - // ("first", "second"), - OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, Comma, + Pound, OpenBracket, Ident(IdentPat::clippy), DoubleColon, + Ident(IdentPat::version), Eq, CaptureLitStr, CloseBracket, + // ("first", "second") + OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, ]; #[rustfmt::skip] - static DEPRECATED_TOKENS: &[cursor::Pat<'_>] = &[ + static DEPRECATED_TOKENS: &[cursor::Pat] = &[ // !{ DEPRECATED(DEPRECATED_VERSION) = [ - Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket, + Bang, OpenBrace, Ident(IdentPat::DEPRECATED), OpenParen, + Ident(IdentPat::DEPRECATED_VERSION), CloseParen, Eq, OpenBracket, ]; #[rustfmt::skip] - static RENAMED_TOKENS: &[cursor::Pat<'_>] = &[ + static RENAMED_TOKENS: &[cursor::Pat] = &[ // !{ RENAMED(RENAMED_VERSION) = [ - Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket, + Bang, OpenBrace, Ident(IdentPat::RENAMED), OpenParen, + Ident(IdentPat::RENAMED_VERSION), CloseParen, Eq, OpenBracket, ]; - let path = "clippy_lints/src/deprecated_lints.rs"; - let mut contents = String::new(); - File::open_read_to_cleared_string(path, &mut contents); - - let mut cursor = Cursor::new(&contents); + let file = data.deprecated_file; + let mut cursor = Cursor::new(&file.contents); let mut captures = [Capture::EMPTY; 3]; - // First instance is the macro definition. - assert!( - cursor.find_ident("declare_with_version").is_some(), - "error reading deprecated lints" - ); - - if cursor.find_ident("declare_with_version").is_some() && cursor.match_all(DEPRECATED_TOKENS, &mut []) { - while cursor.match_all(DECL_TOKENS, &mut captures) { - assert!( - data.lints - .insert( - self.parse_clippy_lint_name(path.as_ref(), cursor.get_text(captures[1])), - Lint::Deprecated(DeprecatedLint { - reason: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[2])), - version: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])), - }), + if let Err(expected) = cursor + .find_ident("declare_with_version") + .ok_or("`declare_with_version`") + .and_then(|()| { + cursor + .find_ident("declare_with_version") + .ok_or("`declare_with_version`") + }) + .and_then(|()| cursor.match_all(DEPRECATED_TOKENS, &mut [])) + .and_then(|()| { + cursor.eat_list(|cursor| { + let parsed = cursor.opt_match_all(DECL_TOKENS, &mut captures)?; + let name_sp = captures[1].mk_sp(file); + if parsed + && let (Some(version), Some(name), Some(reason)) = ( + self.parse_version(cursor.get_text(captures[0]), captures[0].mk_sp(file)), + self.parse_clippy_lint_name(cursor.get_text(captures[1]), name_sp), + self.parse_str_lit(cursor.get_text(captures[2]), captures[0].mk_sp(file)), ) - .is_none() - ); - } - } else { - panic!("error reading deprecated lints"); - } - - if cursor.find_ident("declare_with_version").is_some() && cursor.match_all(RENAMED_TOKENS, &mut []) { - while cursor.match_all(DECL_TOKENS, &mut captures) { - assert!( - data.lints - .insert( - self.parse_clippy_lint_name(path.as_ref(), cursor.get_text(captures[1])), - Lint::Renamed(RenamedLint { - new_name: self.parse_lint_name(path.as_ref(), cursor.get_text(captures[2])), - version: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])), - }), + && let Some(e) = data.get_vacant_lint(&mut self.dcx, name, name_sp) + { + e.insert(Lint { + name_sp, + data: LintData::Deprecated(DeprecatedLint { reason, version }), + }); + } + Ok(parsed) + }) + }) + .and_then(|()| { + cursor + .find_ident("declare_with_version") + .ok_or("`declare_with_version`") + }) + .and_then(|()| cursor.match_all(RENAMED_TOKENS, &mut [])) + .and_then(|()| { + cursor.eat_list(|cursor| { + let parsed = cursor.opt_match_all(DECL_TOKENS, &mut captures)?; + let name_sp = captures[1].mk_sp(file); + if parsed + && let (Some(version), Some(name), Some(new_name)) = ( + self.parse_version(cursor.get_text(captures[0]), captures[0].mk_sp(file)), + self.parse_clippy_lint_name(cursor.get_text(captures[1]), name_sp), + self.parse_lint_name(cursor.get_text(captures[2]), captures[0].mk_sp(file)), ) - .is_none() - ); - } - } else { - panic!("error reading renamed lints"); + && let Some(e) = data.get_vacant_lint(&mut self.dcx, name, name_sp) + { + e.insert(Lint { + name_sp, + data: LintData::Renamed(RenamedLint { new_name, version }), + }); + } + Ok(parsed) + }) + }) + { + cursor.emit_unexpected(&mut self.dcx, file, expected); } } - /// Removes the line splices and surrounding quotes from a string literal - fn parse_str_lit(&mut self, s: &str) -> &'cx str { - let (s, is_raw) = if let Some(s) = s.strip_prefix("r") { - (s.trim_matches('#'), true) + /// Removes the line splices and surrounding quotes from a string literal. + fn parse_str_lit(&mut self, s: &'cx str, sp: Span<'cx>) -> Option<&'cx str> { + let (s, is_raw, sp_base) = if let Some(trimmed) = s.strip_prefix("r") { + let trimmed = trimmed.trim_start_matches('#'); + #[expect(clippy::cast_possible_truncation)] + let sp_base = (s.len() - trimmed.len() + 1) as u32; + (trimmed.trim_end_matches('#'), true, sp_base) } else { - (s, false) + (s, false, 1) }; + let sp_base = sp.range.start + sp_base; let s = s .strip_prefix('"') .and_then(|s| s.strip_suffix('"')) .unwrap_or_else(|| panic!("expected quoted string, found `{s}`")); + let mut is_ok = true; if is_raw { - if s.is_empty() { "" } else { self.arena.alloc_str(s) } + rustc_literal_escaper::check_raw_str(s, |range, c| { + if c.is_err_and(|e| e.is_fatal()) { + #[expect(clippy::cast_possible_truncation)] + let range = range.start as u32 + sp_base..range.end as u32 + sp_base; + self.dcx + .emit_spanned_err(Span::new(sp.file, range), "invalid string escape sequence"); + is_ok = false; + } + }); + is_ok.then_some(s) } else { self.str_buf.with(|buf| { - rustc_literal_escaper::unescape_str(s, &mut |_, ch| { - if let Ok(ch) = ch { - buf.push(ch); - } + rustc_literal_escaper::unescape_str(s, |range, c| match c { + Ok(c) => buf.push(c), + Err(e) if e.is_fatal() => { + #[expect(clippy::cast_possible_truncation)] + let range = range.start as u32 + sp_base..range.end as u32 + sp_base; + self.dcx + .emit_spanned_err(Span::new(sp.file, range), "invalid string escape sequence"); + is_ok = false; + }, + Err(_) => {}, }); - if buf.is_empty() { "" } else { self.arena.alloc_str(buf) } + is_ok.then(|| { + if buf == s { + s + } else if buf.is_empty() { + "" + } else { + self.arena.alloc_str(buf) + } + }) }) } } - fn parse_str_single_line(&mut self, path: &Path, s: &str) -> &'cx str { - let value = self.parse_str_lit(s); - assert!( - !value.contains('\n'), - "error parsing `{}`: `{s}` should be a single line string", - path.display(), - ); - value + #[track_caller] + fn parse_lint_name(&mut self, s: &'cx str, sp: Span<'cx>) -> Option> { + let s = self.parse_str_lit(s, sp)?; + let (tool, name) = match s.split_once("::") { + Some((tool, name)) if let Some(tool) = LintTool::from_prefix(tool) => (tool, name), + Some(..) => { + self.dcx.emit_spanned_err(sp, "unknown lint tool"); + return None; + }, + None => (LintTool::Rustc, s), + }; + if name + .bytes() + .all(|c| matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')) + { + Some(LintName { tool, name }) + } else { + self.dcx.emit_spanned_err(sp, "unparsable lint name"); + None + } } - fn parse_clippy_lint_name(&mut self, path: &Path, s: &str) -> &'cx str { - match self.parse_str_single_line(path, s).strip_prefix("clippy::") { - Some(x) => x, - None => panic!( - "error parsing `{}`: `{s}` should be a string starting with `clippy::`", - path.display() - ), + #[track_caller] + fn parse_clippy_lint_name(&mut self, s: &'cx str, sp: Span<'cx>) -> Option<&'cx str> { + let name = self.parse_lint_name(s, sp)?; + if name.tool == LintTool::Clippy { + Some(name.name) + } else { + self.dcx.emit_not_clippy_lint_name(sp); + None } } - fn parse_lint_name(&mut self, path: &Path, s: &str) -> LintName<'cx> { - let s = self.parse_str_single_line(path, s); - let (name, tool) = match s.strip_prefix("clippy::") { - Some(s) => (s, LintTool::Clippy), - None => (s, LintTool::Rustc), - }; - LintName { name, tool } + #[track_caller] + fn parse_version(&mut self, s: &'cx str, sp: Span<'cx>) -> Option<&'cx str> { + let s = self.parse_str_lit(s, sp)?; + if s.bytes().all(|c| matches!(c, b'0'..=b'9' | b'.')) || matches!(s, "pre 1.29.0" | "CURRENT_RUSTC_VERSION") { + Some(s) + } else { + self.dcx.emit_spanned_err(sp, "unparsable version number"); + None + } } } diff --git a/clippy_dev/src/parse/cursor.rs b/clippy_dev/src/parse/cursor.rs index 2c142af4883a..5b27ef97da02 100644 --- a/clippy_dev/src/parse/cursor.rs +++ b/clippy_dev/src/parse/cursor.rs @@ -1,5 +1,8 @@ -use core::slice; -use rustc_lexer::{self as lex, LiteralKind, Token, TokenKind}; +use crate::utils::{StrBuf, VecBuf}; +use crate::{DiagCx, SourceFile, Span}; +use core::{ptr, slice}; +use rustc_arena::DroplessArena; +use rustc_lexer::{self as lex, DocStyle, LiteralKind, Token, TokenKind}; /// A token pattern used for searching and matching by the [`Cursor`]. /// @@ -8,29 +11,98 @@ use rustc_lexer::{self as lex, LiteralKind, Token, TokenKind}; /// `DoubleColon` will consume the first `:` and then fail to match, leaving the cursor at /// the `*`. #[derive(Clone, Copy)] -pub enum Pat<'a> { +pub enum Pat { /// Matches any number of comments and doc comments. - AnyComment, - Ident(&'a str), + AnyComments, + CaptureDocLines, CaptureIdent, - LitStr, + CaptureLifetime, CaptureLitStr, + AnyIdent, + At, Bang, - CloseBrace, CloseBracket, CloseParen, + Colon, Comma, DoubleColon, Eq, + FatArrow, + Gt, + Ident(IdentPat), Lifetime, + Lit, + LitStr, Lt, - Gt, OpenBrace, OpenBracket, OpenParen, Pound, Semi, } +impl Pat { + pub fn desc(self) -> &'static str { + match self { + Self::AnyComments => "comments", + Self::CaptureDocLines => "doc line comments", + Self::AnyIdent | Self::CaptureIdent => "an identifier", + Self::At => "`@`", + Self::Bang => "`!`", + Self::CloseBracket => "`]`", + Self::CloseParen => "`)`", + Self::Colon => "`:`", + Self::Comma => "`,`", + Self::DoubleColon => "`::`", + Self::Eq => "`=`", + Self::FatArrow => "`=>`", + Self::Gt => "`>`", + Self::Ident(x) => x.desc(), + Self::Lifetime | Self::CaptureLifetime => "a lifetime", + Self::Lit => "a literal", + Self::LitStr | Self::CaptureLitStr => "a string literal", + Self::Lt => "`<`", + Self::OpenBrace => "`{`", + Self::OpenBracket => "`[`", + Self::OpenParen => "`(`", + Self::Pound => "`#`", + Self::Semi => "`;`", + } + } +} + +macro_rules! ident_or_lit { + ($ident:ident) => { + stringify!($ident) + }; + ($_ident:ident $lit:literal) => { + $lit + }; +} +macro_rules! decl_ident_pats { + ($($ident:ident $(= $s:literal)?,)*) => { + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Clone, Copy)] + pub enum IdentPat { $($ident),* } + impl IdentPat { + pub fn as_str(self) -> &'static str { + match self { $(Self::$ident => ident_or_lit!($ident $($s)?)),* } + } + + pub fn desc(self) -> &'static str { + match self { $(Self::$ident => concat!("`", ident_or_lit!($ident $($s)?), "`")),* } + } + } + } +} +decl_ident_pats! { + DEPRECATED, + DEPRECATED_VERSION, + RENAMED, + RENAMED_VERSION, + clippy, + r#pub = "pub", + version, +} #[derive(Clone, Copy)] pub struct Capture { @@ -39,6 +111,13 @@ pub struct Capture { } impl Capture { pub const EMPTY: Self = Self { pos: 0, len: 0 }; + + pub fn mk_sp<'cx>(self, file: &'cx SourceFile<'cx>) -> Span<'cx> { + Span { + file, + range: self.pos..self.pos + self.len, + } + } } /// A unidirectional cursor over a token stream that is lexed on demand. @@ -93,12 +172,6 @@ impl<'txt> Cursor<'txt> { self.pos } - /// Gets whether the cursor has exhausted its input. - #[must_use] - pub fn at_end(&self) -> bool { - self.next_token.kind == TokenKind::Eof - } - /// Advances the cursor to the next token. If the stream is exhausted this will set /// the next token to [`TokenKind::Eof`]. pub fn step(&mut self) { @@ -107,28 +180,45 @@ impl<'txt> Cursor<'txt> { self.next_token = self.inner.advance_token(); } + #[track_caller] + pub fn emit_unexpected<'cx>(&self, dcx: &mut DiagCx, file: &'cx SourceFile<'cx>, expected: &'static str) { + let sp = Span::new(file, self.pos..self.pos + self.next_token.len); + let msg = if matches!(self.next_token.kind, TokenKind::Eof) { + "end of file" + } else { + "token" + }; + dcx.emit_spanned_err(sp, format!("unexpected {msg}, expected {expected}")); + } + /// Consumes tokens until the given pattern is either fully matched of fails to match. /// Returns whether the pattern was fully matched. /// /// For each capture made by the pattern one item will be taken from the capture /// sequence with the result placed inside. - fn match_impl(&mut self, pat: Pat<'_>, captures: &mut slice::IterMut<'_, Capture>) -> bool { + fn match_impl(&mut self, pat: Pat, captures: &mut slice::IterMut<'_, Capture>) -> bool { loop { match (pat, self.next_token.kind) { #[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/6697 (_, TokenKind::Whitespace) | ( - Pat::AnyComment, + Pat::AnyComments, TokenKind::BlockComment { terminated: true, .. } | TokenKind::LineComment { .. }, ) => self.step(), - (Pat::AnyComment, _) => return true, - (Pat::Bang, TokenKind::Bang) - | (Pat::CloseBrace, TokenKind::CloseBrace) + (Pat::AnyComments, _) => return true, + + (Pat::Ident(x), TokenKind::Ident) if x.as_str() == self.peek_text() => break, + (Pat::Lit, TokenKind::Ident) if matches!(self.peek_text(), "true" | "false") => break, + (Pat::At, TokenKind::At) + | (Pat::AnyIdent, TokenKind::Ident | TokenKind::RawIdent) + | (Pat::Bang, TokenKind::Bang) | (Pat::CloseBracket, TokenKind::CloseBracket) | (Pat::CloseParen, TokenKind::CloseParen) + | (Pat::Colon, TokenKind::Colon) | (Pat::Comma, TokenKind::Comma) | (Pat::Eq, TokenKind::Eq) | (Pat::Lifetime, TokenKind::Lifetime { .. }) + | (Pat::Lit, TokenKind::Literal { .. }) | (Pat::Lt, TokenKind::Lt) | (Pat::Gt, TokenKind::Gt) | (Pat::OpenBrace, TokenKind::OpenBrace) @@ -142,22 +232,17 @@ impl<'txt> Cursor<'txt> { kind: LiteralKind::Str { terminated: true } | LiteralKind::RawStr { .. }, .. }, - ) => { - self.step(); - return true; - }, - (Pat::Ident(x), TokenKind::Ident) if x == self.peek_text() => { + ) => break, + + (Pat::DoubleColon, TokenKind::Colon) if self.inner.as_str().starts_with(':') => { self.step(); - return true; + break; }, - (Pat::DoubleColon, TokenKind::Colon) => { + (Pat::FatArrow, TokenKind::Eq) if self.inner.as_str().starts_with('>') => { self.step(); - if !self.at_end() && matches!(self.next_token.kind, TokenKind::Colon) { - self.step(); - return true; - } - return false; + break; }, + #[rustfmt::skip] ( Pat::CaptureLitStr, @@ -168,43 +253,67 @@ impl<'txt> Cursor<'txt> { .. }, ) - | (Pat::CaptureIdent, TokenKind::Ident) => { + | (Pat::CaptureIdent, TokenKind::Ident) + | (Pat::CaptureLifetime, TokenKind::Lifetime { .. }) => { *captures.next().unwrap() = Capture { pos: self.pos, len: self.next_token.len }; self.step(); return true; }, + + ( + Pat::CaptureDocLines, + TokenKind::LineComment { + doc_style: Some(DocStyle::Outer), + }, + ) => { + let pos = self.pos; + self.eat_doc_lines(); + *captures.next().unwrap() = Capture { + pos, + len: self.pos - pos, + }; + return true; + }, + (Pat::CaptureDocLines, _) => { + *captures.next().unwrap() = Capture::EMPTY; + return true; + }, + _ => return false, } } + + self.step(); + true } - /// Consumes all tokens until the specified identifier is found and returns its - /// position. Returns `None` if the identifier could not be found. - /// - /// The cursor will be positioned immediately after the identifier, or at the end if - /// it is not. - pub fn find_ident(&mut self, ident: &str) -> Option { + /// Consumes and captures the next non-whitespace token if it's an identifier. Returns + /// `None` otherwise. + #[must_use] + pub fn capture_ident(&mut self) -> Option { loop { match self.next_token.kind { - TokenKind::Ident if self.peek_text() == ident => { - let pos = self.pos; + TokenKind::Whitespace => self.step(), + TokenKind::Ident => { + let res = Capture { + pos: self.pos, + len: self.next_token.len, + }; self.step(); - return Some(pos); + return Some(res); }, - TokenKind::Eof => return None, - _ => self.step(), + _ => return None, } } } - /// Consumes all tokens until the next identifier is found and captures it. Returns - /// `None` if no identifier could be found. - /// - /// The cursor will be positioned immediately after the identifier, or at the end if - /// it is not. - pub fn find_any_ident(&mut self) -> Option { + /// Consumes all tokens up to and including the next identifier. Returns either the + /// captured identifier or `None` if one was not found. + #[must_use] + pub fn find_capture_ident(&mut self) -> Option { loop { match self.next_token.kind { + TokenKind::Eof => return None, TokenKind::Ident => { let res = Capture { pos: self.pos, @@ -213,43 +322,168 @@ impl<'txt> Cursor<'txt> { self.step(); return Some(res); }, - TokenKind::Eof => return None, _ => self.step(), } } } - /// Consume the returns the position of the next non-whitespace token if it's an - /// identifier. Returns `None` otherwise. - pub fn match_ident(&mut self, s: &str) -> Option { + /// Finds the next call to a macro with the specified name and returns it's captured + /// name. + #[must_use] + pub fn find_mac_call(&mut self, name: &str) -> Option { + while let Some(mac) = self.find_capture_ident() { + if self.eat_bang() && self.get_text(mac) == name { + return Some(mac); + } + } + None + } + + /// Consumes and captures the text of a path without any internal whitespace. Returns + /// `Err` if the path ends with `::`, and `None` if no path component exists at the + /// current position. + /// + /// Only paths containing identifiers separated by `::` with a possible leading `::`. + /// Generic arguments and qualified paths are not considered. + pub fn capture_opt_path( + &mut self, + buf: &mut StrBuf, + arena: &'txt DroplessArena, + ) -> Result, &'static str> { + #[derive(Clone, Copy)] + enum State { + Start, + Sep, + Ident, + } + + buf.with(|buf| { + let start = self.pos; + let mut state = State::Start; + loop { + match (state, self.next_token.kind) { + (_, TokenKind::Whitespace) => self.step(), + (State::Start | State::Ident, TokenKind::Colon) if self.inner.first() == ':' => { + state = State::Sep; + buf.push_str("::"); + self.step(); + self.step(); + }, + (State::Start | State::Sep, TokenKind::Ident) => { + state = State::Ident; + buf.push_str(self.peek_text()); + self.step(); + }, + (State::Ident, _) => break, + (State::Start, _) => return Ok(None), + (State::Sep, _) => return Err(Pat::AnyIdent.desc()), + } + } + let text = self.text[start as usize..self.pos as usize].trim(); + Ok(Some(if text.len() == buf.len() { + text + } else { + arena.alloc_str(buf) + })) + }) + } + + pub fn eat_list(&mut self, mut f: impl FnMut(&mut Self) -> Result) -> Result<(), &'static str> { + while f(self)? { + if !self.eat_comma() { + break; + } + } + Ok(()) + } + + pub fn capture_list<'cx, T: Copy>( + &mut self, + buf: &mut VecBuf, + arena: &'cx DroplessArena, + mut f: impl FnMut(&mut Self) -> Result, &'static str>, + ) -> Result<&'cx mut [T], &'static str> { + buf.with(|buf| { + while let Some(x) = f(self)? { + buf.push(x); + if !self.eat_comma() { + break; + } + } + Ok(if buf.is_empty() { + &mut [] + } else { + arena.alloc_slice(buf) + }) + }) + } + + /// Consumes all doc line comments until another non-whitespace token is found. + pub fn eat_doc_lines(&mut self) { + while matches!( + self.next_token.kind, + TokenKind::Whitespace + | TokenKind::LineComment { + doc_style: Some(DocStyle::Outer) + } + ) { + self.step(); + } + } + + /// Consumes and captures all doc line comments until another non-whitespace token is + /// found. + pub fn capture_doc_lines(&mut self) -> Capture { loop { match self.next_token.kind { - TokenKind::Ident if s == self.peek_text() => { + TokenKind::Whitespace => self.step(), + TokenKind::LineComment { + doc_style: Some(DocStyle::Outer), + } => { let pos = self.pos; self.step(); - return Some(pos); + self.eat_doc_lines(); + return Capture { + pos, + len: self.pos - pos, + }; }, - TokenKind::Whitespace => self.step(), - _ => return None, + _ => return Capture { pos: self.pos, len: 0 }, } } } - /// Continually attempt to match the pattern on subsequent tokens until a match is - /// found. Returns whether the pattern was successfully matched. - /// - /// Not generally suitable for multi-token patterns or patterns that can match - /// nothing. - #[must_use] - pub fn find_pat(&mut self, pat: Pat<'_>) -> bool { - let mut capture = [].iter_mut(); - while !self.match_impl(pat, &mut capture) { - self.step(); - if self.at_end() { - return false; + /// Consumes and captures the next outer attribute. Returns `Err` is the attribute + /// could not be parsed and `None` if the next token does not start an attribute. + pub fn capture_opt_attr_start(&mut self) -> Result, &'static str> { + if !self.eat_pound() { + return Ok(None); + } + let start = self.pos - 1; + self.eat_open_bracket() + .ok_or("`[`") + .and_then(|()| self.capture_ident().ok_or("an identifier")) + .map(|name| Some((start, name))) + } + + /// Consumes everything until the end of a list item indicated by either an unwrapped + /// comma or an unmatched closing delimiter. + pub fn eat_list_item(&mut self) { + let mut depth: u32 = 0; + loop { + match self.next_token.kind { + TokenKind::OpenBrace | TokenKind::OpenBracket | TokenKind::OpenParen => depth += 1, + TokenKind::CloseBrace | TokenKind::CloseBracket | TokenKind::CloseParen if depth > 0 => depth -= 1, + TokenKind::Comma if depth > 0 => {}, + TokenKind::Eof + | TokenKind::Comma + | TokenKind::CloseBrace + | TokenKind::CloseBracket + | TokenKind::CloseParen => break, + _ => {}, } + self.step(); } - true } /// Attempts to match a sequence of patterns at the current position. Returns whether @@ -261,19 +495,103 @@ impl<'txt> Cursor<'txt> { /// unmodified. /// /// If the match fails the cursor will be positioned at the first failing token. - #[must_use] - pub fn match_all(&mut self, pats: &[Pat<'_>], captures: &mut [Capture]) -> bool { + pub fn match_all(&mut self, pats: &[Pat], captures: &mut [Capture]) -> Result<(), &'static str> { let mut captures = captures.iter_mut(); - pats.iter().all(|&p| self.match_impl(p, &mut captures)) + pats.iter() + .try_for_each(|&pat| self.match_impl(pat, &mut captures).ok_or(pat.desc())) } - /// Attempts to match a single pattern at the current position. Returns whether the - /// pattern was successfully matched. + /// Attempts to match a sequence of patterns at the current position. Returns whether + /// all patterns were successfully matched. /// - /// If the pattern attempts to capture anything this will panic. If the match fails - /// the cursor will be positioned at the first failing token. - #[must_use] - pub fn match_pat(&mut self, pat: Pat<'_>) -> bool { - self.match_impl(pat, &mut [].iter_mut()) + /// Captures will be written to the given slice in the order they're matched. If a + /// capture is matched, but there are no more capture slots this will panic. If the + /// match is completed without filling all the capture slots they will be left + /// unmodified. + /// + /// If the match fails the cursor will be positioned at the first failing token. + pub fn opt_match_all(&mut self, pats: &[Pat], captures: &mut [Capture]) -> Result { + let mut captures = captures.iter_mut(); + match pats + .iter() + .try_for_each(|p| self.match_impl(*p, &mut captures).ok_or(p)) + { + Ok(()) => Ok(true), + Err(p) if ptr::addr_eq(pats.as_ptr(), p) => Ok(false), + Err(p) => Err(p.desc()), + } + } +} + +macro_rules! mk_tk_methods { + ($( + [$desc:literal] + $name:ident(&mut $self:tt $($params:tt)*) + { $pat:pat $(if $guard:expr)? $(=> $extra:block)? } + )*) => { + #[allow(dead_code)] + impl Cursor<'_> {$( + #[doc = "Consumes the next non-whitespace token if it's "] + #[doc = $desc] + #[doc = " and returns whether the token was found."] + #[must_use] + pub fn ${concat(eat_, $name)}(&mut $self $($params)*) -> bool { + loop { + match $self.next_token.kind { + TokenKind::Whitespace => $self.step(), + $pat $(if $guard)? => { + $self.step(); + return true; + }, + _ => return false, + } + } + } + + #[doc = "Consumes all tokens up to and including "] + #[doc = $desc] + #[doc = " and returns whether the token was found."] + #[must_use] + pub fn ${concat(find_, $name)}(&mut $self $($params)*) -> bool { + loop { + match $self.next_token.kind { + TokenKind::Eof => return false, + $pat $(if $guard)? => { + $self.step(); + return true; + }, + _ => $self.step(), + } + } + } + )*} + } +} +mk_tk_methods! { + ["`!`"] + bang(&mut self) { TokenKind::Bang } + ["`}`"] + close_brace(&mut self) { TokenKind::CloseBrace } + ["`]`"] + close_bracket(&mut self) { TokenKind::CloseBracket } + ["`,`"] + comma(&mut self) { TokenKind::Comma } + ["`::`"] + double_colon(&mut self) { + TokenKind::Colon if self.inner.as_str().starts_with(':') => { self.step(); } } + ["`=`"] + eq(&mut self) { TokenKind::Eq } + ["the specified identifier"] + ident(&mut self, s: &str) { TokenKind::Ident if self.peek_text() == s } + ["`{`"] + open_brace(&mut self) { TokenKind::OpenBrace } + ["`[`"] + open_bracket(&mut self) { TokenKind::OpenBracket } + ["`(`"] + open_paren(&mut self) { TokenKind::OpenParen } + ["`#`"] + pound(&mut self) { TokenKind::Pound } + ["`;`"] + semi(&mut self) { TokenKind::Semi } } diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs deleted file mode 100644 index a4cf15058986..000000000000 --- a/clippy_dev/src/update_lints.rs +++ /dev/null @@ -1,204 +0,0 @@ -use crate::parse::cursor::Cursor; -use crate::parse::{Lint, LintData, ParseCx}; -use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn}; -use itertools::Itertools; -use std::collections::HashSet; -use std::fmt::Write; -use std::path::{self, Path}; - -const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\ - // Use that command to update this file and do not edit by hand.\n\ - // Manual edits will be overwritten.\n\n"; - -const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html"; - -/// Runs the `update_lints` command. -/// -/// This updates various generated values from the lint source code. -/// -/// `update_mode` indicates if the files should be updated or if updates should be checked for. -/// -/// # Panics -/// -/// Panics if a file path could not read from or then written to -pub fn update(cx: ParseCx<'_>, update_mode: UpdateMode) { - let data = cx.parse_lint_decls(); - generate_lint_files(update_mode, &data); -} - -#[expect(clippy::too_many_lines)] -pub fn generate_lint_files(update_mode: UpdateMode, data: &LintData<'_>) { - 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, - "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(lints.len()); - let mut deprecated = Vec::with_capacity(lints.len() / 8); - let mut renamed = Vec::with_capacity(lints.len() / 8); - for &(name, lint) in &lints { - match lint { - Lint::Active(lint) => active.push((name, lint)), - Lint::Deprecated(lint) => deprecated.push((name, lint)), - Lint::Renamed(lint) => renamed.push((name, lint)), - } - } - active.sort_by_key(|&(_, lint)| lint.module); - - // Round to avoid updating the readme every time a lint is added/deprecated. - let lint_count = active.len() / 50 * 50; - 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, "{lint_count}").unwrap(); - }), - ); - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "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 cursor = Cursor::new(src); - assert!( - cursor.find_ident("declare_with_version").is_some() - && cursor.find_ident("declare_with_version").is_some(), - "error reading deprecated lints" - ); - dst.push_str(&src[..cursor.pos() as usize]); - dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n"); - for &(name, data) in &deprecated { - write!( - dst, - " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", - data.version, data.reason, - ) - .unwrap(); - } - dst.push_str( - "]}\n\n\ - #[rustfmt::skip]\n\ - declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\ - ", - ); - for &(name, data) in &renamed { - write!( - dst, - " #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n", - data.version, data.new_name, - ) - .unwrap(); - } - dst.push_str("]}\n"); - UpdateStatus::from_changed(src != dst) - }, - ); - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - "tests/ui/deprecated.rs", - &mut |_, src, dst| { - dst.push_str(GENERATED_FILE_COMMENT); - for &(lint, _) in &deprecated { - writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); - } - dst.push_str("\nfn main() {}\n"); - UpdateStatus::from_changed(src != dst) - }, - ); - updater.update_file_checked( - "cargo dev update_lints", - 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(); - } - } - for &(lint, _) in &renamed { - writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap(); - } - dst.push_str("\nfn main() {}\n"); - UpdateStatus::from_changed(src != dst) - }, - ); - for (crate_name, lints) in active.iter().copied().into_group_map_by(|&(_, lint)| { - let Some(path::Component::Normal(name)) = lint.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 `{}`", - lint.path.display() - ); - }; - name - }) { - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - Path::new(crate_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| { - let mut prev = ""; - for &(_, lint) in &lints { - if lint.module != prev { - writeln!(dst, "mod {};", lint.module).unwrap(); - prev = lint.module; - } - } - }, - ), - ); - updater.update_file_checked( - "cargo dev update_lints", - update_mode, - Path::new(crate_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"); - let mut buf = String::new(); - for &(name, lint) in &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::{}::{buf}_INFO,", lint.module).unwrap(); - } - } - dst.push_str("];\n"); - UpdateStatus::from_changed(src != dst) - }, - ); - } -} diff --git a/clippy_dev/src/utils.rs b/clippy_dev/src/utils.rs index 52452dd86b49..58833be15f59 100644 --- a/clippy_dev/src/utils.rs +++ b/clippy_dev/src/utils.rs @@ -1,13 +1,19 @@ -use core::fmt::{self, Display}; +use core::cell::{Cell, OnceCell}; +use core::fmt::{self, Display, Write as _}; +use core::hash::{Hash, Hasher}; use core::marker::PhantomData; use core::num::NonZero; use core::ops::{Deref, DerefMut}; use core::range::Range; use core::str::FromStr; +use core::str::pattern::Pattern; +use core::{mem, ptr}; +use memchr::memchr_iter; +use rustc_arena::DroplessArena; use std::ffi::OsStr; use std::fs::{self, OpenOptions}; use std::io::{self, Read as _, Seek as _, SeekFrom, Write}; -use std::path::{Path, PathBuf}; +use std::path::{self, Path, PathBuf}; use std::process::{self, Command, Stdio}; use std::{env, thread}; use walkdir::WalkDir; @@ -95,13 +101,10 @@ impl<'a> File<'a> { } } - /// Opens and reads a file into a string, panicking of failure. + /// Opens a file for reading, 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) + pub fn open_read(path: &'a (impl AsRef + ?Sized)) -> Self { + Self::open(path, OpenOptions::new().read(true)) } /// Read the entire contents of a file to the given buffer. @@ -117,13 +120,19 @@ impl<'a> File<'a> { self.read_append_to_string(dst) } + /// Writes the entire contents of the specified buffer to the file, panicking on failure. + #[track_caller] + pub fn write(&mut self, data: &[u8]) { + expect_action(self.inner.write_all(data), ErrAction::Write, self.path); + } + /// Replaces the entire contents of a file. #[track_caller] pub fn replace_contents(&mut self, data: &[u8]) { let res = match self.inner.seek(SeekFrom::Start(0)) { - Ok(_) => match self.inner.write_all(data) { - Ok(()) => self.inner.set_len(data.len() as u64), - Err(e) => Err(e), + Ok(_) => { + self.write(data); + self.inner.set_len(data.len() as u64) }, Err(e) => Err(e), }; @@ -363,6 +372,34 @@ impl FileUpdater { } } + #[track_caller] + pub fn update_loaded_file_checked( + &mut self, + tool: &str, + mode: UpdateMode, + file: &SourceFile<'_>, + update: &mut dyn FnMut(&Path, &str, &mut String) -> UpdateStatus, + ) { + self.dst_buf.clear(); + match ( + mode, + update(file.path.get().as_ref(), &file.contents, &mut self.dst_buf), + ) { + (UpdateMode::Check, UpdateStatus::Changed) => { + eprintln!( + "the contents of `{}` are out of date\nplease run `{tool}` to update", + file.path.get(), + ); + process::exit(1); + }, + (UpdateMode::Change, UpdateStatus::Changed) => { + File::open(file.path.get(), OpenOptions::new().truncate(true).write(true)) + .write(self.dst_buf.as_bytes()); + }, + (UpdateMode::Check | UpdateMode::Change, UpdateStatus::Unchanged) => {}, + } + } + #[track_caller] fn update_file_inner(&mut self, path: &Path, update: &mut dyn FnMut(&Path, &str, &mut String) -> UpdateStatus) { let mut file = File::open(path, OpenOptions::new().read(true).write(true)); @@ -429,51 +466,59 @@ pub fn update_text_region_fn( } #[track_caller] -pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool { - match OpenOptions::new().create_new(true).write(true).open(new_name) { - Ok(file) => drop(file), - Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false, - Err(ref e) => panic_action(e, ErrAction::Create, new_name), - } - match fs::rename(old_name, new_name) { - Ok(()) => true, - Err(ref e) => { - drop(fs::remove_file(new_name)); - // `NotADirectory` happens on posix when renaming a directory to an existing file. - // Windows will ignore this and rename anyways. - if matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::NotADirectory) { - false - } else { - panic_action(e, ErrAction::Rename, old_name); - } - }, +pub fn try_rename_file(old_name: impl AsRef, new_name: impl AsRef) -> bool { + #[track_caller] + fn f(old_name: &Path, new_name: &Path) -> bool { + match OpenOptions::new().create_new(true).write(true).open(new_name) { + Ok(file) => drop(file), + Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false, + Err(ref e) => panic_action(e, ErrAction::Create, new_name), + } + match fs::rename(old_name, new_name) { + Ok(()) => true, + Err(ref e) => { + drop(fs::remove_file(new_name)); + // `NotADirectory` happens on posix when renaming a directory to an existing file. + // Windows will ignore this and rename anyways. + if matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::NotADirectory) { + false + } else { + panic_action(e, ErrAction::Rename, old_name); + } + }, + } } + f(old_name.as_ref(), new_name.as_ref()) } #[track_caller] -pub fn try_rename_dir(old_name: &Path, new_name: &Path) -> bool { - match fs::create_dir(new_name) { - Ok(()) => {}, - Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false, - Err(ref e) => panic_action(e, ErrAction::Create, new_name), - } - // Windows can't reliably rename to an empty directory. - #[cfg(windows)] - drop(fs::remove_dir(new_name)); - match fs::rename(old_name, new_name) { - Ok(()) => true, - Err(ref e) => { - // Already dropped earlier on windows. - #[cfg(not(windows))] - drop(fs::remove_dir(new_name)); - // `NotADirectory` happens on posix when renaming a file to an existing directory. - if matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::NotADirectory) { - false - } else { - panic_action(e, ErrAction::Rename, old_name); - } - }, +pub fn try_rename_dir(old_name: impl AsRef, new_name: impl AsRef) -> bool { + #[track_caller] + fn f(old_name: &Path, new_name: &Path) -> bool { + match fs::create_dir(new_name) { + Ok(()) => {}, + Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false, + Err(ref e) => panic_action(e, ErrAction::Create, new_name), + } + // Windows can't reliably rename to an empty directory. + #[cfg(windows)] + drop(fs::remove_dir(new_name)); + match fs::rename(old_name, new_name) { + Ok(()) => true, + Err(ref e) => { + // Already dropped earlier on windows. + #[cfg(not(windows))] + drop(fs::remove_dir(new_name)); + // `NotADirectory` happens on posix when renaming a file to an existing directory. + if matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::NotADirectory) { + false + } else { + panic_action(e, ErrAction::Rename, old_name); + } + }, + } } + f(old_name.as_ref(), new_name.as_ref()) } #[track_caller] @@ -575,21 +620,29 @@ pub fn split_args_for_threads( } #[track_caller] -pub fn delete_file_if_exists(path: &Path) -> bool { - match fs::remove_file(path) { - Ok(()) => true, - Err(e) if matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::IsADirectory) => false, - Err(ref e) => panic_action(e, ErrAction::Delete, path), +pub fn delete_file_if_exists(path: impl AsRef) -> bool { + #[track_caller] + fn f(path: &Path) -> bool { + match fs::remove_file(path) { + Ok(()) => true, + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::IsADirectory) => false, + Err(ref e) => panic_action(e, ErrAction::Delete, path), + } } + f(path.as_ref()) } #[track_caller] -pub fn delete_dir_if_exists(path: &Path) { - match fs::remove_dir_all(path) { - Ok(()) => {}, - Err(e) if matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::NotADirectory) => {}, - Err(ref e) => panic_action(e, ErrAction::Delete, path), +pub fn delete_dir_if_exists(path: impl AsRef) { + #[track_caller] + fn f(path: &Path) { + match fs::remove_dir_all(path) { + Ok(()) => {}, + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::NotADirectory) => {}, + Err(ref e) => panic_action(e, ErrAction::Delete, path), + } } + f(path.as_ref()); } /// Walks all items excluding top-level dot files/directories and any target directories. @@ -600,3 +653,226 @@ pub fn walk_dir_no_dot_or_target(p: impl AsRef) -> impl Iterator(slice: &[T], split_idx: impl FnMut(&T, &[T]) -> usize) -> impl Iterator { + struct I<'a, T, F> { + slice: &'a [T], + split_idx: F, + } + impl<'a, T, F: FnMut(&T, &[T]) -> usize> Iterator for I<'a, T, F> { + type Item = &'a [T]; + fn next(&mut self) -> Option { + let (head, tail) = self.slice.split_first()?; + let idx = (self.split_idx)(head, tail) + 1; + if let Some((head, tail)) = self.slice.split_at_checked(idx) { + self.slice = tail; + Some(head) + } else { + self.slice = &mut []; + None + } + } + } + I { slice, split_idx } +} + +pub fn slice_groups_mut( + slice: &mut [T], + split_idx: impl FnMut(&T, &[T]) -> usize, +) -> impl Iterator { + struct I<'a, T, F> { + slice: &'a mut [T], + split_idx: F, + } + impl<'a, T, F: FnMut(&T, &[T]) -> usize> Iterator for I<'a, T, F> { + type Item = &'a mut [T]; + fn next(&mut self) -> Option { + let (head, tail) = self.slice.split_first()?; + let idx = (self.split_idx)(head, tail) + 1; + // `mem::take` makes it so `self.slice` isn't reborrowed. + if let Some((head, tail)) = mem::take(&mut self.slice).split_at_mut_checked(idx) { + self.slice = tail; + Some(head) + } else { + self.slice = &mut []; + None + } + } + } + I { slice, split_idx } +} + +/// A string used as a temporary buffer used to avoid allocating for short lived strings. +pub struct StrBuf(String); +impl StrBuf { + /// Creates a new buffer with the specified initial capacity. + pub fn with_capacity(cap: usize) -> Self { + Self(String::with_capacity(cap)) + } + + /// Allocates the result of formatting the given value onto the arena. + pub fn alloc_display<'cx>(&mut self, arena: &'cx DroplessArena, value: impl Display) -> &'cx str { + self.0.clear(); + write!(self.0, "{value}").expect("`Display` impl returned an error"); + if self.0.is_empty() { + "" + } else { + arena.alloc_str(&self.0) + } + } + + /// Allocates the string onto the arena with all ascii characters converted to + /// lowercase. + pub fn alloc_ascii_lower<'cx>(&mut self, arena: &'cx DroplessArena, s: &str) -> &'cx str { + self.0.clear(); + self.0.push_str(s); + self.0.make_ascii_lowercase(); + if self.0.is_empty() { + "" + } else { + arena.alloc_str(&self.0) + } + } + + /// Collects all elements into the buffer and allocates that onto the arena. + pub fn alloc_collect<'cx, I>(&mut self, arena: &'cx DroplessArena, iter: I) -> &'cx str + where + I: IntoIterator, + String: Extend, + { + self.0.clear(); + self.0.extend(iter); + if self.0.is_empty() { + "" + } else { + arena.alloc_str(&self.0) + } + } + + /// Allocates the result of replacing all instances the pattern with the given string + /// onto the arena. + pub fn alloc_replaced<'cx>( + &mut self, + arena: &'cx DroplessArena, + s: &str, + pat: impl Pattern, + replacement: &str, + ) -> &'cx str { + let mut parts = s.split(pat); + let Some(first) = parts.next() else { + return ""; + }; + self.0.clear(); + self.0.push_str(first); + for part in parts { + self.0.push_str(replacement); + self.0.push_str(part); + } + if self.0.is_empty() { + "" + } else { + arena.alloc_str(&self.0) + } + } + + /// Performs an operation with the freshly cleared buffer. + pub fn with(&mut self, f: impl FnOnce(&mut String) -> T) -> T { + self.0.clear(); + f(&mut self.0) + } +} + +pub struct VecBuf(Vec); +impl VecBuf { + /// Creates a new buffer with the specified initial capacity. + pub fn with_capacity(cap: usize) -> Self { + Self(Vec::with_capacity(cap)) + } + + /// Performs an operation with the freshly cleared buffer. + pub fn with(&mut self, f: impl FnOnce(&mut Vec) -> R) -> R { + self.0.clear(); + f(&mut self.0) + } +} + +#[derive(Eq)] +pub struct SourceFile<'cx> { + // `cargo dev rename_lint` needs to be able to rename files. + pub path: Cell<&'cx str>, + pub line_starts: OnceCell>, + pub contents: String, +} +impl<'cx> SourceFile<'cx> { + #[must_use] + pub fn load(path: &'cx str) -> Self { + let mut contents = String::new(); + File::open_read(path).read_append_to_string(&mut contents); + SourceFile { + path: Cell::new(path), + line_starts: OnceCell::new(), + contents, + } + } + + pub fn line_starts(&self) -> &[u32] { + self.line_starts.get_or_init(|| { + let mut line_starts = Vec::with_capacity(self.contents.len() / 32 + 4); + line_starts.push(0); + #[expect(clippy::cast_possible_truncation)] + line_starts.extend(memchr_iter(b'\n', self.contents.as_bytes()).map(|x| x as u32 + 1)); + line_starts + }) + } + + /// Splits the file's path into the crate it's a part of and the module it implements. + /// + /// Only supports paths in the form `CRATE_NAME/src/PATH/TO/FILE.rs` using the current + /// platform's path separator. The module path returned will use the current platform's + /// path separator. + pub fn path_as_krate_mod(&self) -> (&'cx str, &'cx str) { + let path = self.path.get(); + let Some((krate, path)) = path.split_once(path::MAIN_SEPARATOR) else { + return ("", ""); + }; + let module = if let Some(path) = path.strip_prefix("src") + && let Some(path) = path.strip_prefix(path::MAIN_SEPARATOR) + && let Some(path) = path.strip_suffix(".rs") + { + if path == "lib" { + "" + } else if let Some(path) = path.strip_suffix("mod") + && let Some(path) = path.strip_suffix(path::MAIN_SEPARATOR) + { + path + } else { + path + } + } else { + "" + }; + (krate, module) + } +} +impl PartialEq> for SourceFile<'_> { + fn eq(&self, other: &SourceFile<'_>) -> bool { + // We should only be creating one source file per path. + ptr::addr_eq(self, other) + } +} +impl Hash for SourceFile<'_> { + fn hash(&self, state: &mut H) { + ptr::hash(self, state); + } +} + +#[derive(Clone, Copy)] +pub struct Span<'cx> { + pub file: &'cx SourceFile<'cx>, + pub range: Range, +} +impl<'cx> Span<'cx> { + pub fn new(file: &'cx SourceFile<'cx>, range: Range) -> Self { + Self { file, range } + } +} diff --git a/clippy_lints/src/absolute_paths.rs b/clippy_lints/src/absolute_paths.rs index 1af6d448a93c..fd515939dfb8 100644 --- a/clippy_lints/src/absolute_paths.rs +++ b/clippy_lints/src/absolute_paths.rs @@ -52,6 +52,7 @@ declare_clippy_lint! { restriction, "checks for usage of an item without a `use` statement" } + impl_lint_pass!(AbsolutePaths => [ABSOLUTE_PATHS]); pub struct AbsolutePaths { diff --git a/clippy_lints/src/almost_complete_range.rs b/clippy_lints/src/almost_complete_range.rs index 4f55968d5625..258970393023 100644 --- a/clippy_lints/src/almost_complete_range.rs +++ b/clippy_lints/src/almost_complete_range.rs @@ -28,6 +28,7 @@ declare_clippy_lint! { suspicious, "almost complete range" } + impl_lint_pass!(AlmostCompleteRange => [ALMOST_COMPLETE_RANGE]); pub struct AlmostCompleteRange { diff --git a/clippy_lints/src/approx_const.rs b/clippy_lints/src/approx_const.rs index a3710ca51655..2ea921e5d461 100644 --- a/clippy_lints/src/approx_const.rs +++ b/clippy_lints/src/approx_const.rs @@ -39,6 +39,8 @@ declare_clippy_lint! { "the approximate of a known float constant (in `std::fXX::consts`)" } +impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]); + // Tuples are of the form (constant, name, min_digits, msrv) const KNOWN_CONSTS: [(f64, &str, usize, Option); 19] = [ (f64::E, "E", 4, None), @@ -111,8 +113,6 @@ impl ApproxConstant { } } -impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]); - fn count_digits_after_dot(input: &str) -> usize { input .char_indices() diff --git a/clippy_lints/src/arc_with_non_send_sync.rs b/clippy_lints/src/arc_with_non_send_sync.rs index acfdfa65baed..e449b06199d3 100644 --- a/clippy_lints/src/arc_with_non_send_sync.rs +++ b/clippy_lints/src/arc_with_non_send_sync.rs @@ -39,6 +39,7 @@ declare_clippy_lint! { suspicious, "using `Arc` with a type that does not implement `Send` and `Sync`" } + declare_lint_pass!(ArcWithNonSendSync => [ARC_WITH_NON_SEND_SYNC]); impl<'tcx> LateLintPass<'tcx> for ArcWithNonSendSync { diff --git a/clippy_lints/src/asm_syntax.rs b/clippy_lints/src/asm_syntax.rs index 69a8eb7d94e7..3c5cf74d5a17 100644 --- a/clippy_lints/src/asm_syntax.rs +++ b/clippy_lints/src/asm_syntax.rs @@ -59,10 +59,10 @@ fn check_asm_syntax( declare_clippy_lint! { /// ### What it does - /// Checks for usage of Intel x86 assembly syntax. + /// Checks for usage of AT&T x86 assembly syntax. /// /// ### Why restrict this? - /// To enforce consistent use of AT&T x86 assembly syntax. + /// To enforce consistent use of Intel x86 assembly syntax. /// /// ### Example /// @@ -71,7 +71,7 @@ declare_clippy_lint! { /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] /// # unsafe { let ptr = "".as_ptr(); /// # use std::arch::asm; - /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr); + /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax)); /// # } /// ``` /// Use instead: @@ -80,37 +80,21 @@ declare_clippy_lint! { /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] /// # unsafe { let ptr = "".as_ptr(); /// # use std::arch::asm; - /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax)); + /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr); /// # } /// ``` #[clippy::version = "1.49.0"] - pub INLINE_ASM_X86_INTEL_SYNTAX, + pub INLINE_ASM_X86_ATT_SYNTAX, restriction, - "prefer AT&T x86 assembly syntax" -} - -declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]); - -impl EarlyLintPass for InlineAsmX86IntelSyntax { - fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - if let ExprKind::InlineAsm(inline_asm) = &expr.kind { - check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, expr.span, AsmStyle::Intel); - } - } - - fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { - if let ItemKind::GlobalAsm(inline_asm) = &item.kind { - check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, item.span, AsmStyle::Intel); - } - } + "prefer Intel x86 assembly syntax" } declare_clippy_lint! { /// ### What it does - /// Checks for usage of AT&T x86 assembly syntax. + /// Checks for usage of Intel x86 assembly syntax. /// /// ### Why restrict this? - /// To enforce consistent use of Intel x86 assembly syntax. + /// To enforce consistent use of AT&T x86 assembly syntax. /// /// ### Example /// @@ -119,7 +103,7 @@ declare_clippy_lint! { /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] /// # unsafe { let ptr = "".as_ptr(); /// # use std::arch::asm; - /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax)); + /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr); /// # } /// ``` /// Use instead: @@ -128,17 +112,33 @@ declare_clippy_lint! { /// # #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] /// # unsafe { let ptr = "".as_ptr(); /// # use std::arch::asm; - /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr); + /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax)); /// # } /// ``` #[clippy::version = "1.49.0"] - pub INLINE_ASM_X86_ATT_SYNTAX, + pub INLINE_ASM_X86_INTEL_SYNTAX, restriction, - "prefer Intel x86 assembly syntax" + "prefer AT&T x86 assembly syntax" } declare_lint_pass!(InlineAsmX86AttSyntax => [INLINE_ASM_X86_ATT_SYNTAX]); +declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]); + +impl EarlyLintPass for InlineAsmX86IntelSyntax { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if let ExprKind::InlineAsm(inline_asm) = &expr.kind { + check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, expr.span, AsmStyle::Intel); + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + if let ItemKind::GlobalAsm(inline_asm) = &item.kind { + check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, item.span, AsmStyle::Intel); + } + } +} + impl EarlyLintPass for InlineAsmX86AttSyntax { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { if let ExprKind::InlineAsm(inline_asm) = &expr.kind { diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index 4aa55e53445c..6e57d0608bed 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -33,6 +33,7 @@ declare_clippy_lint! { } impl_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]); + pub struct AssertionsOnConstants { msrv: Msrv, } diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index efce23d13a38..60bc9b2b5b85 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -53,6 +53,8 @@ declare_clippy_lint! { "assigning the result of cloning may be inefficient" } +impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]); + pub struct AssigningClones { msrv: Msrv, } @@ -63,8 +65,6 @@ impl AssigningClones { } } -impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]); - impl<'tcx> LateLintPass<'tcx> for AssigningClones { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { if let ExprKind::Assign(lhs, rhs, _) = e.kind diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index fa2951d91934..c15a378053e3 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -25,106 +25,60 @@ use utils::{is_lint_level, is_relevant_impl, is_relevant_item, is_relevant_trait declare_clippy_lint! { /// ### What it does - /// Checks for items annotated with `#[inline(always)]`, - /// unless the annotated function is empty or simply panics. - /// - /// ### Why is this bad? - /// While there are valid uses of this annotation (and once - /// you know when to use it, by all means `allow` this lint), it's a common - /// newbie-mistake to pepper one's code with it. - /// - /// As a rule of thumb, before slapping `#[inline(always)]` on a function, - /// measure if that additional function call really affects your runtime profile - /// sufficiently to make up for the increase in compile time. - /// - /// ### Known problems - /// False positives, big time. This lint is meant to be - /// deactivated by everyone doing serious performance work. This means having - /// done the measurement. - /// - /// ### Example - /// ```ignore - /// #[inline(always)] - /// fn not_quite_hot_code(..) { ... } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub INLINE_ALWAYS, - pedantic, - "use of `#[inline(always)]`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `extern crate` and `use` items annotated with - /// lint attributes. - /// - /// This lint permits lint attributes for lints emitted on the items themself. - /// For `use` items these lints are: - /// * ambiguous_glob_reexports - /// * dead_code - /// * deprecated - /// * hidden_glob_reexports - /// * unreachable_pub - /// * unused - /// * unused_braces - /// * unused_import_braces - /// * clippy::disallowed_types - /// * clippy::enum_glob_use - /// * clippy::macro_use_imports - /// * clippy::module_name_repetitions - /// * clippy::redundant_pub_crate - /// * clippy::single_component_path_imports - /// * clippy::unsafe_removed_from_name - /// * clippy::wildcard_imports + /// Checks for usage of the `#[allow]` attribute and suggests replacing it with + /// the `#[expect]` attribute (See [RFC 2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html)) /// - /// For `extern crate` items these lints are: - /// * `unused_imports` on items with `#[macro_use]` + /// This lint only warns outer attributes (`#[allow]`), as inner attributes + /// (`#![allow]`) are usually used to enable or disable lints on a global scale. /// /// ### Why is this bad? - /// Lint attributes have no effect on crate imports. Most - /// likely a `!` was forgotten. + /// `#[expect]` attributes suppress the lint emission, but emit a warning, if + /// the expectation is unfulfilled. This can be useful to be notified when the + /// lint is no longer triggered. /// /// ### Example - /// ```ignore - /// #[deny(dead_code)] - /// extern crate foo; - /// #[forbid(dead_code)] - /// use foo::bar; + /// ```rust,ignore + /// #[allow(unused_mut)] + /// fn foo() -> usize { + /// let mut a = Vec::new(); + /// a.len() + /// } /// ``` - /// /// Use instead: /// ```rust,ignore - /// #[allow(unused_imports)] - /// use foo::baz; - /// #[allow(unused_imports)] - /// #[macro_use] - /// extern crate baz; + /// #[expect(unused_mut)] + /// fn foo() -> usize { + /// let mut a = Vec::new(); + /// a.len() + /// } /// ``` - #[clippy::version = "pre 1.29.0"] - pub USELESS_ATTRIBUTE, - correctness, - "use of lint attributes on `extern crate` items" + #[clippy::version = "1.70.0"] + pub ALLOW_ATTRIBUTES, + restriction, + "`#[allow]` will not trigger if a warning isn't found. `#[expect]` triggers if there are no warnings." } declare_clippy_lint! { /// ### What it does - /// Checks for `#[deprecated]` annotations with a `since` - /// field that is not a valid semantic version. Also allows "TBD" to signal - /// future deprecation. + /// Checks for attributes that allow lints without a reason. /// - /// ### Why is this bad? - /// For checking the version of the deprecation, it must be - /// a valid semver. Failing that, the contained information is useless. + /// ### Why restrict this? + /// Justifying each `allow` helps readers understand the reasoning, + /// and may allow removing `allow` attributes if their purpose is obsolete. /// /// ### Example /// ```no_run - /// #[deprecated(since = "forever")] - /// fn something_else() { /* ... */ } + /// #![allow(clippy::some_lint)] /// ``` - #[clippy::version = "pre 1.29.0"] - pub DEPRECATED_SEMVER, - correctness, - "use of `#[deprecated(since = \"x\")]` where x is not semver" + /// + /// Use instead: + /// ```no_run + /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")] + /// ``` + #[clippy::version = "1.61.0"] + pub ALLOW_ATTRIBUTES_WITHOUT_REASON, + restriction, + "ensures that all `allow` and `expect` attributes have a reason" } declare_clippy_lint! { @@ -183,95 +137,195 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for attributes that allow lints without a reason. + /// Checks for `#[cfg_attr(feature = "cargo-clippy", ...)]` and for + /// `#[cfg(feature = "cargo-clippy")]` and suggests to replace it with + /// `#[cfg_attr(clippy, ...)]` or `#[cfg(clippy)]`. /// - /// ### Why restrict this? - /// Justifying each `allow` helps readers understand the reasoning, - /// and may allow removing `allow` attributes if their purpose is obsolete. + /// ### Why is this bad? + /// This feature has been deprecated for years and shouldn't be used anymore. /// /// ### Example /// ```no_run - /// #![allow(clippy::some_lint)] + /// #[cfg(feature = "cargo-clippy")] + /// struct Bar; /// ``` /// /// Use instead: /// ```no_run - /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")] + /// #[cfg(clippy)] + /// struct Bar; /// ``` - #[clippy::version = "1.61.0"] - pub ALLOW_ATTRIBUTES_WITHOUT_REASON, - restriction, - "ensures that all `allow` and `expect` attributes have a reason" + #[clippy::version = "1.78.0"] + pub DEPRECATED_CLIPPY_CFG_ATTR, + suspicious, + "usage of `cfg(feature = \"cargo-clippy\")` instead of `cfg(clippy)`" } declare_clippy_lint! { /// ### What it does - /// Checks for usage of the `#[allow]` attribute and suggests replacing it with - /// the `#[expect]` attribute (See [RFC 2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html)) + /// Checks for `#[deprecated]` annotations with a `since` + /// field that is not a valid semantic version. Also allows "TBD" to signal + /// future deprecation. /// - /// This lint only warns outer attributes (`#[allow]`), as inner attributes - /// (`#![allow]`) are usually used to enable or disable lints on a global scale. + /// ### Why is this bad? + /// For checking the version of the deprecation, it must be + /// a valid semver. Failing that, the contained information is useless. + /// + /// ### Example + /// ```no_run + /// #[deprecated(since = "forever")] + /// fn something_else() { /* ... */ } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub DEPRECATED_SEMVER, + correctness, + "use of `#[deprecated(since = \"x\")]` where x is not semver" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for attributes that appear two or more times. /// /// ### Why is this bad? - /// `#[expect]` attributes suppress the lint emission, but emit a warning, if - /// the expectation is unfulfilled. This can be useful to be notified when the - /// lint is no longer triggered. + /// Repeating an attribute on the same item (or globally on the same crate) + /// is unnecessary and doesn't have an effect. /// /// ### Example - /// ```rust,ignore - /// #[allow(unused_mut)] - /// fn foo() -> usize { - /// let mut a = Vec::new(); - /// a.len() - /// } + /// ```no_run + /// #[allow(dead_code)] + /// #[allow(dead_code)] + /// fn foo() {} /// ``` + /// /// Use instead: - /// ```rust,ignore - /// #[expect(unused_mut)] - /// fn foo() -> usize { - /// let mut a = Vec::new(); - /// a.len() + /// ```no_run + /// #[allow(dead_code)] + /// fn foo() {} + /// ``` + #[clippy::version = "1.79.0"] + pub DUPLICATED_ATTRIBUTES, + suspicious, + "duplicated attribute" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for ignored tests without messages. + /// + /// ### Why is this bad? + /// The reason for ignoring the test may not be obvious. + /// + /// ### Example + /// ```no_run + /// #[test] + /// #[ignore] + /// fn test() {} + /// ``` + /// Use instead: + /// ```no_run + /// #[test] + /// #[ignore = "Some good reason"] + /// fn test() {} + /// ``` + /// + /// ### Note + /// Clippy can only lint compiled code. For this lint to trigger, you must configure `cargo clippy` + /// to include test compilation, for instance, by using flags such as `--tests` or `--all-targets`. + #[clippy::version = "1.88.0"] + pub IGNORE_WITHOUT_REASON, + pedantic, + "ignored tests without messages" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for items annotated with `#[inline(always)]`, + /// unless the annotated function is empty or simply panics. + /// + /// ### Why is this bad? + /// While there are valid uses of this annotation (and once + /// you know when to use it, by all means `allow` this lint), it's a common + /// newbie-mistake to pepper one's code with it. + /// + /// As a rule of thumb, before slapping `#[inline(always)]` on a function, + /// measure if that additional function call really affects your runtime profile + /// sufficiently to make up for the increase in compile time. + /// + /// ### Known problems + /// False positives, big time. This lint is meant to be + /// deactivated by everyone doing serious performance work. This means having + /// done the measurement. + /// + /// ### Example + /// ```ignore + /// #[inline(always)] + /// fn not_quite_hot_code(..) { ... } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub INLINE_ALWAYS, + pedantic, + "use of `#[inline(always)]`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for items that have the same kind of attributes with mixed styles (inner/outer). + /// + /// ### Why is this bad? + /// Having both style of said attributes makes it more complicated to read code. + /// + /// ### Known problems + /// This lint currently has false-negatives when mixing same attributes + /// but they have different path symbols, for example: + /// ```ignore + /// #[custom_attribute] + /// pub fn foo() { + /// #![my_crate::custom_attribute] + /// } + /// ``` + /// + /// ### Example + /// ```no_run + /// #[cfg(linux)] + /// pub fn foo() { + /// #![cfg(windows)] + /// } + /// ``` + /// Use instead: + /// ```no_run + /// #[cfg(linux)] + /// #[cfg(windows)] + /// pub fn foo() { /// } /// ``` - #[clippy::version = "1.70.0"] - pub ALLOW_ATTRIBUTES, - restriction, - "`#[allow]` will not trigger if a warning isn't found. `#[expect]` triggers if there are no warnings." + #[clippy::version = "1.78.0"] + pub MIXED_ATTRIBUTES_STYLE, + style, + "item has both inner and outer attributes" } declare_clippy_lint! { /// ### What it does - /// Checks for `#[should_panic]` attributes without specifying the expected panic message. + /// Checks for `any` and `all` combinators in `cfg` with only one condition. /// /// ### Why is this bad? - /// The expected panic message should be specified to ensure that the test is actually - /// panicking with the expected message, and not another unrelated panic. + /// If there is only one condition, no need to wrap it into `any` or `all` combinators. /// /// ### Example /// ```no_run - /// fn random() -> i32 { 0 } - /// - /// #[should_panic] - /// #[test] - /// fn my_test() { - /// let _ = 1 / random(); - /// } + /// #[cfg(any(unix))] + /// pub struct Bar; /// ``` /// /// Use instead: /// ```no_run - /// fn random() -> i32 { 0 } - /// - /// #[should_panic = "attempt to divide by zero"] - /// #[test] - /// fn my_test() { - /// let _ = 1 / random(); - /// } + /// #[cfg(unix)] + /// pub struct Bar; /// ``` - #[clippy::version = "1.74.0"] - pub SHOULD_PANIC_WITHOUT_EXPECT, - pedantic, - "ensures that all `should_panic` attributes specify its expected panic message" + #[clippy::version = "1.71.0"] + pub NON_MINIMAL_CFG, + style, + "ensure that all `cfg(any())` and `cfg(all())` have more than one condition" } declare_clippy_lint! { @@ -314,52 +368,37 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for `any` and `all` combinators in `cfg` with only one condition. + /// Checks for `#[should_panic]` attributes without specifying the expected panic message. /// /// ### Why is this bad? - /// If there is only one condition, no need to wrap it into `any` or `all` combinators. + /// The expected panic message should be specified to ensure that the test is actually + /// panicking with the expected message, and not another unrelated panic. /// /// ### Example /// ```no_run - /// #[cfg(any(unix))] - /// pub struct Bar; - /// ``` - /// - /// Use instead: - /// ```no_run - /// #[cfg(unix)] - /// pub struct Bar; - /// ``` - #[clippy::version = "1.71.0"] - pub NON_MINIMAL_CFG, - style, - "ensure that all `cfg(any())` and `cfg(all())` have more than one condition" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `#[cfg_attr(feature = "cargo-clippy", ...)]` and for - /// `#[cfg(feature = "cargo-clippy")]` and suggests to replace it with - /// `#[cfg_attr(clippy, ...)]` or `#[cfg(clippy)]`. - /// - /// ### Why is this bad? - /// This feature has been deprecated for years and shouldn't be used anymore. + /// fn random() -> i32 { 0 } /// - /// ### Example - /// ```no_run - /// #[cfg(feature = "cargo-clippy")] - /// struct Bar; + /// #[should_panic] + /// #[test] + /// fn my_test() { + /// let _ = 1 / random(); + /// } /// ``` /// /// Use instead: /// ```no_run - /// #[cfg(clippy)] - /// struct Bar; + /// fn random() -> i32 { 0 } + /// + /// #[should_panic = "attempt to divide by zero"] + /// #[test] + /// fn my_test() { + /// let _ = 1 / random(); + /// } /// ``` - #[clippy::version = "1.78.0"] - pub DEPRECATED_CLIPPY_CFG_ATTR, - suspicious, - "usage of `cfg(feature = \"cargo-clippy\")` instead of `cfg(clippy)`" + #[clippy::version = "1.74.0"] + pub SHOULD_PANIC_WITHOUT_EXPECT, + pedantic, + "ensures that all `should_panic` attributes specify its expected panic message" } declare_clippy_lint! { @@ -388,105 +427,82 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for items that have the same kind of attributes with mixed styles (inner/outer). - /// - /// ### Why is this bad? - /// Having both style of said attributes makes it more complicated to read code. + /// Checks for `extern crate` and `use` items annotated with + /// lint attributes. /// - /// ### Known problems - /// This lint currently has false-negatives when mixing same attributes - /// but they have different path symbols, for example: - /// ```ignore - /// #[custom_attribute] - /// pub fn foo() { - /// #![my_crate::custom_attribute] - /// } - /// ``` + /// This lint permits lint attributes for lints emitted on the items themself. + /// For `use` items these lints are: + /// * ambiguous_glob_reexports + /// * dead_code + /// * deprecated + /// * hidden_glob_reexports + /// * unreachable_pub + /// * unused + /// * unused_braces + /// * unused_import_braces + /// * clippy::disallowed_types + /// * clippy::enum_glob_use + /// * clippy::macro_use_imports + /// * clippy::module_name_repetitions + /// * clippy::redundant_pub_crate + /// * clippy::single_component_path_imports + /// * clippy::unsafe_removed_from_name + /// * clippy::wildcard_imports /// - /// ### Example - /// ```no_run - /// #[cfg(linux)] - /// pub fn foo() { - /// #![cfg(windows)] - /// } - /// ``` - /// Use instead: - /// ```no_run - /// #[cfg(linux)] - /// #[cfg(windows)] - /// pub fn foo() { - /// } - /// ``` - #[clippy::version = "1.78.0"] - pub MIXED_ATTRIBUTES_STYLE, - style, - "item has both inner and outer attributes" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for attributes that appear two or more times. + /// For `extern crate` items these lints are: + /// * `unused_imports` on items with `#[macro_use]` /// /// ### Why is this bad? - /// Repeating an attribute on the same item (or globally on the same crate) - /// is unnecessary and doesn't have an effect. + /// Lint attributes have no effect on crate imports. Most + /// likely a `!` was forgotten. /// /// ### Example - /// ```no_run - /// #[allow(dead_code)] - /// #[allow(dead_code)] - /// fn foo() {} + /// ```ignore + /// #[deny(dead_code)] + /// extern crate foo; + /// #[forbid(dead_code)] + /// use foo::bar; /// ``` /// /// Use instead: - /// ```no_run - /// #[allow(dead_code)] - /// fn foo() {} + /// ```rust,ignore + /// #[allow(unused_imports)] + /// use foo::baz; + /// #[allow(unused_imports)] + /// #[macro_use] + /// extern crate baz; /// ``` - #[clippy::version = "1.79.0"] - pub DUPLICATED_ATTRIBUTES, - suspicious, - "duplicated attribute" + #[clippy::version = "pre 1.29.0"] + pub USELESS_ATTRIBUTE, + correctness, + "use of lint attributes on `extern crate` items" } -declare_clippy_lint! { - /// ### What it does - /// Checks for ignored tests without messages. - /// - /// ### Why is this bad? - /// The reason for ignoring the test may not be obvious. - /// - /// ### Example - /// ```no_run - /// #[test] - /// #[ignore] - /// fn test() {} - /// ``` - /// Use instead: - /// ```no_run - /// #[test] - /// #[ignore = "Some good reason"] - /// fn test() {} - /// ``` - /// - /// ### Note - /// Clippy can only lint compiled code. For this lint to trigger, you must configure `cargo clippy` - /// to include test compilation, for instance, by using flags such as `--tests` or `--all-targets`. - #[clippy::version = "1.88.0"] - pub IGNORE_WITHOUT_REASON, - pedantic, - "ignored tests without messages" -} +impl_lint_pass!(Attributes => [INLINE_ALWAYS, REPR_PACKED_WITHOUT_ABI]); + +impl_lint_pass!(EarlyAttributes => [ + DEPRECATED_CFG_ATTR, + DEPRECATED_CLIPPY_CFG_ATTR, + NON_MINIMAL_CFG, + UNNECESSARY_CLIPPY_CFG, +]); + +impl_lint_pass!(PostExpansionEarlyAttributes => [ + ALLOW_ATTRIBUTES, + ALLOW_ATTRIBUTES_WITHOUT_REASON, + BLANKET_CLIPPY_RESTRICTION_LINTS, + DEPRECATED_SEMVER, + DUPLICATED_ATTRIBUTES, + IGNORE_WITHOUT_REASON, + MIXED_ATTRIBUTES_STYLE, + SHOULD_PANIC_WITHOUT_EXPECT, + USELESS_ATTRIBUTE, +]); pub struct Attributes { msrv: Msrv, } -impl_lint_pass!(Attributes => [ - INLINE_ALWAYS, - REPR_PACKED_WITHOUT_ABI, -]); - impl Attributes { pub fn new(conf: &'static Conf) -> Self { Self { msrv: conf.msrv } @@ -529,13 +545,6 @@ impl EarlyAttributes { } } -impl_lint_pass!(EarlyAttributes => [ - DEPRECATED_CFG_ATTR, - NON_MINIMAL_CFG, - DEPRECATED_CLIPPY_CFG_ATTR, - UNNECESSARY_CLIPPY_CFG, -]); - impl EarlyLintPass for EarlyAttributes { fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { deprecated_cfg_attr::check(cx, attr, &self.msrv); @@ -558,18 +567,6 @@ impl PostExpansionEarlyAttributes { } } -impl_lint_pass!(PostExpansionEarlyAttributes => [ - ALLOW_ATTRIBUTES, - ALLOW_ATTRIBUTES_WITHOUT_REASON, - DEPRECATED_SEMVER, - IGNORE_WITHOUT_REASON, - USELESS_ATTRIBUTE, - BLANKET_CLIPPY_RESTRICTION_LINTS, - SHOULD_PANIC_WITHOUT_EXPECT, - MIXED_ATTRIBUTES_STYLE, - DUPLICATED_ATTRIBUTES, -]); - impl EarlyLintPass for PostExpansionEarlyAttributes { fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &ast::Crate) { blanket_clippy_restriction_lints::check_command_line(cx); diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 31cc004f6855..dbd7dbdd1c6f 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -10,6 +10,43 @@ use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; use rustc_span::{Span, sym}; +declare_clippy_lint! { + /// ### What it does + /// Allows users to configure types which should not be held across await + /// suspension points. + /// + /// ### Why is this bad? + /// There are some types which are perfectly safe to use concurrently from + /// a memory access perspective, but that will cause bugs at runtime if + /// they are held in such a way. + /// + /// ### Example + /// + /// ```toml + /// await-holding-invalid-types = [ + /// # You can specify a type name + /// "CustomLockType", + /// # You can (optionally) specify a reason + /// { path = "OtherCustomLockType", reason = "Relies on a thread local" } + /// ] + /// ``` + /// + /// ```no_run + /// # async fn baz() {} + /// struct CustomLockType; + /// struct OtherCustomLockType; + /// async fn foo() { + /// let _x = CustomLockType; + /// let _y = OtherCustomLockType; + /// baz().await; // Lint violation + /// } + /// ``` + #[clippy::version = "1.62.0"] + pub AWAIT_HOLDING_INVALID_TYPE, + suspicious, + "holding a type across an await point which is not allowed to be held as per the configuration" +} + declare_clippy_lint! { /// ### What it does /// Checks for calls to `await` while holding a non-async-aware @@ -134,44 +171,11 @@ declare_clippy_lint! { "inside an async function, holding a `RefCell` ref while calling `await`" } -declare_clippy_lint! { - /// ### What it does - /// Allows users to configure types which should not be held across await - /// suspension points. - /// - /// ### Why is this bad? - /// There are some types which are perfectly safe to use concurrently from - /// a memory access perspective, but that will cause bugs at runtime if - /// they are held in such a way. - /// - /// ### Example - /// - /// ```toml - /// await-holding-invalid-types = [ - /// # You can specify a type name - /// "CustomLockType", - /// # You can (optionally) specify a reason - /// { path = "OtherCustomLockType", reason = "Relies on a thread local" } - /// ] - /// ``` - /// - /// ```no_run - /// # async fn baz() {} - /// struct CustomLockType; - /// struct OtherCustomLockType; - /// async fn foo() { - /// let _x = CustomLockType; - /// let _y = OtherCustomLockType; - /// baz().await; // Lint violation - /// } - /// ``` - #[clippy::version = "1.62.0"] - pub AWAIT_HOLDING_INVALID_TYPE, - suspicious, - "holding a type across an await point which is not allowed to be held as per the configuration" -} - -impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF, AWAIT_HOLDING_INVALID_TYPE]); +impl_lint_pass!(AwaitHolding => [ + AWAIT_HOLDING_INVALID_TYPE, + AWAIT_HOLDING_LOCK, + AWAIT_HOLDING_REFCELL_REF, +]); pub struct AwaitHolding { def_ids: DefIdMap<(&'static str, &'static DisallowedPathWithoutReplacement)>, diff --git a/clippy_lints/src/bool_to_int_with_if.rs b/clippy_lints/src/bool_to_int_with_if.rs index 129e77478406..b98a20a90ccb 100644 --- a/clippy_lints/src/bool_to_int_with_if.rs +++ b/clippy_lints/src/bool_to_int_with_if.rs @@ -43,6 +43,7 @@ declare_clippy_lint! { pedantic, "using if to convert bool to int" } + declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]); impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf { diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 0bd459d8b021..8b7619d11a83 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -74,6 +74,8 @@ declare_clippy_lint! { "boolean expressions that contain terminals which can be eliminated" } +impl_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, OVERLY_COMPLEX_BOOL_EXPR]); + // For each pairs, both orders are considered. const METHODS_WITH_NEGATION: [(Option, Symbol, Symbol); 3] = [ (None, sym::is_some, sym::is_none), @@ -91,8 +93,6 @@ impl NonminimalBool { } } -impl_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, OVERLY_COMPLEX_BOOL_EXPR]); - impl<'tcx> LateLintPass<'tcx> for NonminimalBool { fn check_fn( &mut self, diff --git a/clippy_lints/src/byte_char_slices.rs b/clippy_lints/src/byte_char_slices.rs index fc9931439e93..6c023189f69e 100644 --- a/clippy_lints/src/byte_char_slices.rs +++ b/clippy_lints/src/byte_char_slices.rs @@ -27,6 +27,7 @@ declare_clippy_lint! { style, "hard to read byte char slice" } + declare_lint_pass!(ByteCharSlice => [BYTE_CHAR_SLICES]); impl EarlyLintPass for ByteCharSlice { diff --git a/clippy_lints/src/cargo/mod.rs b/clippy_lints/src/cargo/mod.rs index 08d92adbacef..0aa6e4c3e8e2 100644 --- a/clippy_lints/src/cargo/mod.rs +++ b/clippy_lints/src/cargo/mod.rs @@ -58,34 +58,68 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for feature names with prefix `use-`, `with-` or suffix `-support` + /// Checks for lint groups with the same priority as lints in the `Cargo.toml` + /// [`[lints]` table](https://doc.rust-lang.org/cargo/reference/manifest.html#the-lints-section). + /// + /// This lint will be removed once [cargo#12918](https://github.com/rust-lang/cargo/issues/12918) + /// is resolved. /// /// ### Why is this bad? - /// These prefixes and suffixes have no significant meaning. + /// The order of lints in the `[lints]` is ignored, to have a lint override a group the + /// `priority` field needs to be used, otherwise the sort order is undefined. + /// + /// ### Known problems + /// Does not check lints inherited using `lints.workspace = true` /// /// ### Example /// ```toml - /// # The `Cargo.toml` with feature name redundancy - /// [features] - /// default = ["use-abc", "with-def", "ghi-support"] - /// use-abc = [] // redundant - /// with-def = [] // redundant - /// ghi-support = [] // redundant + /// # Passed as `--allow=clippy::similar_names --warn=clippy::pedantic` + /// # which results in `similar_names` being `warn` + /// [lints.clippy] + /// pedantic = "warn" + /// similar_names = "allow" /// ``` - /// /// Use instead: /// ```toml - /// [features] - /// default = ["abc", "def", "ghi"] - /// abc = [] - /// def = [] - /// ghi = [] + /// # Passed as `--warn=clippy::pedantic --allow=clippy::similar_names` + /// # which results in `similar_names` being `allow` + /// [lints.clippy] + /// pedantic = { level = "warn", priority = -1 } + /// similar_names = "allow" /// ``` + #[clippy::version = "1.78.0"] + pub LINT_GROUPS_PRIORITY, + correctness, + "a lint group in `Cargo.toml` at the same priority as a lint" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks to see if multiple versions of a crate are being + /// used. /// - #[clippy::version = "1.57.0"] - pub REDUNDANT_FEATURE_NAMES, + /// ### Why is this bad? + /// This bloats the size of targets, and can lead to + /// confusing error messages when structs or traits are used interchangeably + /// between different versions of a crate. + /// + /// ### Known problems + /// Because this can be caused purely by the dependencies + /// themselves, it's not always possible to fix this issue. + /// In those cases, you can allow that specific crate using + /// the `allowed-duplicate-crates` configuration option. + /// + /// ### Example + /// ```toml + /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning. + /// [dependencies] + /// ctrlc = "=3.1.0" + /// ansi_term = "=0.11.0" + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MULTIPLE_CRATE_VERSIONS, cargo, - "usage of a redundant feature name" + "multiple versions of the same crate being used" } declare_clippy_lint! { @@ -120,31 +154,34 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks to see if multiple versions of a crate are being - /// used. + /// Checks for feature names with prefix `use-`, `with-` or suffix `-support` /// /// ### Why is this bad? - /// This bloats the size of targets, and can lead to - /// confusing error messages when structs or traits are used interchangeably - /// between different versions of a crate. - /// - /// ### Known problems - /// Because this can be caused purely by the dependencies - /// themselves, it's not always possible to fix this issue. - /// In those cases, you can allow that specific crate using - /// the `allowed-duplicate-crates` configuration option. + /// These prefixes and suffixes have no significant meaning. /// /// ### Example /// ```toml - /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning. - /// [dependencies] - /// ctrlc = "=3.1.0" - /// ansi_term = "=0.11.0" + /// # The `Cargo.toml` with feature name redundancy + /// [features] + /// default = ["use-abc", "with-def", "ghi-support"] + /// use-abc = [] // redundant + /// with-def = [] // redundant + /// ghi-support = [] // redundant /// ``` - #[clippy::version = "pre 1.29.0"] - pub MULTIPLE_CRATE_VERSIONS, + /// + /// Use instead: + /// ```toml + /// [features] + /// default = ["abc", "def", "ghi"] + /// abc = [] + /// def = [] + /// ghi = [] + /// ``` + /// + #[clippy::version = "1.57.0"] + pub REDUNDANT_FEATURE_NAMES, cargo, - "multiple versions of the same crate being used" + "usage of a redundant feature name" } declare_clippy_lint! { @@ -176,57 +213,20 @@ declare_clippy_lint! { "wildcard dependencies being used" } -declare_clippy_lint! { - /// ### What it does - /// Checks for lint groups with the same priority as lints in the `Cargo.toml` - /// [`[lints]` table](https://doc.rust-lang.org/cargo/reference/manifest.html#the-lints-section). - /// - /// This lint will be removed once [cargo#12918](https://github.com/rust-lang/cargo/issues/12918) - /// is resolved. - /// - /// ### Why is this bad? - /// The order of lints in the `[lints]` is ignored, to have a lint override a group the - /// `priority` field needs to be used, otherwise the sort order is undefined. - /// - /// ### Known problems - /// Does not check lints inherited using `lints.workspace = true` - /// - /// ### Example - /// ```toml - /// # Passed as `--allow=clippy::similar_names --warn=clippy::pedantic` - /// # which results in `similar_names` being `warn` - /// [lints.clippy] - /// pedantic = "warn" - /// similar_names = "allow" - /// ``` - /// Use instead: - /// ```toml - /// # Passed as `--warn=clippy::pedantic --allow=clippy::similar_names` - /// # which results in `similar_names` being `allow` - /// [lints.clippy] - /// pedantic = { level = "warn", priority = -1 } - /// similar_names = "allow" - /// ``` - #[clippy::version = "1.78.0"] - pub LINT_GROUPS_PRIORITY, - correctness, - "a lint group in `Cargo.toml` at the same priority as a lint" -} - -pub struct Cargo { - allowed_duplicate_crates: FxHashSet, - ignore_publish: bool, -} - impl_lint_pass!(Cargo => [ CARGO_COMMON_METADATA, - REDUNDANT_FEATURE_NAMES, - NEGATIVE_FEATURE_NAMES, + LINT_GROUPS_PRIORITY, MULTIPLE_CRATE_VERSIONS, + NEGATIVE_FEATURE_NAMES, + REDUNDANT_FEATURE_NAMES, WILDCARD_DEPENDENCIES, - LINT_GROUPS_PRIORITY, ]); +pub struct Cargo { + allowed_duplicate_crates: FxHashSet, + ignore_publish: bool, +} + impl Cargo { pub fn new(conf: &'static Conf) -> Self { Self { diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 3c9ebef73f0d..75761de4ae73 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -36,50 +36,231 @@ 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. + /// Checks for the usage of `as *const _` or `as *mut _` conversion using inferred type. /// - /// Basically, this warns on casting any integer with 32 or more bits to `f32` - /// or any 64-bit integer to `f64`. + /// ### 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 the result of a `&self`-taking `as_ptr` being cast to a mutable pointer. /// /// ### 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. + /// 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 x = u64::MAX; - /// x as f64; + /// let mut vec = Vec::::with_capacity(1); + /// let ptr = vec.as_ptr() as *mut u8; + /// unsafe { ptr.write(4) }; // UNDEFINED BEHAVIOUR /// ``` - #[clippy::version = "pre 1.29.0"] - pub CAST_PRECISION_LOSS, + /// 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 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, - "casts that cause loss of precision, e.g., `x as f32` where `x: u64`" + "borrowing just to cast to a raw pointer" } 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. + /// Checks for usage of the `abs()` method that cast the result to unsigned. /// /// ### Why is this bad? - /// Possibly surprising results. You can activate this lint - /// as a one-time check to see where numeric wrapping can arise. + /// The `unsigned_abs()` method avoids panic when called on the MIN value. /// /// ### Example /// ```no_run - /// let y: i8 = -1; - /// y as u64; // will return 18446744073709551615 + /// 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 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 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 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_SIGN_LOSS, + pub CAST_LOSSLESS, pedantic, - "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`" + "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 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! { @@ -159,71 +340,28 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for casts between numeric types that can be replaced by safe - /// conversion functions. + /// 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? - /// 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. + /// 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 - /// 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) - /// } + /// let x = u64::MAX; + /// x as f64; /// ``` #[clippy::version = "pre 1.29.0"] - pub CAST_LOSSLESS, + pub CAST_PRECISION_LOSS, 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`" + "casts that cause loss of precision, e.g., `x as f32` where `x: u64`" } declare_clippy_lint! { @@ -256,107 +394,96 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for casts of function pointers to something other than `usize`. + /// 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? - /// 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. + /// Possibly surprising results. You can activate this lint + /// as a one-time check to see where numeric wrapping can arise. /// /// ### Example /// ```no_run - /// fn fun() -> i32 { 1 } - /// let _ = fun as i64; - /// ``` - /// - /// Use instead: - /// ```no_run - /// # fn fun() -> i32 { 1 } - /// let _ = fun as usize; + /// let y: i8 = -1; + /// y as u64; // will return 18446744073709551615 /// ``` #[clippy::version = "pre 1.29.0"] - pub FN_TO_NUMERIC_CAST, - style, - "casting a function pointer to a numeric type other than `usize`" + 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 of a function pointer to a numeric type not wide enough to - /// store an address. + /// Checks for `as` casts between raw pointers to slices with differently sized elements. /// /// ### 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. + /// 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 - /// fn fn1() -> i16 { - /// 1 - /// }; - /// let _ = fn1 as i32; + /// let a = [1_i32, 2, 3, 4]; + /// let p = &a as *const [i32] as *const [u8]; + /// unsafe { + /// println!("{:?}", &*p); + /// } /// ``` - /// - /// Use instead: + /// // Undefined Behavior (note: also potential alignment issues) /// ```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; + /// let a = [1_u8, 2, 3, 4]; + /// let p = &a as *const [u8] as *const [u32]; + /// unsafe { + /// println!("{:?}", &*p); + /// } /// ``` - #[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" + /// 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 of a function pointer to any integer type. + /// Checks for a raw slice being cast to a slice pointer /// - /// ### 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. + /// ### 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 - /// ```no_run - /// // fn1 is cast as `usize` - /// fn fn1() -> u16 { - /// 1 - /// }; - /// let _ = fn1 as usize; + /// ```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: - /// ```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; + /// ```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); /// ``` - #[clippy::version = "1.58.0"] - pub FN_TO_NUMERIC_CAST_ANY, - restriction, - "casting a function pointer to any integer type" + /// [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! { @@ -389,332 +516,251 @@ declare_clippy_lint! { 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`. + /// Checks for casts of a primitive method pointer like `max`/`min` to any integer type. /// - /// ### 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`. + /// ### 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 ptr: *const u32 = &42_u32; - /// let mut_ptr: *mut u32 = &mut 42_u32; - /// let _ = ptr as *const i32; - /// let _ = mut_ptr as *mut i32; + /// let _ = u16::max as usize; /// ``` + /// /// 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::(); + /// let _ = u16::MAX as usize; /// ``` - #[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`" + #[clippy::version = "1.89.0"] + pub CONFUSING_METHOD_TO_NUMERIC_CAST, + suspicious, + "casting a primitive method pointer to any integer type" } 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`. + /// Checks for casts of function pointers to something other than `usize`. /// /// ### 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. + /// 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 - /// 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(); + /// fn fun() -> i32 { 1 } + /// let _ = fun as i64; /// ``` + /// /// 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::(); + /// # fn fun() -> i32 { 1 } + /// let _ = fun as usize; /// ``` - #[clippy::version = "1.72.0"] - pub PTR_CAST_CONSTNESS, - pedantic, - "casting using `as` on raw pointers to change constness when specialized methods apply" + #[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 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. + /// Checks for casts of a function pointer to any integer type. /// - /// ### 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. + /// ### 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 - /// enum E { X(i32) }; - /// let _ = E::X as usize; + /// // fn1 is cast as `usize` + /// fn fn1() -> u16 { + /// 1 + /// }; + /// let _ = fn1 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. + /// // maybe you intended to call the function? + /// fn fn2() -> u16 { + /// 1 + /// }; + /// let _ = fn2() as usize; /// - /// The lint is allowed by default as using `_` is less wordy than always specifying the type. + /// // or /// - /// ### 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); + /// // maybe you intended to cast it to a function type? + /// fn fn3() -> u16 { + /// 1 + /// } + /// let _ = fn3 as fn() -> u16; /// ``` - #[clippy::version = "1.63.0"] - pub AS_UNDERSCORE, + #[clippy::version = "1.58.0"] + pub FN_TO_NUMERIC_CAST_ANY, restriction, - "detects `as _` conversion" + "casting a function pointer to any integer type" } 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. + /// Checks for casts of a function pointer to a numeric type not wide enough to + /// store an address. /// /// ### 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. + /// 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 - /// let val = 1; - /// let p = &val as *const i32; - /// - /// let mut val_mut = 1; - /// let p_mut = &mut val_mut as *mut i32; + /// fn fn1() -> i16 { + /// 1 + /// }; + /// let _ = fn1 as 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; + /// // 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 = "1.60.0"] - pub BORROW_AS_PTR, - pedantic, - "borrowing just to cast to a raw pointer" + #[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 a raw slice being cast to a slice pointer + /// Checks for casts of small constant literals or `mem::align_of` results to raw pointers. /// /// ### 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. + /// This creates a dangling pointer and is better expressed as + /// {`std`, `core`}`::ptr::`{`dangling`, `dangling_mut`}. /// /// ### 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 _; + /// ```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: - /// ```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); + /// ```no_run + /// let ptr = std::ptr::dangling::(); + /// let aligned = std::ptr::dangling::(); + /// let mut_ptr: *mut i64 = std::ptr::dangling_mut(); /// ``` - /// [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" + #[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 the result of a `&self`-taking `as_ptr` being cast to a mutable pointer. + /// Checks for bindings (constants, statics, or let bindings) that are defined + /// with one numeric type but are consistently cast to a different type in all usages. /// /// ### 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. + /// If a binding is always cast to a different type when used, it would be clearer + /// and more efficient to define it with the target type from the start. /// /// ### Example /// ```no_run - /// let mut vec = Vec::::with_capacity(1); - /// let ptr = vec.as_ptr() as *mut u8; - /// unsafe { ptr.write(4) }; // UNDEFINED BEHAVIOUR + /// const SIZE: u16 = 15; + /// let arr: [u8; SIZE as usize] = [0; SIZE as usize]; /// ``` + /// /// Use instead: /// ```no_run - /// let mut vec = Vec::::with_capacity(1); - /// let ptr = vec.as_mut_ptr(); - /// unsafe { ptr.write(4) }; + /// const SIZE: usize = 15; + /// let arr: [u8; SIZE] = [0; SIZE]; /// ``` - #[clippy::version = "1.66.0"] - pub AS_PTR_CAST_MUT, + #[clippy::version = "1.93.0"] + pub NEEDLESS_TYPE_CAST, nursery, - "casting the result of the `&self`-taking `as_ptr` to a mutable pointer" + "binding defined with one type but always cast to another" } declare_clippy_lint! { /// ### What it does - /// Checks for a known NaN float being cast to an integer + /// 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? - /// 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. + /// 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 - /// ```rust,ignore - /// let _ = (0.0_f32 / 0.0) as u64; + /// ```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: - /// ```rust,ignore - /// let _ = 0_u64; + /// ```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.66.0"] - pub CAST_NAN_TO_INT, - suspicious, - "casting a known floating-point NaN into an integer" + #[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 - /// Catch casts from `0` to some pointer type + /// 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? - /// This generally means `null` and is better expressed as - /// {`std`, `core`}`::ptr::`{`null`, `null_mut`}. + /// 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 a = 0 as *const u32; + /// 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 a = std::ptr::null::(); + /// 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 = "pre 1.29.0"] - pub ZERO_PTR, - style, - "using `0 as *{const, mut} T`" + #[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! { @@ -743,150 +789,104 @@ declare_clippy_lint! { 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. + /// 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? - /// This creates a dangling pointer and is better expressed as - /// {`std`, `core`}`::ptr::`{`dangling`, `dangling_mut`}. + /// It's just unnecessary. /// - /// ### 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. + /// ### 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. /// - /// ### 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. + /// 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 _ = u16::max as usize; + /// let _ = 2i32 as i32; + /// let _ = 0.5 as f32; /// ``` /// - /// Use instead: + /// Better: + /// /// ```no_run - /// let _ = u16::MAX as usize; + /// let _ = 2_i32; + /// let _ = 0.5_f32; /// ``` - #[clippy::version = "1.89.0"] - pub CONFUSING_METHOD_TO_NUMERIC_CAST, - suspicious, - "casting a primitive method pointer to any integer type" + #[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 bindings (constants, statics, or let bindings) that are defined - /// with one numeric type but are consistently cast to a different type in all usages. + /// Catch casts from `0` to some pointer type /// /// ### Why is this bad? - /// If a binding is always cast to a different type when used, it would be clearer - /// and more efficient to define it with the target type from the start. + /// This generally means `null` and is better expressed as + /// {`std`, `core`}`::ptr::`{`null`, `null_mut`}. /// /// ### Example /// ```no_run - /// const SIZE: u16 = 15; - /// let arr: [u8; SIZE as usize] = [0; SIZE as usize]; + /// let a = 0 as *const u32; /// ``` /// /// Use instead: /// ```no_run - /// const SIZE: usize = 15; - /// let arr: [u8; SIZE] = [0; SIZE]; + /// let a = std::ptr::null::(); /// ``` - #[clippy::version = "1.93.0"] - pub NEEDLESS_TYPE_CAST, - nursery, - "binding defined with one type but always cast to another" -} - -pub struct Casts { - msrv: Msrv, -} - -impl Casts { - pub fn new(conf: &'static Conf) -> Self { - Self { msrv: conf.msrv } - } + #[clippy::version = "pre 1.29.0"] + pub ZERO_PTR, + style, + "using `0 as *{const, mut} T`" } impl_lint_pass!(Casts => [ - CAST_PRECISION_LOSS, - CAST_SIGN_LOSS, + AS_POINTER_UNDERSCORE, + AS_PTR_CAST_MUT, + AS_UNDERSCORE, + BORROW_AS_PTR, + CAST_ABS_TO_UNSIGNED, + CAST_ENUM_CONSTRUCTOR, + CAST_ENUM_TRUNCATION, + CAST_LOSSLESS, + CAST_NAN_TO_INT, CAST_POSSIBLE_TRUNCATION, CAST_POSSIBLE_WRAP, - CAST_LOSSLESS, + CAST_PRECISION_LOSS, CAST_PTR_ALIGNMENT, + CAST_SIGN_LOSS, CAST_SLICE_DIFFERENT_SIZES, - UNNECESSARY_CAST, - FN_TO_NUMERIC_CAST_ANY, + CAST_SLICE_FROM_RAW_PARTS, + CHAR_LIT_AS_U8, + CONFUSING_METHOD_TO_NUMERIC_CAST, FN_TO_NUMERIC_CAST, + FN_TO_NUMERIC_CAST_ANY, FN_TO_NUMERIC_CAST_WITH_TRUNCATION, - CHAR_LIT_AS_U8, + MANUAL_DANGLING_PTR, + NEEDLESS_TYPE_CAST, 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, - NEEDLESS_TYPE_CAST, + UNNECESSARY_CAST, + ZERO_PTR, ]); +pub struct Casts { + msrv: Msrv, +} + +impl Casts { + pub fn new(conf: &'static Conf) -> Self { + Self { msrv: conf.msrv } + } +} + 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()) { diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 8303897d1294..5e9009c67197 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -34,6 +34,8 @@ declare_clippy_lint! { "`try_from` could replace manual bounds checking when casting" } +impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]); + pub struct CheckedConversions { msrv: Msrv, } @@ -44,8 +46,6 @@ impl CheckedConversions { } } -impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]); - impl LateLintPass<'_> for CheckedConversions { fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) { if let ExprKind::Binary(op, lhs, rhs) = item.kind diff --git a/clippy_lints/src/cloned_ref_to_slice_refs.rs b/clippy_lints/src/cloned_ref_to_slice_refs.rs index 35b799aefb04..c5eabe4c2b88 100644 --- a/clippy_lints/src/cloned_ref_to_slice_refs.rs +++ b/clippy_lints/src/cloned_ref_to_slice_refs.rs @@ -44,6 +44,8 @@ declare_clippy_lint! { "cloning a reference for slice references" } +impl_lint_pass!(ClonedRefToSliceRefs<'_> => [CLONED_REF_TO_SLICE_REFS]); + pub struct ClonedRefToSliceRefs<'a> { msrv: &'a Msrv, } @@ -53,8 +55,6 @@ impl<'a> ClonedRefToSliceRefs<'a> { } } -impl_lint_pass!(ClonedRefToSliceRefs<'_> => [CLONED_REF_TO_SLICE_REFS]); - impl<'tcx> LateLintPass<'tcx> for ClonedRefToSliceRefs<'_> { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { if self.msrv.meets(cx, { diff --git a/clippy_lints/src/coerce_container_to_any.rs b/clippy_lints/src/coerce_container_to_any.rs index 2e3acb7748e2..1a7e20b98271 100644 --- a/clippy_lints/src/coerce_container_to_any.rs +++ b/clippy_lints/src/coerce_container_to_any.rs @@ -46,6 +46,7 @@ declare_clippy_lint! { nursery, "coercing to `&dyn Any` when dereferencing could produce a `dyn Any` without coercion is usually not intended" } + declare_lint_pass!(CoerceContainerToAny => [COERCE_CONTAINER_TO_ANY]); impl<'tcx> LateLintPass<'tcx> for CoerceContainerToAny { diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index 950ac863d755..eaad18d25436 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -40,6 +40,8 @@ declare_clippy_lint! { @eval_always = true } +impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]); + pub struct CognitiveComplexity { limit: LimitStack, } @@ -52,8 +54,6 @@ impl CognitiveComplexity { } } -impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]); - impl CognitiveComplexity { fn check<'tcx>( &self, diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index 17e11b8b281d..a76027caebc8 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -12,38 +12,6 @@ use rustc_session::impl_lint_pass; use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, Span, Symbol}; -declare_clippy_lint! { - /// ### What it does - /// Checks for nested `if` statements which can be collapsed - /// by `&&`-combining their conditions. - /// - /// ### Why is this bad? - /// Each `if`-statement adds one level of nesting, which - /// makes code look more complex than it really is. - /// - /// ### Example - /// ```no_run - /// # let (x, y) = (true, true); - /// if x { - /// if y { - /// // … - /// } - /// } - /// ``` - /// - /// Use instead: - /// ```no_run - /// # let (x, y) = (true, true); - /// if x && y { - /// // … - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub COLLAPSIBLE_IF, - style, - "nested `if`s that can be collapsed (e.g., `if x { if y { ... } }`" -} - declare_clippy_lint! { /// ### What it does /// Checks for collapsible `else { if ... }` expressions @@ -80,6 +48,40 @@ declare_clippy_lint! { "nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)" } +declare_clippy_lint! { + /// ### What it does + /// Checks for nested `if` statements which can be collapsed + /// by `&&`-combining their conditions. + /// + /// ### Why is this bad? + /// Each `if`-statement adds one level of nesting, which + /// makes code look more complex than it really is. + /// + /// ### Example + /// ```no_run + /// # let (x, y) = (true, true); + /// if x { + /// if y { + /// // … + /// } + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let (x, y) = (true, true); + /// if x && y { + /// // … + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub COLLAPSIBLE_IF, + style, + "nested `if`s that can be collapsed (e.g., `if x { if y { ... } }`" +} + +impl_lint_pass!(CollapsibleIf => [COLLAPSIBLE_ELSE_IF, COLLAPSIBLE_IF]); + pub struct CollapsibleIf { msrv: Msrv, lint_commented_code: bool, @@ -259,8 +261,6 @@ impl CollapsibleIf { } } -impl_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_IF]); - impl LateLintPass<'_> for CollapsibleIf { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { if let ExprKind::If(cond, then, else_) = &expr.kind diff --git a/clippy_lints/src/collection_is_never_read.rs b/clippy_lints/src/collection_is_never_read.rs index fd84ce70bd71..f9afdb0cabf9 100644 --- a/clippy_lints/src/collection_is_never_read.rs +++ b/clippy_lints/src/collection_is_never_read.rs @@ -41,6 +41,7 @@ declare_clippy_lint! { nursery, "a collection is never queried" } + declare_lint_pass!(CollectionIsNeverRead => [COLLECTION_IS_NEVER_READ]); impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead { diff --git a/clippy_lints/src/crate_in_macro_def.rs b/clippy_lints/src/crate_in_macro_def.rs index 19f62e8bf79c..509b345048c1 100644 --- a/clippy_lints/src/crate_in_macro_def.rs +++ b/clippy_lints/src/crate_in_macro_def.rs @@ -49,6 +49,7 @@ declare_clippy_lint! { suspicious, "using `crate` in a macro definition" } + declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]); impl EarlyLintPass for CrateInMacroDef { diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index 45de4035d992..525f3da3efb1 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -33,6 +33,8 @@ declare_clippy_lint! { "`dbg!` macro is intended as a debugging tool" } +impl_lint_pass!(DbgMacro => [DBG_MACRO]); + pub struct DbgMacro { allow_dbg_in_tests: bool, /// Tracks the `dbg!` macro callsites that are already checked. @@ -41,8 +43,6 @@ pub struct DbgMacro { prev_ctxt: SyntaxContext, } -impl_lint_pass!(DbgMacro => [DBG_MACRO]); - impl DbgMacro { pub fn new(conf: &'static Conf) -> Self { DbgMacro { diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index a48e4d2fbd57..2064d896861b 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -69,14 +69,14 @@ declare_clippy_lint! { "binding initialized with Default should have its fields set in the initializer" } +impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]); + #[derive(Default)] pub struct Default { // Spans linted by `field_reassign_with_default`. reassigned_linted: FxHashSet, } -impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]); - impl<'tcx> LateLintPass<'tcx> for Default { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if !expr.span.from_expansion() diff --git a/clippy_lints/src/default_constructed_unit_structs.rs b/clippy_lints/src/default_constructed_unit_structs.rs index 641f8ae03b72..c831f96443c6 100644 --- a/clippy_lints/src/default_constructed_unit_structs.rs +++ b/clippy_lints/src/default_constructed_unit_structs.rs @@ -45,7 +45,10 @@ declare_clippy_lint! { complexity, "unit structs can be constructed without calling `default`" } -declare_lint_pass!(DefaultConstructedUnitStructs => [DEFAULT_CONSTRUCTED_UNIT_STRUCTS]); + +declare_lint_pass!(DefaultConstructedUnitStructs => [ + DEFAULT_CONSTRUCTED_UNIT_STRUCTS, +]); fn is_alias(ty: hir::Ty<'_>) -> bool { if let hir::TyKind::Path(ref qpath) = ty.kind { diff --git a/clippy_lints/src/default_instead_of_iter_empty.rs b/clippy_lints/src/default_instead_of_iter_empty.rs index 056e39c02af9..f041fc95fdd2 100644 --- a/clippy_lints/src/default_instead_of_iter_empty.rs +++ b/clippy_lints/src/default_instead_of_iter_empty.rs @@ -28,6 +28,7 @@ declare_clippy_lint! { style, "check `std::iter::Empty::default()` and replace with `std::iter::empty()`" } + declare_lint_pass!(DefaultIterEmpty => [DEFAULT_INSTEAD_OF_ITER_EMPTY]); impl<'tcx> LateLintPass<'tcx> for DefaultIterEmpty { diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index df6525ce040e..c04e07ee7f10 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -48,6 +48,7 @@ declare_clippy_lint! { restriction, "unions without a `#[repr(C)]` attribute" } + declare_lint_pass!(DefaultUnionRepresentation => [DEFAULT_UNION_REPRESENTATION]); impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation { diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 32fd4afb122e..920200bf8263 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -23,6 +23,29 @@ use rustc_span::symbol::sym; use rustc_span::{Span, Symbol}; use std::borrow::Cow; +declare_clippy_lint! { + /// ### What it does + /// Checks for dereferencing expressions which would be covered by auto-deref. + /// + /// ### Why is this bad? + /// This unnecessarily complicates the code. + /// + /// ### Example + /// ```no_run + /// let x = String::new(); + /// let y: &str = &*x; + /// ``` + /// Use instead: + /// ```no_run + /// let x = String::new(); + /// let y: &str = &x; + /// ``` + #[clippy::version = "1.64.0"] + pub EXPLICIT_AUTO_DEREF, + complexity, + "dereferencing when the compiler would automatically dereference" +} + declare_clippy_lint! { /// ### What it does /// Checks for explicit `deref()` or `deref_mut()` method calls. @@ -120,34 +143,11 @@ declare_clippy_lint! { "`ref` binding to a reference" } -declare_clippy_lint! { - /// ### What it does - /// Checks for dereferencing expressions which would be covered by auto-deref. - /// - /// ### Why is this bad? - /// This unnecessarily complicates the code. - /// - /// ### Example - /// ```no_run - /// let x = String::new(); - /// let y: &str = &*x; - /// ``` - /// Use instead: - /// ```no_run - /// let x = String::new(); - /// let y: &str = &x; - /// ``` - #[clippy::version = "1.64.0"] - pub EXPLICIT_AUTO_DEREF, - complexity, - "dereferencing when the compiler would automatically dereference" -} - impl_lint_pass!(Dereferencing<'_> => [ + EXPLICIT_AUTO_DEREF, EXPLICIT_DEREF_METHODS, NEEDLESS_BORROW, REF_BINDING_TO_REFERENCE, - EXPLICIT_AUTO_DEREF, ]); #[derive(Default)] diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index 992ed320ce68..c04163b1c7a0 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -56,6 +56,8 @@ declare_clippy_lint! { "manual implementation of the `Default` trait which is equal to a derive" } +impl_lint_pass!(DerivableImpls => [DERIVABLE_IMPLS]); + pub struct DerivableImpls { msrv: Msrv, } @@ -66,8 +68,6 @@ impl DerivableImpls { } } -impl_lint_pass!(DerivableImpls => [DERIVABLE_IMPLS]); - fn is_path_self(e: &Expr<'_>) -> bool { if let ExprKind::Path(QPath::Resolved(_, p)) = e.kind { matches!(p.res, Res::SelfCtor(..) | Res::Def(DefKind::Ctor(..), _)) diff --git a/clippy_lints/src/derive/mod.rs b/clippy_lints/src/derive/mod.rs index 86614201c406..fa1a7037154e 100644 --- a/clippy_lints/src/derive/mod.rs +++ b/clippy_lints/src/derive/mod.rs @@ -10,36 +10,6 @@ mod derived_hash_with_manual_eq; mod expl_impl_clone_on_copy; mod unsafe_derive_deserialize; -declare_clippy_lint! { - /// ### What it does - /// Lints against manual `PartialEq` implementations for types with a derived `Hash` - /// implementation. - /// - /// ### Why is this bad? - /// The implementation of these traits must agree (for - /// example for use with `HashMap`) so it’s probably a bad idea to use a - /// default-generated `Hash` implementation with an explicitly defined - /// `PartialEq`. In particular, the following must hold for any type: - /// - /// ```text - /// k1 == k2 ⇒ hash(k1) == hash(k2) - /// ``` - /// - /// ### Example - /// ```ignore - /// #[derive(Hash)] - /// struct Foo; - /// - /// impl PartialEq for Foo { - /// ... - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub DERIVED_HASH_WITH_MANUAL_EQ, - correctness, - "deriving `Hash` but implementing `PartialEq` explicitly" -} - declare_clippy_lint! { /// ### What it does /// Lints against manual `PartialOrd` and `Ord` implementations for types with a derived `Ord` @@ -91,6 +61,68 @@ declare_clippy_lint! { "deriving `Ord` but implementing `PartialOrd` explicitly" } +declare_clippy_lint! { + /// ### What it does + /// Checks for types that derive `PartialEq` and could implement `Eq`. + /// + /// ### Why is this bad? + /// If a type `T` derives `PartialEq` and all of its members implement `Eq`, + /// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used + /// in APIs that require `Eq` types. It also allows structs containing `T` to derive + /// `Eq` themselves. + /// + /// ### Example + /// ```no_run + /// #[derive(PartialEq)] + /// struct Foo { + /// i_am_eq: i32, + /// i_am_eq_too: Vec, + /// } + /// ``` + /// Use instead: + /// ```no_run + /// #[derive(PartialEq, Eq)] + /// struct Foo { + /// i_am_eq: i32, + /// i_am_eq_too: Vec, + /// } + /// ``` + #[clippy::version = "1.63.0"] + pub DERIVE_PARTIAL_EQ_WITHOUT_EQ, + nursery, + "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`" +} + +declare_clippy_lint! { + /// ### What it does + /// Lints against manual `PartialEq` implementations for types with a derived `Hash` + /// implementation. + /// + /// ### Why is this bad? + /// The implementation of these traits must agree (for + /// example for use with `HashMap`) so it’s probably a bad idea to use a + /// default-generated `Hash` implementation with an explicitly defined + /// `PartialEq`. In particular, the following must hold for any type: + /// + /// ```text + /// k1 == k2 ⇒ hash(k1) == hash(k2) + /// ``` + /// + /// ### Example + /// ```ignore + /// #[derive(Hash)] + /// struct Foo; + /// + /// impl PartialEq for Foo { + /// ... + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub DERIVED_HASH_WITH_MANUAL_EQ, + correctness, + "deriving `Hash` but implementing `PartialEq` explicitly" +} + declare_clippy_lint! { /// ### What it does /// Checks for explicit `Clone` implementations for `Copy` @@ -152,44 +184,12 @@ declare_clippy_lint! { "deriving `serde::Deserialize` on a type that has methods using `unsafe`" } -declare_clippy_lint! { - /// ### What it does - /// Checks for types that derive `PartialEq` and could implement `Eq`. - /// - /// ### Why is this bad? - /// If a type `T` derives `PartialEq` and all of its members implement `Eq`, - /// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used - /// in APIs that require `Eq` types. It also allows structs containing `T` to derive - /// `Eq` themselves. - /// - /// ### Example - /// ```no_run - /// #[derive(PartialEq)] - /// struct Foo { - /// i_am_eq: i32, - /// i_am_eq_too: Vec, - /// } - /// ``` - /// Use instead: - /// ```no_run - /// #[derive(PartialEq, Eq)] - /// struct Foo { - /// i_am_eq: i32, - /// i_am_eq_too: Vec, - /// } - /// ``` - #[clippy::version = "1.63.0"] - pub DERIVE_PARTIAL_EQ_WITHOUT_EQ, - nursery, - "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`" -} - declare_lint_pass!(Derive => [ - EXPL_IMPL_CLONE_ON_COPY, DERIVED_HASH_WITH_MANUAL_EQ, DERIVE_ORD_XOR_PARTIAL_ORD, + DERIVE_PARTIAL_EQ_WITHOUT_EQ, + EXPL_IMPL_CLONE_ON_COPY, UNSAFE_DERIVE_DESERIALIZE, - DERIVE_PARTIAL_EQ_WITHOUT_EQ ]); impl<'tcx> LateLintPass<'tcx> for Derive { diff --git a/clippy_lints/src/disallowed_fields.rs b/clippy_lints/src/disallowed_fields.rs index f1136556e2ed..9873c32f427f 100644 --- a/clippy_lints/src/disallowed_fields.rs +++ b/clippy_lints/src/disallowed_fields.rs @@ -56,6 +56,8 @@ declare_clippy_lint! { "declaration of a disallowed field use" } +impl_lint_pass!(DisallowedFields => [DISALLOWED_FIELDS]); + pub struct DisallowedFields { disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>, } @@ -74,8 +76,6 @@ impl DisallowedFields { } } -impl_lint_pass!(DisallowedFields => [DISALLOWED_FIELDS]); - impl<'tcx> LateLintPass<'tcx> for DisallowedFields { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { let (id, span) = match &expr.kind { diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index 1c9c971730f6..7253b65e4337 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -63,6 +63,8 @@ declare_clippy_lint! { "use of a disallowed macro" } +impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]); + pub struct DisallowedMacros { disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>, seen: FxHashSet, @@ -125,8 +127,6 @@ impl DisallowedMacros { } } -impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]); - impl LateLintPass<'_> for DisallowedMacros { fn check_crate(&mut self, cx: &LateContext<'_>) { // once we check a crate in the late pass we can emit the early pass lints diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index 58403ad19235..e2fd71b7d990 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -61,6 +61,8 @@ declare_clippy_lint! { "use of a disallowed method call" } +impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]); + pub struct DisallowedMethods { disallowed: DefIdMap<(&'static str, &'static DisallowedPath)>, } @@ -84,8 +86,6 @@ impl DisallowedMethods { } } -impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]); - impl<'tcx> LateLintPass<'tcx> for DisallowedMethods { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if expr.span.desugaring_kind().is_some() { diff --git a/clippy_lints/src/disallowed_names.rs b/clippy_lints/src/disallowed_names.rs index 566aa12b08f5..1f658b2aa068 100644 --- a/clippy_lints/src/disallowed_names.rs +++ b/clippy_lints/src/disallowed_names.rs @@ -26,6 +26,8 @@ declare_clippy_lint! { "usage of a disallowed/placeholder name" } +impl_lint_pass!(DisallowedNames => [DISALLOWED_NAMES]); + pub struct DisallowedNames { disallow: FxHashSet, } @@ -38,8 +40,6 @@ impl DisallowedNames { } } -impl_lint_pass!(DisallowedNames => [DISALLOWED_NAMES]); - impl<'tcx> LateLintPass<'tcx> for DisallowedNames { fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { if let PatKind::Binding(.., ident, _) = pat.kind diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs index 5b4fe2a6b803..4596d9457c0b 100644 --- a/clippy_lints/src/disallowed_script_idents.rs +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -45,6 +45,8 @@ declare_clippy_lint! { "usage of non-allowed Unicode scripts" } +impl_lint_pass!(DisallowedScriptIdents => [DISALLOWED_SCRIPT_IDENTS]); + pub struct DisallowedScriptIdents { whitelist: FxHashSet