Merge from rust-lang/rust
This commit is contained in:
commit
88223c56d9
3427 changed files with 107813 additions and 37739 deletions
|
|
@ -40,7 +40,9 @@ static HOSTS: &[&str] = &[
|
|||
"powerpc64le-unknown-linux-musl",
|
||||
"riscv64gc-unknown-linux-gnu",
|
||||
"s390x-unknown-linux-gnu",
|
||||
"sparcv9-sun-solaris",
|
||||
"x86_64-apple-darwin",
|
||||
"x86_64-pc-solaris",
|
||||
"x86_64-pc-windows-gnu",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"x86_64-unknown-freebsd",
|
||||
|
|
@ -111,6 +113,8 @@ static TARGETS: &[&str] = &[
|
|||
"i686-unknown-uefi",
|
||||
"loongarch64-unknown-linux-gnu",
|
||||
"loongarch64-unknown-linux-musl",
|
||||
"loongarch32-unknown-none",
|
||||
"loongarch32-unknown-none-softfloat",
|
||||
"loongarch64-unknown-none",
|
||||
"loongarch64-unknown-none-softfloat",
|
||||
"m68k-unknown-linux-gnu",
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 47c911e9e6f6461f90ce19142031fe16876a3b95
|
||||
Subproject commit 64a12460708cf146e16cc61f28aba5dc2463bbb4
|
||||
|
|
@ -6440,6 +6440,7 @@ Released 2018-09-13
|
|||
[`used_underscore_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_items
|
||||
[`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref
|
||||
[`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute
|
||||
[`useless_concat`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_concat
|
||||
[`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
|
||||
[`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format
|
||||
[`useless_let_if_seq`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_let_if_seq
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ Clippy team directly by mentioning them in the issue or over on [Zulip]. All
|
|||
currently active team members can be found
|
||||
[here](https://github.com/rust-lang/rust-clippy/blob/master/triagebot.toml#L18)
|
||||
|
||||
Some issues are easier than others. The [`good-first-issue`] label can be used to find the easy
|
||||
Some issues are easier than others. The [`good first issue`] label can be used to find the easy
|
||||
issues. You can use `@rustbot claim` to assign the issue to yourself.
|
||||
|
||||
There are also some abandoned PRs, marked with [`S-inactive-closed`].
|
||||
|
|
@ -70,7 +70,7 @@ To figure out how this syntax structure is encoded in the AST, it is recommended
|
|||
Usually the lint will end up to be a nested series of matches and ifs, [like so][deep-nesting].
|
||||
But we can make it nest-less by using [let chains], [like this][nest-less].
|
||||
|
||||
[`E-medium`] issues are generally pretty easy too, though it's recommended you work on an [`good-first-issue`]
|
||||
[`E-medium`] issues are generally pretty easy too, though it's recommended you work on an [`good first issue`]
|
||||
first. Sometimes they are only somewhat involved code wise, but not difficult per-se.
|
||||
Note that [`E-medium`] issues may require some knowledge of Clippy internals or some
|
||||
debugging to find the actual problem behind the issue.
|
||||
|
|
@ -79,7 +79,7 @@ debugging to find the actual problem behind the issue.
|
|||
lot of methods that are useful, though one of the most useful would be `expr_ty` (gives the type of
|
||||
an AST expression).
|
||||
|
||||
[`good-first-issue`]: https://github.com/rust-lang/rust-clippy/labels/good-first-issue
|
||||
[`good first issue`]: https://github.com/rust-lang/rust-clippy/labels/good%20first%20issue
|
||||
[`S-inactive-closed`]: https://github.com/rust-lang/rust-clippy/pulls?q=is%3Aclosed+label%3AS-inactive-closed
|
||||
[`T-AST`]: https://github.com/rust-lang/rust-clippy/labels/T-AST
|
||||
[`T-middle`]: https://github.com/rust-lang/rust-clippy/labels/T-middle
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
[package]
|
||||
name = "clippy"
|
||||
# begin autogenerated version
|
||||
version = "0.1.89"
|
||||
# end autogenerated version
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
|
|
@ -44,7 +42,7 @@ walkdir = "2.3"
|
|||
filetime = "0.2.9"
|
||||
itertools = "0.12"
|
||||
pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] }
|
||||
askama = { version = "0.13", default-features = false, features = ["alloc", "config", "derive"] }
|
||||
askama = { version = "0.14", default-features = false, features = ["alloc", "config", "derive"] }
|
||||
|
||||
# UI test dependencies
|
||||
if_chain = "1.0"
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ toolchain called `clippy` by default, see `cargo dev setup toolchain --help`
|
|||
for other options.
|
||||
|
||||
```terminal
|
||||
cargo dev setup toolcahin
|
||||
cargo dev setup toolchain
|
||||
```
|
||||
|
||||
Now you may run `cargo +clippy clippy` in any project using the new toolchain.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ this group to help with triaging, which can include:
|
|||
|
||||
1. **Labeling issues**
|
||||
|
||||
For the `good-first-issue` label, it can still be good to use `@rustbot` to
|
||||
For the `good first issue` label, it can still be good to use `@rustbot` to
|
||||
subscribe to the issue and help interested parties, if they post questions
|
||||
in the comments.
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ providing the `LateContext` (`cx`), our expression at hand, and
|
|||
the symbol of the trait in question:
|
||||
|
||||
```rust
|
||||
use clippy_utils::is_trait_method;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_span::symbol::sym;
|
||||
|
|
@ -25,7 +25,7 @@ use rustc_span::symbol::sym;
|
|||
impl LateLintPass<'_> for CheckIteratorTraitLint {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
let implements_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| {
|
||||
implements_trait(cx, cx.typeck_results().expr_ty(arg), id, &[])
|
||||
implements_trait(cx, cx.typeck_results().expr_ty(expr), id, &[])
|
||||
});
|
||||
if implements_iterator {
|
||||
// [...]
|
||||
|
|
|
|||
|
|
@ -836,6 +836,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
|
|||
* [`manual_repeat_n`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_repeat_n)
|
||||
* [`manual_retain`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain)
|
||||
* [`manual_slice_fill`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_fill)
|
||||
* [`manual_slice_size_calculation`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_size_calculation)
|
||||
* [`manual_split_once`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once)
|
||||
* [`manual_str_repeat`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat)
|
||||
* [`manual_strip`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip)
|
||||
|
|
@ -1025,7 +1026,7 @@ The order of associated items in traits.
|
|||
The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by
|
||||
reference.
|
||||
|
||||
**Default Value:** `target_pointer_width * 2`
|
||||
**Default Value:** `target_pointer_width`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_config"
|
||||
# begin autogenerated version
|
||||
version = "0.1.89"
|
||||
# end autogenerated version
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
|
|
|
|||
|
|
@ -739,6 +739,7 @@ define_Conf! {
|
|||
manual_repeat_n,
|
||||
manual_retain,
|
||||
manual_slice_fill,
|
||||
manual_slice_size_calculation,
|
||||
manual_split_once,
|
||||
manual_str_repeat,
|
||||
manual_strip,
|
||||
|
|
@ -827,7 +828,7 @@ define_Conf! {
|
|||
trait_assoc_item_kinds_order: SourceItemOrderingTraitAssocItemKinds = DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER.into(),
|
||||
/// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by
|
||||
/// reference.
|
||||
#[default_text = "target_pointer_width * 2"]
|
||||
#[default_text = "target_pointer_width"]
|
||||
#[lints(trivially_copy_pass_by_ref)]
|
||||
trivial_copy_size_limit: Option<u64> = None,
|
||||
/// The maximum complexity a type can have
|
||||
|
|
|
|||
|
|
@ -5,13 +5,11 @@ version = "0.0.1"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
aho-corasick = "1.0"
|
||||
chrono = { version = "0.4.38", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
indoc = "1.0"
|
||||
itertools = "0.12"
|
||||
opener = "0.7"
|
||||
shell-escape = "0.1"
|
||||
walkdir = "2.3"
|
||||
|
||||
[package.metadata.rust-analyzer]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
use crate::update_lints::{
|
||||
DeprecatedLint, DeprecatedLints, Lint, find_lint_decls, generate_lint_files, read_deprecated_lints,
|
||||
};
|
||||
use crate::update_lints::{DeprecatedLint, Lint, find_lint_decls, generate_lint_files, read_deprecated_lints};
|
||||
use crate::utils::{UpdateMode, Version};
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -16,28 +14,34 @@ use std::{fs, io};
|
|||
///
|
||||
/// If a file path could not read from or written to
|
||||
pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
|
||||
let prefixed_name = if name.starts_with("clippy::") {
|
||||
name.to_owned()
|
||||
} else {
|
||||
format!("clippy::{name}")
|
||||
};
|
||||
let stripped_name = &prefixed_name[8..];
|
||||
if let Some((prefix, _)) = name.split_once("::") {
|
||||
panic!("`{name}` should not contain the `{prefix}` prefix");
|
||||
}
|
||||
|
||||
let mut lints = find_lint_decls();
|
||||
let DeprecatedLints {
|
||||
renamed: renamed_lints,
|
||||
deprecated: mut deprecated_lints,
|
||||
file: mut deprecated_file,
|
||||
contents: mut deprecated_contents,
|
||||
deprecated_end,
|
||||
..
|
||||
} = read_deprecated_lints();
|
||||
let (mut deprecated_lints, renamed_lints) = read_deprecated_lints();
|
||||
|
||||
let Some(lint) = lints.iter().find(|l| l.name == stripped_name) else {
|
||||
let Some(lint) = lints.iter().find(|l| l.name == name) else {
|
||||
eprintln!("error: failed to find lint `{name}`");
|
||||
return;
|
||||
};
|
||||
|
||||
let prefixed_name = String::from_iter(["clippy::", name]);
|
||||
match deprecated_lints.binary_search_by(|x| x.name.cmp(&prefixed_name)) {
|
||||
Ok(_) => {
|
||||
println!("`{name}` is already deprecated");
|
||||
return;
|
||||
},
|
||||
Err(idx) => deprecated_lints.insert(
|
||||
idx,
|
||||
DeprecatedLint {
|
||||
name: prefixed_name,
|
||||
reason: reason.into(),
|
||||
version: clippy_version.rust_display().to_string(),
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
let mod_path = {
|
||||
let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module));
|
||||
if mod_path.is_dir() {
|
||||
|
|
@ -48,24 +52,7 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
|
|||
mod_path
|
||||
};
|
||||
|
||||
if remove_lint_declaration(stripped_name, &mod_path, &mut lints).unwrap_or(false) {
|
||||
deprecated_contents.insert_str(
|
||||
deprecated_end as usize,
|
||||
&format!(
|
||||
" #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n",
|
||||
clippy_version.rust_display(),
|
||||
prefixed_name,
|
||||
reason,
|
||||
),
|
||||
);
|
||||
deprecated_file.replace_contents(deprecated_contents.as_bytes());
|
||||
drop(deprecated_file);
|
||||
|
||||
deprecated_lints.push(DeprecatedLint {
|
||||
name: prefixed_name,
|
||||
reason: reason.into(),
|
||||
});
|
||||
|
||||
if remove_lint_declaration(name, &mod_path, &mut lints).unwrap_or(false) {
|
||||
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
|
||||
println!("info: `{name}` has successfully been deprecated");
|
||||
println!("note: you must run `cargo uitest` to update the test results");
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
use crate::utils::{
|
||||
ErrAction, FileUpdater, UpdateMode, UpdateStatus, expect_action, run_with_output, split_args_for_threads,
|
||||
walk_dir_no_dot_or_target,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use rustc_lexer::{TokenKind, tokenize};
|
||||
use shell_escape::escape;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt::Write;
|
||||
use std::fs;
|
||||
use std::io::{self, Read};
|
||||
use std::ops::ControlFlow;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use std::process::{self, Command, Stdio};
|
||||
use std::{fs, io};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub enum Error {
|
||||
CommandFailed(String, String),
|
||||
Io(io::Error),
|
||||
RustfmtNotInstalled,
|
||||
WalkDir(walkdir::Error),
|
||||
IntellijSetupActive,
|
||||
Parse(PathBuf, usize, String),
|
||||
CheckFailed,
|
||||
}
|
||||
|
|
@ -24,37 +23,15 @@ impl From<io::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<walkdir::Error> for Error {
|
||||
fn from(error: walkdir::Error) -> Self {
|
||||
Self::WalkDir(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
fn display(&self) {
|
||||
match self {
|
||||
Self::CheckFailed => {
|
||||
eprintln!("Formatting check failed!\nRun `cargo dev fmt` to update.");
|
||||
},
|
||||
Self::CommandFailed(command, stderr) => {
|
||||
eprintln!("error: command `{command}` failed!\nstderr: {stderr}");
|
||||
},
|
||||
Self::Io(err) => {
|
||||
eprintln!("error: {err}");
|
||||
},
|
||||
Self::RustfmtNotInstalled => {
|
||||
eprintln!("error: rustfmt nightly is not installed.");
|
||||
},
|
||||
Self::WalkDir(err) => {
|
||||
eprintln!("error: {err}");
|
||||
},
|
||||
Self::IntellijSetupActive => {
|
||||
eprintln!(
|
||||
"error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`.\n\
|
||||
Not formatting because that would format the local repo as well!\n\
|
||||
Please revert the changes to `Cargo.toml`s with `cargo dev remove intellij`."
|
||||
);
|
||||
},
|
||||
Self::Parse(path, line, msg) => {
|
||||
eprintln!("error parsing `{}:{line}`: {msg}", path.display());
|
||||
},
|
||||
|
|
@ -62,12 +39,6 @@ impl Error {
|
|||
}
|
||||
}
|
||||
|
||||
struct FmtContext {
|
||||
check: bool,
|
||||
verbose: bool,
|
||||
rustfmt_path: String,
|
||||
}
|
||||
|
||||
struct ClippyConf<'a> {
|
||||
name: &'a str,
|
||||
attrs: &'a str,
|
||||
|
|
@ -257,155 +228,108 @@ fn fmt_conf(check: bool) -> Result<(), Error> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn run_rustfmt(context: &FmtContext) -> Result<(), Error> {
|
||||
// if we added a local rustc repo as path dependency to clippy for rust analyzer, we do NOT want to
|
||||
// format because rustfmt would also format the entire rustc repo as it is a local
|
||||
// dependency
|
||||
if fs::read_to_string("Cargo.toml")
|
||||
.expect("Failed to read clippy Cargo.toml")
|
||||
.contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]")
|
||||
{
|
||||
return Err(Error::IntellijSetupActive);
|
||||
}
|
||||
|
||||
check_for_rustfmt(context)?;
|
||||
|
||||
cargo_fmt(context, ".".as_ref())?;
|
||||
cargo_fmt(context, "clippy_dev".as_ref())?;
|
||||
cargo_fmt(context, "rustc_tools_util".as_ref())?;
|
||||
cargo_fmt(context, "lintcheck".as_ref())?;
|
||||
|
||||
let chunks = WalkDir::new("tests")
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
let entry = entry.expect("failed to find tests");
|
||||
let path = entry.path();
|
||||
if path.extension() != Some("rs".as_ref())
|
||||
|| path
|
||||
.components()
|
||||
.nth_back(1)
|
||||
.is_some_and(|c| c.as_os_str() == "syntax-error-recovery")
|
||||
|| entry.file_name() == "ice-3891.rs"
|
||||
{
|
||||
None
|
||||
/// Format the symbols list
|
||||
fn fmt_syms(update_mode: UpdateMode) {
|
||||
FileUpdater::default().update_file_checked(
|
||||
"cargo dev fmt",
|
||||
update_mode,
|
||||
"clippy_utils/src/sym.rs",
|
||||
&mut |_, text: &str, new_text: &mut String| {
|
||||
let (pre, conf) = text.split_once("generate! {\n").expect("can't find generate! call");
|
||||
let (conf, post) = conf.split_once("\n}\n").expect("can't find end of generate! call");
|
||||
let mut lines = conf
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let line = line.trim();
|
||||
line.strip_suffix(',').unwrap_or(line).trim_end()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
lines.sort_unstable();
|
||||
write!(
|
||||
new_text,
|
||||
"{pre}generate! {{\n {},\n}}\n{post}",
|
||||
lines.join(",\n "),
|
||||
)
|
||||
.unwrap();
|
||||
if text == new_text {
|
||||
UpdateStatus::Unchanged
|
||||
} else {
|
||||
Some(entry.into_path().into_os_string())
|
||||
UpdateStatus::Changed
|
||||
}
|
||||
})
|
||||
.chunks(250);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for chunk in &chunks {
|
||||
rustfmt(context, chunk)?;
|
||||
fn run_rustfmt(update_mode: UpdateMode) {
|
||||
let mut rustfmt_path = String::from_utf8(run_with_output(
|
||||
"rustup which rustfmt",
|
||||
Command::new("rustup").args(["which", "rustfmt"]),
|
||||
))
|
||||
.expect("invalid rustfmt path");
|
||||
rustfmt_path.truncate(rustfmt_path.trim_end().len());
|
||||
|
||||
let args: Vec<_> = walk_dir_no_dot_or_target()
|
||||
.filter_map(|e| {
|
||||
let e = expect_action(e, ErrAction::Read, ".");
|
||||
e.path()
|
||||
.as_os_str()
|
||||
.as_encoded_bytes()
|
||||
.ends_with(b".rs")
|
||||
.then(|| e.into_path().into_os_string())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut children: Vec<_> = split_args_for_threads(
|
||||
32,
|
||||
|| {
|
||||
let mut cmd = Command::new(&rustfmt_path);
|
||||
if update_mode.is_check() {
|
||||
cmd.arg("--check");
|
||||
}
|
||||
cmd.stdout(Stdio::null())
|
||||
.stdin(Stdio::null())
|
||||
.stderr(Stdio::piped())
|
||||
.args(["--unstable-features", "--skip-children"]);
|
||||
cmd
|
||||
},
|
||||
args.iter(),
|
||||
)
|
||||
.map(|mut cmd| expect_action(cmd.spawn(), ErrAction::Run, "rustfmt"))
|
||||
.collect();
|
||||
|
||||
for child in &mut children {
|
||||
let status = expect_action(child.wait(), ErrAction::Run, "rustfmt");
|
||||
match (update_mode, status.exit_ok()) {
|
||||
(UpdateMode::Check | UpdateMode::Change, Ok(())) => {},
|
||||
(UpdateMode::Check, Err(_)) => {
|
||||
let mut s = String::new();
|
||||
if let Some(mut stderr) = child.stderr.take()
|
||||
&& stderr.read_to_string(&mut s).is_ok()
|
||||
{
|
||||
eprintln!("{s}");
|
||||
}
|
||||
eprintln!("Formatting check failed!\nRun `cargo dev fmt` to update.");
|
||||
process::exit(1);
|
||||
},
|
||||
(UpdateMode::Change, e) => {
|
||||
let mut s = String::new();
|
||||
if let Some(mut stderr) = child.stderr.take()
|
||||
&& stderr.read_to_string(&mut s).is_ok()
|
||||
{
|
||||
eprintln!("{s}");
|
||||
}
|
||||
expect_action(e, ErrAction::Run, "rustfmt");
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// the "main" function of cargo dev fmt
|
||||
pub fn run(check: bool, verbose: bool) {
|
||||
let output = Command::new("rustup")
|
||||
.args(["which", "rustfmt"])
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.expect("error running `rustup which rustfmt`");
|
||||
if !output.status.success() {
|
||||
eprintln!("`rustup which rustfmt` did not execute successfully");
|
||||
process::exit(1);
|
||||
}
|
||||
let mut rustfmt_path = String::from_utf8(output.stdout).expect("invalid rustfmt path");
|
||||
rustfmt_path.truncate(rustfmt_path.trim_end().len());
|
||||
|
||||
let context = FmtContext {
|
||||
check,
|
||||
verbose,
|
||||
rustfmt_path,
|
||||
};
|
||||
if let Err(e) = run_rustfmt(&context).and_then(|()| fmt_conf(check)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
fn format_command(program: impl AsRef<OsStr>, dir: impl AsRef<Path>, args: &[impl AsRef<OsStr>]) -> String {
|
||||
let arg_display: Vec<_> = args.iter().map(|a| escape(a.as_ref().to_string_lossy())).collect();
|
||||
|
||||
format!(
|
||||
"cd {} && {} {}",
|
||||
escape(dir.as_ref().to_string_lossy()),
|
||||
escape(program.as_ref().to_string_lossy()),
|
||||
arg_display.join(" ")
|
||||
)
|
||||
}
|
||||
|
||||
fn exec_fmt_command(
|
||||
context: &FmtContext,
|
||||
program: impl AsRef<OsStr>,
|
||||
dir: impl AsRef<Path>,
|
||||
args: &[impl AsRef<OsStr>],
|
||||
) -> Result<(), Error> {
|
||||
if context.verbose {
|
||||
println!("{}", format_command(&program, &dir, args));
|
||||
}
|
||||
|
||||
let output = Command::new(&program)
|
||||
.env("RUSTFMT", &context.rustfmt_path)
|
||||
.current_dir(&dir)
|
||||
.args(args.iter())
|
||||
.output()
|
||||
.unwrap();
|
||||
let success = output.status.success();
|
||||
|
||||
match (context.check, success) {
|
||||
(_, true) => Ok(()),
|
||||
(true, false) => Err(Error::CheckFailed),
|
||||
(false, false) => {
|
||||
let stderr = std::str::from_utf8(&output.stderr).unwrap_or("");
|
||||
Err(Error::CommandFailed(
|
||||
format_command(&program, &dir, args),
|
||||
String::from(stderr),
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn cargo_fmt(context: &FmtContext, path: &Path) -> Result<(), Error> {
|
||||
let mut args = vec!["fmt", "--all"];
|
||||
if context.check {
|
||||
args.push("--check");
|
||||
}
|
||||
exec_fmt_command(context, "cargo", path, &args)
|
||||
}
|
||||
|
||||
fn check_for_rustfmt(context: &FmtContext) -> Result<(), Error> {
|
||||
let program = "rustfmt";
|
||||
let dir = std::env::current_dir()?;
|
||||
let args = &["--version"];
|
||||
|
||||
if context.verbose {
|
||||
println!("{}", format_command(program, &dir, args));
|
||||
}
|
||||
|
||||
let output = Command::new(program).current_dir(&dir).args(args.iter()).output()?;
|
||||
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else if std::str::from_utf8(&output.stderr)
|
||||
.unwrap_or("")
|
||||
.starts_with("error: 'rustfmt' is not installed")
|
||||
{
|
||||
Err(Error::RustfmtNotInstalled)
|
||||
} else {
|
||||
Err(Error::CommandFailed(
|
||||
format_command(program, &dir, args),
|
||||
std::str::from_utf8(&output.stderr).unwrap_or("").to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn rustfmt(context: &FmtContext, paths: impl Iterator<Item = OsString>) -> Result<(), Error> {
|
||||
let mut args = Vec::new();
|
||||
if context.check {
|
||||
args.push(OsString::from("--check"));
|
||||
}
|
||||
args.extend(paths);
|
||||
exec_fmt_command(context, &context.rustfmt_path, std::env::current_dir()?, &args)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
#![feature(rustc_private, if_let_guard, let_chains)]
|
||||
#![feature(
|
||||
rustc_private,
|
||||
exit_status_error,
|
||||
if_let_guard,
|
||||
let_chains,
|
||||
os_str_slice,
|
||||
os_string_truncate,
|
||||
slice_split_once
|
||||
)]
|
||||
#![warn(
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ fn main() {
|
|||
allow_staged,
|
||||
allow_no_vcs,
|
||||
} => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs),
|
||||
DevCommand::Fmt { check, verbose } => fmt::run(check, verbose),
|
||||
DevCommand::Fmt { check } => fmt::run(utils::UpdateMode::from_check(check)),
|
||||
DevCommand::UpdateLints { check } => update_lints::update(utils::UpdateMode::from_check(check)),
|
||||
DevCommand::NewLint {
|
||||
pass,
|
||||
|
|
@ -125,9 +125,6 @@ enum DevCommand {
|
|||
#[arg(long)]
|
||||
/// Use the rustfmt --check option
|
||||
check: bool,
|
||||
#[arg(short, long)]
|
||||
/// Echo commands run
|
||||
verbose: bool,
|
||||
},
|
||||
#[command(name = "update_lints")]
|
||||
/// Updates lint registration and information from the source code
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::utils::{FileUpdater, Version, update_text_region_fn};
|
||||
use crate::utils::{FileUpdater, UpdateStatus, Version, parse_cargo_package};
|
||||
use std::fmt::Write;
|
||||
|
||||
static CARGO_TOML_FILES: &[&str] = &[
|
||||
|
|
@ -13,15 +13,17 @@ pub fn bump_version(mut version: Version) {
|
|||
|
||||
let mut updater = FileUpdater::default();
|
||||
for file in CARGO_TOML_FILES {
|
||||
updater.update_file(
|
||||
file,
|
||||
&mut update_text_region_fn(
|
||||
"# begin autogenerated version\n",
|
||||
"# end autogenerated version",
|
||||
|dst| {
|
||||
writeln!(dst, "version = \"{}\"", version.toml_display()).unwrap();
|
||||
},
|
||||
),
|
||||
);
|
||||
updater.update_file(file, &mut |_, src, dst| {
|
||||
let package = parse_cargo_package(src);
|
||||
if package.version_range.is_empty() {
|
||||
dst.push_str(src);
|
||||
UpdateStatus::Unchanged
|
||||
} else {
|
||||
dst.push_str(&src[..package.version_range.start]);
|
||||
write!(dst, "\"{}\"", version.toml_display()).unwrap();
|
||||
dst.push_str(&src[package.version_range.end..]);
|
||||
UpdateStatus::from_changed(src.get(package.version_range.clone()) != dst.get(package.version_range))
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use crate::update_lints::{
|
||||
DeprecatedLints, RenamedLint, find_lint_decls, gen_renamed_lints_test_fn, generate_lint_files,
|
||||
read_deprecated_lints,
|
||||
use crate::update_lints::{RenamedLint, find_lint_decls, generate_lint_files, read_deprecated_lints};
|
||||
use crate::utils::{
|
||||
ErrAction, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, Version, delete_dir_if_exists,
|
||||
delete_file_if_exists, expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target,
|
||||
};
|
||||
use crate::utils::{FileUpdater, StringReplacer, UpdateMode, Version, try_rename_file};
|
||||
use std::ffi::OsStr;
|
||||
use rustc_lexer::TokenKind;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
/// Runs the `rename_lint` command.
|
||||
///
|
||||
|
|
@ -22,7 +23,7 @@ use walkdir::WalkDir;
|
|||
/// * If either lint name has a prefix
|
||||
/// * If `old_name` doesn't name an existing lint.
|
||||
/// * If `old_name` names a deprecated or renamed lint.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: bool) {
|
||||
if let Some((prefix, _)) = old_name.split_once("::") {
|
||||
panic!("`{old_name}` should not contain the `{prefix}` prefix");
|
||||
|
|
@ -33,162 +34,362 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
|
|||
|
||||
let mut updater = FileUpdater::default();
|
||||
let mut lints = find_lint_decls();
|
||||
let DeprecatedLints {
|
||||
renamed: mut renamed_lints,
|
||||
deprecated: deprecated_lints,
|
||||
file: mut deprecated_file,
|
||||
contents: mut deprecated_contents,
|
||||
renamed_end,
|
||||
..
|
||||
} = read_deprecated_lints();
|
||||
let (deprecated_lints, mut renamed_lints) = read_deprecated_lints();
|
||||
|
||||
let mut old_lint_index = None;
|
||||
let mut found_new_name = false;
|
||||
for (i, lint) in lints.iter().enumerate() {
|
||||
if lint.name == old_name {
|
||||
old_lint_index = Some(i);
|
||||
} else if lint.name == new_name {
|
||||
found_new_name = true;
|
||||
}
|
||||
}
|
||||
let old_lint_index = old_lint_index.unwrap_or_else(|| panic!("could not find lint `{old_name}`"));
|
||||
let Ok(lint_idx) = lints.binary_search_by(|x| x.name.as_str().cmp(old_name)) else {
|
||||
panic!("could not find lint `{old_name}`");
|
||||
};
|
||||
let lint = &lints[lint_idx];
|
||||
|
||||
let lint = RenamedLint {
|
||||
old_name: format!("clippy::{old_name}"),
|
||||
new_name: if uplift {
|
||||
new_name.into()
|
||||
} else {
|
||||
format!("clippy::{new_name}")
|
||||
},
|
||||
let old_name_prefixed = String::from_iter(["clippy::", old_name]);
|
||||
let new_name_prefixed = if uplift {
|
||||
new_name.to_owned()
|
||||
} else {
|
||||
String::from_iter(["clippy::", new_name])
|
||||
};
|
||||
|
||||
// Renamed lints and deprecated lints shouldn't have been found in the lint list, but check just in
|
||||
// case.
|
||||
assert!(
|
||||
!renamed_lints.iter().any(|l| lint.old_name == l.old_name),
|
||||
"`{old_name}` has already been renamed"
|
||||
);
|
||||
assert!(
|
||||
!deprecated_lints.iter().any(|l| lint.old_name == l.name),
|
||||
"`{old_name}` has already been deprecated"
|
||||
);
|
||||
|
||||
// Update all lint level attributes. (`clippy::lint_name`)
|
||||
let replacements = &[(&*lint.old_name, &*lint.new_name)];
|
||||
let replacer = StringReplacer::new(replacements);
|
||||
for file in WalkDir::new(".").into_iter().map(Result::unwrap).filter(|f| {
|
||||
let name = f.path().file_name();
|
||||
let ext = f.path().extension();
|
||||
(ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed")))
|
||||
&& name != Some(OsStr::new("rename.rs"))
|
||||
&& name != Some(OsStr::new("deprecated_lints.rs"))
|
||||
}) {
|
||||
updater.update_file(file.path(), &mut replacer.replace_ident_fn());
|
||||
for lint in &mut renamed_lints {
|
||||
if lint.new_name == old_name_prefixed {
|
||||
lint.new_name.clone_from(&new_name_prefixed);
|
||||
}
|
||||
}
|
||||
match renamed_lints.binary_search_by(|x| x.old_name.cmp(&old_name_prefixed)) {
|
||||
Ok(_) => {
|
||||
println!("`{old_name}` already has a rename registered");
|
||||
return;
|
||||
},
|
||||
Err(idx) => {
|
||||
renamed_lints.insert(
|
||||
idx,
|
||||
RenamedLint {
|
||||
old_name: old_name_prefixed,
|
||||
new_name: if uplift {
|
||||
new_name.to_owned()
|
||||
} else {
|
||||
String::from_iter(["clippy::", new_name])
|
||||
},
|
||||
version: clippy_version.rust_display().to_string(),
|
||||
},
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
deprecated_contents.insert_str(
|
||||
renamed_end as usize,
|
||||
&format!(
|
||||
" #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n",
|
||||
clippy_version.rust_display(),
|
||||
lint.old_name,
|
||||
lint.new_name,
|
||||
),
|
||||
);
|
||||
deprecated_file.replace_contents(deprecated_contents.as_bytes());
|
||||
drop(deprecated_file);
|
||||
// Some tests are named `lint_name_suffix` which should also be renamed,
|
||||
// but we can't do that if the renamed lint's name overlaps with another
|
||||
// lint. e.g. renaming 'foo' to 'bar' when a lint 'foo_bar' also exists.
|
||||
let change_prefixed_tests = lints.get(lint_idx + 1).is_none_or(|l| !l.name.starts_with(old_name));
|
||||
|
||||
renamed_lints.push(lint);
|
||||
renamed_lints.sort_by(|lhs, rhs| {
|
||||
lhs.new_name
|
||||
.starts_with("clippy::")
|
||||
.cmp(&rhs.new_name.starts_with("clippy::"))
|
||||
.reverse()
|
||||
.then_with(|| lhs.old_name.cmp(&rhs.old_name))
|
||||
});
|
||||
let mut mod_edit = ModEdit::None;
|
||||
if uplift {
|
||||
let is_unique_mod = lints[..lint_idx].iter().any(|l| l.module == lint.module)
|
||||
|| lints[lint_idx + 1..].iter().any(|l| l.module == lint.module);
|
||||
if is_unique_mod {
|
||||
if delete_file_if_exists(lint.path.as_ref()) {
|
||||
mod_edit = ModEdit::Delete;
|
||||
}
|
||||
} else {
|
||||
updater.update_file(&lint.path, &mut |_, src, dst| -> UpdateStatus {
|
||||
let mut start = &src[..lint.declaration_range.start];
|
||||
if start.ends_with("\n\n") {
|
||||
start = &start[..start.len() - 1];
|
||||
}
|
||||
let mut end = &src[lint.declaration_range.end..];
|
||||
if end.starts_with("\n\n") {
|
||||
end = &end[1..];
|
||||
}
|
||||
dst.push_str(start);
|
||||
dst.push_str(end);
|
||||
UpdateStatus::Changed
|
||||
});
|
||||
}
|
||||
delete_test_files(old_name, change_prefixed_tests);
|
||||
lints.remove(lint_idx);
|
||||
} else if lints.binary_search_by(|x| x.name.as_str().cmp(new_name)).is_err() {
|
||||
let lint = &mut lints[lint_idx];
|
||||
if lint.module.ends_with(old_name)
|
||||
&& lint
|
||||
.path
|
||||
.file_stem()
|
||||
.is_some_and(|x| x.as_encoded_bytes() == old_name.as_bytes())
|
||||
{
|
||||
let mut new_path = lint.path.with_file_name(new_name).into_os_string();
|
||||
new_path.push(".rs");
|
||||
if try_rename_file(lint.path.as_ref(), new_path.as_ref()) {
|
||||
mod_edit = ModEdit::Rename;
|
||||
}
|
||||
|
||||
let mod_len = lint.module.len();
|
||||
lint.module.truncate(mod_len - old_name.len());
|
||||
lint.module.push_str(new_name);
|
||||
}
|
||||
rename_test_files(old_name, new_name, change_prefixed_tests);
|
||||
new_name.clone_into(&mut lints[lint_idx].name);
|
||||
lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
|
||||
} else {
|
||||
println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`");
|
||||
println!("Since `{new_name}` already exists the existing code has not been changed");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut update_fn = file_update_fn(old_name, new_name, mod_edit);
|
||||
for e in walk_dir_no_dot_or_target() {
|
||||
let e = expect_action(e, ErrAction::Read, ".");
|
||||
if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") {
|
||||
updater.update_file(e.path(), &mut update_fn);
|
||||
}
|
||||
}
|
||||
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
|
||||
|
||||
if uplift {
|
||||
updater.update_file("tests/ui/rename.rs", &mut gen_renamed_lints_test_fn(&renamed_lints));
|
||||
println!(
|
||||
"`{old_name}` has be uplifted. All the code inside `clippy_lints` related to it needs to be removed manually."
|
||||
);
|
||||
} else if found_new_name {
|
||||
updater.update_file("tests/ui/rename.rs", &mut gen_renamed_lints_test_fn(&renamed_lints));
|
||||
println!(
|
||||
"`{new_name}` is already defined. The old linting code inside `clippy_lints` needs to be updated/removed manually."
|
||||
);
|
||||
} else {
|
||||
// Rename the lint struct and source files sharing a name with the lint.
|
||||
let lint = &mut lints[old_lint_index];
|
||||
let old_name_upper = old_name.to_uppercase();
|
||||
let new_name_upper = new_name.to_uppercase();
|
||||
lint.name = new_name.into();
|
||||
|
||||
// Rename test files. only rename `.stderr` and `.fixed` files if the new test name doesn't exist.
|
||||
if try_rename_file(
|
||||
Path::new(&format!("tests/ui/{old_name}.rs")),
|
||||
Path::new(&format!("tests/ui/{new_name}.rs")),
|
||||
) {
|
||||
try_rename_file(
|
||||
Path::new(&format!("tests/ui/{old_name}.stderr")),
|
||||
Path::new(&format!("tests/ui/{new_name}.stderr")),
|
||||
);
|
||||
try_rename_file(
|
||||
Path::new(&format!("tests/ui/{old_name}.fixed")),
|
||||
Path::new(&format!("tests/ui/{new_name}.fixed")),
|
||||
);
|
||||
}
|
||||
|
||||
// Try to rename the file containing the lint if the file name matches the lint's name.
|
||||
let replacements;
|
||||
let replacements = if lint.module == old_name
|
||||
&& try_rename_file(
|
||||
Path::new(&format!("clippy_lints/src/{old_name}.rs")),
|
||||
Path::new(&format!("clippy_lints/src/{new_name}.rs")),
|
||||
) {
|
||||
// Edit the module name in the lint list. Note there could be multiple lints.
|
||||
for lint in lints.iter_mut().filter(|l| l.module == old_name) {
|
||||
lint.module = new_name.into();
|
||||
}
|
||||
replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
|
||||
replacements.as_slice()
|
||||
} else if !lint.module.contains("::")
|
||||
// Catch cases like `methods/lint_name.rs` where the lint is stored in `methods/mod.rs`
|
||||
&& try_rename_file(
|
||||
Path::new(&format!("clippy_lints/src/{}/{old_name}.rs", lint.module)),
|
||||
Path::new(&format!("clippy_lints/src/{}/{new_name}.rs", lint.module)),
|
||||
)
|
||||
{
|
||||
// Edit the module name in the lint list. Note there could be multiple lints, or none.
|
||||
let renamed_mod = format!("{}::{old_name}", lint.module);
|
||||
for lint in lints.iter_mut().filter(|l| l.module == renamed_mod) {
|
||||
lint.module = format!("{}::{new_name}", lint.module);
|
||||
}
|
||||
replacements = [(&*old_name_upper, &*new_name_upper), (old_name, new_name)];
|
||||
replacements.as_slice()
|
||||
println!("Uplifted `clippy::{old_name}` as `{new_name}`");
|
||||
if matches!(mod_edit, ModEdit::None) {
|
||||
println!("Only the rename has been registered, the code will need to be edited manually");
|
||||
} else {
|
||||
replacements = [(&*old_name_upper, &*new_name_upper), ("", "")];
|
||||
&replacements[0..1]
|
||||
};
|
||||
println!("All the lint's code has been deleted");
|
||||
println!("Make sure to inspect the results as some things may have been missed");
|
||||
}
|
||||
} else {
|
||||
println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`");
|
||||
println!("All code referencing the old name has been updated");
|
||||
println!("Make sure to inspect the results as some things may have been missed");
|
||||
}
|
||||
println!("note: `cargo uibless` still needs to be run to update the test results");
|
||||
}
|
||||
|
||||
// Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being
|
||||
// renamed.
|
||||
let replacer = StringReplacer::new(replacements);
|
||||
for file in WalkDir::new("clippy_lints/src") {
|
||||
let file = file.expect("error reading `clippy_lints/src`");
|
||||
if file
|
||||
.path()
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.is_some_and(|x| x.ends_with("*.rs") && x["clippy_lints/src/".len()..] != *"deprecated_lints.rs")
|
||||
{
|
||||
updater.update_file(file.path(), &mut replacer.replace_ident_fn());
|
||||
#[derive(Clone, Copy)]
|
||||
enum ModEdit {
|
||||
None,
|
||||
Delete,
|
||||
Rename,
|
||||
}
|
||||
|
||||
fn collect_ui_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) {
|
||||
for e in fs::read_dir("tests/ui").expect("error reading `tests/ui`") {
|
||||
let e = e.expect("error reading `tests/ui`");
|
||||
let name = e.file_name();
|
||||
if let Some((name_only, _)) = name.as_encoded_bytes().split_once(|&x| x == b'.') {
|
||||
if name_only.starts_with(lint.as_bytes()) && (rename_prefixed || name_only.len() == lint.len()) {
|
||||
dst.push((name, true));
|
||||
}
|
||||
} else if name.as_encoded_bytes().starts_with(lint.as_bytes()) && (rename_prefixed || name.len() == lint.len())
|
||||
{
|
||||
dst.push((name, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_ui_toml_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) {
|
||||
if rename_prefixed {
|
||||
for e in fs::read_dir("tests/ui-toml").expect("error reading `tests/ui-toml`") {
|
||||
let e = e.expect("error reading `tests/ui-toml`");
|
||||
let name = e.file_name();
|
||||
if name.as_encoded_bytes().starts_with(lint.as_bytes()) && e.file_type().is_ok_and(|ty| ty.is_dir()) {
|
||||
dst.push((name, false));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dst.push((lint.into(), false));
|
||||
}
|
||||
}
|
||||
|
||||
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
|
||||
println!("{old_name} has been successfully renamed");
|
||||
/// Renames all test files for the given lint.
|
||||
///
|
||||
/// If `rename_prefixed` is `true` this will also rename tests which have the lint name as a prefix.
|
||||
fn rename_test_files(old_name: &str, new_name: &str, rename_prefixed: bool) {
|
||||
let mut tests = Vec::new();
|
||||
|
||||
let mut old_buf = OsString::from("tests/ui/");
|
||||
let mut new_buf = OsString::from("tests/ui/");
|
||||
collect_ui_test_names(old_name, rename_prefixed, &mut tests);
|
||||
for &(ref name, is_file) in &tests {
|
||||
old_buf.push(name);
|
||||
new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]);
|
||||
if is_file {
|
||||
try_rename_file(old_buf.as_ref(), new_buf.as_ref());
|
||||
} else {
|
||||
try_rename_dir(old_buf.as_ref(), new_buf.as_ref());
|
||||
}
|
||||
old_buf.truncate("tests/ui/".len());
|
||||
new_buf.truncate("tests/ui/".len());
|
||||
}
|
||||
|
||||
println!("note: `cargo uitest` still needs to be run to update the test results");
|
||||
tests.clear();
|
||||
old_buf.truncate("tests/ui".len());
|
||||
new_buf.truncate("tests/ui".len());
|
||||
old_buf.push("-toml/");
|
||||
new_buf.push("-toml/");
|
||||
collect_ui_toml_test_names(old_name, rename_prefixed, &mut tests);
|
||||
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());
|
||||
old_buf.truncate("tests/ui/".len());
|
||||
new_buf.truncate("tests/ui/".len());
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_test_files(lint: &str, rename_prefixed: bool) {
|
||||
let mut tests = Vec::new();
|
||||
|
||||
let mut buf = OsString::from("tests/ui/");
|
||||
collect_ui_test_names(lint, rename_prefixed, &mut tests);
|
||||
for &(ref name, is_file) in &tests {
|
||||
buf.push(name);
|
||||
if is_file {
|
||||
delete_file_if_exists(buf.as_ref());
|
||||
} else {
|
||||
delete_dir_if_exists(buf.as_ref());
|
||||
}
|
||||
buf.truncate("tests/ui/".len());
|
||||
}
|
||||
|
||||
buf.truncate("tests/ui".len());
|
||||
buf.push("-toml/");
|
||||
|
||||
tests.clear();
|
||||
collect_ui_toml_test_names(lint, rename_prefixed, &mut tests);
|
||||
for (name, _) in &tests {
|
||||
buf.push(name);
|
||||
delete_dir_if_exists(buf.as_ref());
|
||||
buf.truncate("tests/ui/".len());
|
||||
}
|
||||
}
|
||||
|
||||
fn snake_to_pascal(s: &str) -> String {
|
||||
let mut dst = Vec::with_capacity(s.len());
|
||||
let mut iter = s.bytes();
|
||||
|| -> Option<()> {
|
||||
dst.push(iter.next()?.to_ascii_uppercase());
|
||||
while let Some(c) = iter.next() {
|
||||
if c == b'_' {
|
||||
dst.push(iter.next()?.to_ascii_uppercase());
|
||||
} else {
|
||||
dst.push(c);
|
||||
}
|
||||
}
|
||||
Some(())
|
||||
}();
|
||||
String::from_utf8(dst).unwrap()
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn file_update_fn<'a, 'b>(
|
||||
old_name: &'a str,
|
||||
new_name: &'b str,
|
||||
mod_edit: ModEdit,
|
||||
) -> impl use<'a, 'b> + FnMut(&Path, &str, &mut String) -> UpdateStatus {
|
||||
let old_name_pascal = snake_to_pascal(old_name);
|
||||
let new_name_pascal = snake_to_pascal(new_name);
|
||||
let old_name_upper = old_name.to_ascii_uppercase();
|
||||
let new_name_upper = new_name.to_ascii_uppercase();
|
||||
move |_, src, dst| {
|
||||
let mut copy_pos = 0u32;
|
||||
let mut changed = false;
|
||||
let mut searcher = RustSearcher::new(src);
|
||||
let mut capture = "";
|
||||
loop {
|
||||
match searcher.peek() {
|
||||
TokenKind::Eof => break,
|
||||
TokenKind::Ident => {
|
||||
let match_start = searcher.pos();
|
||||
let text = searcher.peek_text();
|
||||
searcher.step();
|
||||
match text {
|
||||
// clippy::line_name or clippy::lint-name
|
||||
"clippy" => {
|
||||
if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture])
|
||||
&& capture == old_name
|
||||
{
|
||||
dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]);
|
||||
dst.push_str(new_name);
|
||||
copy_pos = searcher.pos();
|
||||
changed = true;
|
||||
}
|
||||
},
|
||||
// mod lint_name
|
||||
"mod" => {
|
||||
if !matches!(mod_edit, ModEdit::None)
|
||||
&& searcher.match_tokens(&[Token::CaptureIdent], &mut [&mut capture])
|
||||
&& capture == old_name
|
||||
{
|
||||
match mod_edit {
|
||||
ModEdit::Rename => {
|
||||
dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]);
|
||||
dst.push_str(new_name);
|
||||
copy_pos = searcher.pos();
|
||||
changed = true;
|
||||
},
|
||||
ModEdit::Delete if searcher.match_tokens(&[Token::Semi], &mut []) => {
|
||||
let mut start = &src[copy_pos as usize..match_start as usize];
|
||||
if start.ends_with("\n\n") {
|
||||
start = &start[..start.len() - 1];
|
||||
}
|
||||
dst.push_str(start);
|
||||
copy_pos = searcher.pos();
|
||||
if src[copy_pos as usize..].starts_with("\n\n") {
|
||||
copy_pos += 1;
|
||||
}
|
||||
changed = true;
|
||||
},
|
||||
ModEdit::Delete | ModEdit::None => {},
|
||||
}
|
||||
}
|
||||
},
|
||||
// lint_name::
|
||||
name if matches!(mod_edit, ModEdit::Rename) && name == old_name => {
|
||||
let name_end = searcher.pos();
|
||||
if searcher.match_tokens(&[Token::DoubleColon], &mut []) {
|
||||
dst.push_str(&src[copy_pos as usize..match_start as usize]);
|
||||
dst.push_str(new_name);
|
||||
copy_pos = name_end;
|
||||
changed = true;
|
||||
}
|
||||
},
|
||||
// LINT_NAME or LintName
|
||||
name => {
|
||||
let replacement = if name == old_name_upper {
|
||||
&new_name_upper
|
||||
} else if name == old_name_pascal {
|
||||
&new_name_pascal
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
dst.push_str(&src[copy_pos as usize..match_start as usize]);
|
||||
dst.push_str(replacement);
|
||||
copy_pos = searcher.pos();
|
||||
changed = true;
|
||||
},
|
||||
}
|
||||
},
|
||||
// //~ lint_name
|
||||
TokenKind::LineComment { doc_style: None } => {
|
||||
let text = searcher.peek_text();
|
||||
if text.starts_with("//~")
|
||||
&& let Some(text) = text.strip_suffix(old_name)
|
||||
&& !text.ends_with(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_'))
|
||||
{
|
||||
dst.push_str(&src[copy_pos as usize..searcher.pos() as usize + text.len()]);
|
||||
dst.push_str(new_name);
|
||||
copy_pos = searcher.pos() + searcher.peek_len();
|
||||
changed = true;
|
||||
}
|
||||
searcher.step();
|
||||
},
|
||||
// ::lint_name
|
||||
TokenKind::Colon
|
||||
if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture])
|
||||
&& capture == old_name =>
|
||||
{
|
||||
dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]);
|
||||
dst.push_str(new_name);
|
||||
copy_pos = searcher.pos();
|
||||
changed = true;
|
||||
},
|
||||
_ => searcher.step(),
|
||||
}
|
||||
}
|
||||
|
||||
dst.push_str(&src[copy_pos as usize..]);
|
||||
UpdateStatus::from_changed(changed)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,22 @@ use std::fmt::Write;
|
|||
|
||||
pub fn update_nightly() {
|
||||
let date = Utc::now().format("%Y-%m-%d").to_string();
|
||||
let update = &mut update_text_region_fn(
|
||||
let toolchain_update = &mut update_text_region_fn(
|
||||
"# begin autogenerated nightly\n",
|
||||
"# end autogenerated nightly",
|
||||
|dst| {
|
||||
writeln!(dst, "channel = \"nightly-{date}\"").unwrap();
|
||||
},
|
||||
);
|
||||
let readme_update = &mut update_text_region_fn(
|
||||
"<!-- begin autogenerated nightly -->\n",
|
||||
"<!-- end autogenerated nightly -->",
|
||||
|dst| {
|
||||
writeln!(dst, "```\nnightly-{date}\n```").unwrap();
|
||||
},
|
||||
);
|
||||
|
||||
let mut updater = FileUpdater::default();
|
||||
updater.update_file("rust-toolchain.toml", update);
|
||||
updater.update_file("clippy_utils/README.md", update);
|
||||
updater.update_file("rust-toolchain.toml", toolchain_update);
|
||||
updater.update_file("clippy_utils/README.md", readme_update);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
use crate::utils::{
|
||||
File, FileAction, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, panic_file, update_text_region_fn,
|
||||
ErrAction, File, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, expect_action, update_text_region_fn,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Write;
|
||||
use std::fs::OpenOptions;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\
|
||||
|
|
@ -26,12 +25,11 @@ const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.ht
|
|||
/// Panics if a file path could not read from or then written to
|
||||
pub fn update(update_mode: UpdateMode) {
|
||||
let lints = find_lint_decls();
|
||||
let DeprecatedLints {
|
||||
renamed, deprecated, ..
|
||||
} = read_deprecated_lints();
|
||||
let (deprecated, renamed) = read_deprecated_lints();
|
||||
generate_lint_files(update_mode, &lints, &deprecated, &renamed);
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn generate_lint_files(
|
||||
update_mode: UpdateMode,
|
||||
lints: &[Lint],
|
||||
|
|
@ -93,6 +91,40 @@ pub fn generate_lint_files(
|
|||
dst.push_str("];\n");
|
||||
UpdateStatus::from_changed(src != dst)
|
||||
}),
|
||||
("clippy_lints/src/deprecated_lints.rs", &mut |_, src, dst| {
|
||||
let mut searcher = RustSearcher::new(src);
|
||||
assert!(
|
||||
searcher.find_token(Token::Ident("declare_with_version"))
|
||||
&& searcher.find_token(Token::Ident("declare_with_version")),
|
||||
"error reading deprecated lints"
|
||||
);
|
||||
dst.push_str(&src[..searcher.pos() as usize]);
|
||||
dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n");
|
||||
for lint in deprecated {
|
||||
write!(
|
||||
dst,
|
||||
" #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n",
|
||||
lint.version, lint.name, lint.reason,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
dst.push_str(
|
||||
"]}\n\n\
|
||||
#[rustfmt::skip]\n\
|
||||
declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\
|
||||
",
|
||||
);
|
||||
for lint in renamed {
|
||||
write!(
|
||||
dst,
|
||||
" #[clippy::version = \"{}\"]\n (\"{}\", \"{}\"),\n",
|
||||
lint.version, lint.old_name, lint.new_name,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
dst.push_str("]}\n");
|
||||
UpdateStatus::from_changed(src != dst)
|
||||
}),
|
||||
("tests/ui/deprecated.rs", &mut |_, src, dst| {
|
||||
dst.push_str(GENERATED_FILE_COMMENT);
|
||||
for lint in deprecated {
|
||||
|
|
@ -101,7 +133,24 @@ pub fn generate_lint_files(
|
|||
dst.push_str("\nfn main() {}\n");
|
||||
UpdateStatus::from_changed(src != dst)
|
||||
}),
|
||||
("tests/ui/rename.rs", &mut gen_renamed_lints_test_fn(renamed)),
|
||||
("tests/ui/rename.rs", &mut move |_, src, dst| {
|
||||
let mut seen_lints = HashSet::new();
|
||||
dst.push_str(GENERATED_FILE_COMMENT);
|
||||
dst.push_str("#![allow(clippy::duplicated_attributes)]\n");
|
||||
for lint in renamed {
|
||||
if seen_lints.insert(&lint.new_name) {
|
||||
writeln!(dst, "#![allow({})]", lint.new_name).unwrap();
|
||||
}
|
||||
}
|
||||
seen_lints.clear();
|
||||
for lint in renamed {
|
||||
if seen_lints.insert(&lint.old_name) {
|
||||
writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap();
|
||||
}
|
||||
}
|
||||
dst.push_str("\nfn main() {}\n");
|
||||
UpdateStatus::from_changed(src != dst)
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
@ -111,44 +160,25 @@ fn round_to_fifty(count: usize) -> usize {
|
|||
}
|
||||
|
||||
/// Lint data parsed from the Clippy source code.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct Lint {
|
||||
pub name: String,
|
||||
pub group: String,
|
||||
pub module: String,
|
||||
pub path: PathBuf,
|
||||
pub declaration_range: Range<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct DeprecatedLint {
|
||||
pub name: String,
|
||||
pub reason: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
pub struct RenamedLint {
|
||||
pub old_name: String,
|
||||
pub new_name: String,
|
||||
}
|
||||
|
||||
pub fn gen_renamed_lints_test_fn(lints: &[RenamedLint]) -> impl Fn(&Path, &str, &mut String) -> UpdateStatus {
|
||||
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 lints {
|
||||
if seen_lints.insert(&lint.new_name) {
|
||||
writeln!(dst, "#![allow({})]", lint.new_name).unwrap();
|
||||
}
|
||||
}
|
||||
seen_lints.clear();
|
||||
for lint in lints {
|
||||
if seen_lints.insert(&lint.old_name) {
|
||||
writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap();
|
||||
}
|
||||
}
|
||||
dst.push_str("\nfn main() {}\n");
|
||||
UpdateStatus::from_changed(src != dst)
|
||||
}
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
/// Finds all lint declarations (`declare_clippy_lint!`)
|
||||
|
|
@ -158,6 +188,7 @@ pub fn find_lint_decls() -> Vec<Lint> {
|
|||
let mut contents = String::new();
|
||||
for (file, module) in read_src_with_module("clippy_lints/src".as_ref()) {
|
||||
parse_clippy_lint_decls(
|
||||
file.path(),
|
||||
File::open_read_to_cleared_string(file.path(), &mut contents),
|
||||
&module,
|
||||
&mut lints,
|
||||
|
|
@ -170,10 +201,7 @@ pub fn find_lint_decls() -> Vec<Lint> {
|
|||
/// Reads the source files from the given root directory
|
||||
fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator<Item = (DirEntry, String)> {
|
||||
WalkDir::new(src_root).into_iter().filter_map(move |e| {
|
||||
let e = match e {
|
||||
Ok(e) => e,
|
||||
Err(ref e) => panic_file(e, FileAction::Read, src_root),
|
||||
};
|
||||
let e = expect_action(e, ErrAction::Read, src_root);
|
||||
let path = e.path().as_os_str().as_encoded_bytes();
|
||||
if let Some(path) = path.strip_suffix(b".rs")
|
||||
&& let Some(path) = path.get("clippy_lints/src/".len()..)
|
||||
|
|
@ -202,17 +230,17 @@ fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator<Item = (DirE
|
|||
}
|
||||
|
||||
/// Parse a source file looking for `declare_clippy_lint` macro invocations.
|
||||
fn parse_clippy_lint_decls(contents: &str, module: &str, lints: &mut Vec<Lint>) {
|
||||
fn parse_clippy_lint_decls(path: &Path, contents: &str, module: &str, lints: &mut Vec<Lint>) {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use Token::*;
|
||||
#[rustfmt::skip]
|
||||
static DECL_TOKENS: &[Token] = &[
|
||||
static DECL_TOKENS: &[Token<'_>] = &[
|
||||
// !{ /// docs
|
||||
Bang, OpenBrace, AnyDoc,
|
||||
Bang, OpenBrace, AnyComment,
|
||||
// #[clippy::version = "version"]
|
||||
Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket,
|
||||
// pub NAME, GROUP,
|
||||
Ident("pub"), CaptureIdent, Comma, CaptureIdent, Comma,
|
||||
Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma,
|
||||
];
|
||||
|
||||
let mut searcher = RustSearcher::new(contents);
|
||||
|
|
@ -224,55 +252,42 @@ fn parse_clippy_lint_decls(contents: &str, module: &str, lints: &mut Vec<Lint>)
|
|||
name: name.to_lowercase(),
|
||||
group: group.into(),
|
||||
module: module.into(),
|
||||
path: path.into(),
|
||||
declaration_range: start..searcher.pos() as usize,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeprecatedLints {
|
||||
pub file: File<'static>,
|
||||
pub contents: String,
|
||||
pub deprecated: Vec<DeprecatedLint>,
|
||||
pub renamed: Vec<RenamedLint>,
|
||||
pub deprecated_end: u32,
|
||||
pub renamed_end: u32,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn read_deprecated_lints() -> DeprecatedLints {
|
||||
pub fn read_deprecated_lints() -> (Vec<DeprecatedLint>, Vec<RenamedLint>) {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use Token::*;
|
||||
#[rustfmt::skip]
|
||||
static DECL_TOKENS: &[Token] = &[
|
||||
static DECL_TOKENS: &[Token<'_>] = &[
|
||||
// #[clippy::version = "version"]
|
||||
Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket,
|
||||
Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, CaptureLitStr, CloseBracket,
|
||||
// ("first", "second"),
|
||||
OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, Comma,
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
static DEPRECATED_TOKENS: &[Token] = &[
|
||||
static DEPRECATED_TOKENS: &[Token<'_>] = &[
|
||||
// !{ DEPRECATED(DEPRECATED_VERSION) = [
|
||||
Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket,
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
static RENAMED_TOKENS: &[Token] = &[
|
||||
static RENAMED_TOKENS: &[Token<'_>] = &[
|
||||
// !{ RENAMED(RENAMED_VERSION) = [
|
||||
Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket,
|
||||
];
|
||||
|
||||
let path = "clippy_lints/src/deprecated_lints.rs";
|
||||
let mut res = DeprecatedLints {
|
||||
file: File::open(path, OpenOptions::new().read(true).write(true)),
|
||||
contents: String::new(),
|
||||
deprecated: Vec::with_capacity(30),
|
||||
renamed: Vec::with_capacity(80),
|
||||
deprecated_end: 0,
|
||||
renamed_end: 0,
|
||||
};
|
||||
let mut deprecated = Vec::with_capacity(30);
|
||||
let mut renamed = Vec::with_capacity(80);
|
||||
let mut contents = String::new();
|
||||
File::open_read_to_cleared_string(path, &mut contents);
|
||||
|
||||
res.file.read_append_to_string(&mut res.contents);
|
||||
let mut searcher = RustSearcher::new(&res.contents);
|
||||
let mut searcher = RustSearcher::new(&contents);
|
||||
|
||||
// First instance is the macro definition.
|
||||
assert!(
|
||||
|
|
@ -281,36 +296,38 @@ pub fn read_deprecated_lints() -> DeprecatedLints {
|
|||
);
|
||||
|
||||
if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(DEPRECATED_TOKENS, &mut []) {
|
||||
let mut version = "";
|
||||
let mut name = "";
|
||||
let mut reason = "";
|
||||
while searcher.match_tokens(DECL_TOKENS, &mut [&mut name, &mut reason]) {
|
||||
res.deprecated.push(DeprecatedLint {
|
||||
while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut name, &mut reason]) {
|
||||
deprecated.push(DeprecatedLint {
|
||||
name: parse_str_single_line(path.as_ref(), name),
|
||||
reason: parse_str_single_line(path.as_ref(), reason),
|
||||
version: parse_str_single_line(path.as_ref(), version),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
panic!("error reading deprecated lints");
|
||||
}
|
||||
// position of the closing `]}` of `declare_with_version`
|
||||
res.deprecated_end = searcher.pos();
|
||||
|
||||
if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(RENAMED_TOKENS, &mut []) {
|
||||
let mut version = "";
|
||||
let mut old_name = "";
|
||||
let mut new_name = "";
|
||||
while searcher.match_tokens(DECL_TOKENS, &mut [&mut old_name, &mut new_name]) {
|
||||
res.renamed.push(RenamedLint {
|
||||
while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut old_name, &mut new_name]) {
|
||||
renamed.push(RenamedLint {
|
||||
old_name: parse_str_single_line(path.as_ref(), old_name),
|
||||
new_name: parse_str_single_line(path.as_ref(), new_name),
|
||||
version: parse_str_single_line(path.as_ref(), version),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
panic!("error reading renamed lints");
|
||||
}
|
||||
// position of the closing `]}` of `declare_with_version`
|
||||
res.renamed_end = searcher.pos();
|
||||
|
||||
res
|
||||
deprecated.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
|
||||
renamed.sort_by(|lhs, rhs| lhs.old_name.cmp(&rhs.old_name));
|
||||
(deprecated, renamed)
|
||||
}
|
||||
|
||||
/// Removes the line splices and surrounding quotes from a string literal
|
||||
|
|
@ -366,7 +383,7 @@ mod tests {
|
|||
}
|
||||
"#;
|
||||
let mut result = Vec::new();
|
||||
parse_clippy_lint_decls(CONTENTS, "module_name", &mut result);
|
||||
parse_clippy_lint_decls("".as_ref(), CONTENTS, "module_name", &mut result);
|
||||
for r in &mut result {
|
||||
r.declaration_range = Range::default();
|
||||
}
|
||||
|
|
@ -376,12 +393,14 @@ mod tests {
|
|||
name: "ptr_arg".into(),
|
||||
group: "style".into(),
|
||||
module: "module_name".into(),
|
||||
path: PathBuf::new(),
|
||||
declaration_range: Range::default(),
|
||||
},
|
||||
Lint {
|
||||
name: "doc_markdown".into(),
|
||||
group: "pedantic".into(),
|
||||
module: "module_name".into(),
|
||||
path: PathBuf::new(),
|
||||
declaration_range: Range::default(),
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
use aho_corasick::{AhoCorasick, AhoCorasickBuilder};
|
||||
use core::fmt::{self, Display};
|
||||
use core::num::NonZero;
|
||||
use core::ops::Range;
|
||||
use core::slice;
|
||||
use core::str::FromStr;
|
||||
use rustc_lexer::{self as lexer, FrontmatterAllowed};
|
||||
use std::env;
|
||||
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::process::{self, ExitStatus};
|
||||
use std::process::{self, Command, ExitStatus, Stdio};
|
||||
use std::{env, thread};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
|
||||
|
|
@ -15,14 +18,16 @@ static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
|
|||
static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum FileAction {
|
||||
pub enum ErrAction {
|
||||
Open,
|
||||
Read,
|
||||
Write,
|
||||
Create,
|
||||
Rename,
|
||||
Delete,
|
||||
Run,
|
||||
}
|
||||
impl FileAction {
|
||||
impl ErrAction {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Open => "opening",
|
||||
|
|
@ -30,16 +35,26 @@ impl FileAction {
|
|||
Self::Write => "writing",
|
||||
Self::Create => "creating",
|
||||
Self::Rename => "renaming",
|
||||
Self::Delete => "deleting",
|
||||
Self::Run => "running",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[track_caller]
|
||||
pub fn panic_file(err: &impl Display, action: FileAction, path: &Path) -> ! {
|
||||
pub fn panic_action(err: &impl Display, action: ErrAction, path: &Path) -> ! {
|
||||
panic!("error {} `{}`: {}", action.as_str(), path.display(), *err)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn expect_action<T>(res: Result<T, impl Display>, action: ErrAction, path: impl AsRef<Path>) -> T {
|
||||
match res {
|
||||
Ok(x) => x,
|
||||
Err(ref e) => panic_action(e, action, path.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around `std::fs::File` which panics with a path on failure.
|
||||
pub struct File<'a> {
|
||||
pub inner: fs::File,
|
||||
|
|
@ -50,9 +65,9 @@ impl<'a> File<'a> {
|
|||
#[track_caller]
|
||||
pub fn open(path: &'a (impl AsRef<Path> + ?Sized), options: &mut OpenOptions) -> Self {
|
||||
let path = path.as_ref();
|
||||
match options.open(path) {
|
||||
Ok(inner) => Self { inner, path },
|
||||
Err(e) => panic_file(&e, FileAction::Open, path),
|
||||
Self {
|
||||
inner: expect_action(options.open(path), ErrAction::Open, path),
|
||||
path,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +78,7 @@ impl<'a> File<'a> {
|
|||
match options.open(path) {
|
||||
Ok(inner) => Some(Self { inner, path }),
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => None,
|
||||
Err(e) => panic_file(&e, FileAction::Open, path),
|
||||
Err(e) => panic_action(&e, ErrAction::Open, path),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,10 +94,7 @@ impl<'a> File<'a> {
|
|||
/// Read the entire contents of a file to the given buffer.
|
||||
#[track_caller]
|
||||
pub fn read_append_to_string<'dst>(&mut self, dst: &'dst mut String) -> &'dst mut String {
|
||||
match self.inner.read_to_string(dst) {
|
||||
Ok(_) => {},
|
||||
Err(e) => panic_file(&e, FileAction::Read, self.path),
|
||||
}
|
||||
expect_action(self.inner.read_to_string(dst), ErrAction::Read, self.path);
|
||||
dst
|
||||
}
|
||||
|
||||
|
|
@ -102,9 +114,7 @@ impl<'a> File<'a> {
|
|||
},
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
if let Err(e) = res {
|
||||
panic_file(&e, FileAction::Write, self.path);
|
||||
}
|
||||
expect_action(res, ErrAction::Write, self.path);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -165,9 +175,83 @@ impl Version {
|
|||
}
|
||||
}
|
||||
|
||||
enum TomlPart<'a> {
|
||||
Table(&'a str),
|
||||
Value(&'a str, &'a str),
|
||||
}
|
||||
|
||||
fn toml_iter(s: &str) -> impl Iterator<Item = (usize, TomlPart<'_>)> {
|
||||
let mut pos = 0;
|
||||
s.split('\n')
|
||||
.map(move |s| {
|
||||
let x = pos;
|
||||
pos += s.len() + 1;
|
||||
(x, s)
|
||||
})
|
||||
.filter_map(|(pos, s)| {
|
||||
if let Some(s) = s.strip_prefix('[') {
|
||||
s.split_once(']').map(|(name, _)| (pos, TomlPart::Table(name)))
|
||||
} else if matches!(s.bytes().next(), Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')) {
|
||||
s.split_once('=').map(|(key, value)| (pos, TomlPart::Value(key, value)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub struct CargoPackage<'a> {
|
||||
pub name: &'a str,
|
||||
pub version_range: Range<usize>,
|
||||
pub not_a_platform_range: Range<usize>,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn parse_cargo_package(s: &str) -> CargoPackage<'_> {
|
||||
let mut in_package = false;
|
||||
let mut in_platform_deps = false;
|
||||
let mut name = "";
|
||||
let mut version_range = 0..0;
|
||||
let mut not_a_platform_range = 0..0;
|
||||
for (offset, part) in toml_iter(s) {
|
||||
match part {
|
||||
TomlPart::Table(name) => {
|
||||
if in_platform_deps {
|
||||
not_a_platform_range.end = offset;
|
||||
}
|
||||
in_package = false;
|
||||
in_platform_deps = false;
|
||||
|
||||
match name.trim() {
|
||||
"package" => in_package = true,
|
||||
"target.'cfg(NOT_A_PLATFORM)'.dependencies" => {
|
||||
in_platform_deps = true;
|
||||
not_a_platform_range.start = offset;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
TomlPart::Value(key, value) if in_package => match key.trim_end() {
|
||||
"name" => name = value.trim(),
|
||||
"version" => {
|
||||
version_range.start = offset + (value.len() - value.trim().len()) + key.len() + 1;
|
||||
version_range.end = offset + key.len() + value.trim_end().len() + 1;
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
TomlPart::Value(..) => {},
|
||||
}
|
||||
}
|
||||
CargoPackage {
|
||||
name,
|
||||
version_range,
|
||||
not_a_platform_range,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClippyInfo {
|
||||
pub path: PathBuf,
|
||||
pub version: Version,
|
||||
pub has_intellij_hook: bool,
|
||||
}
|
||||
impl ClippyInfo {
|
||||
#[must_use]
|
||||
|
|
@ -177,35 +261,21 @@ impl ClippyInfo {
|
|||
loop {
|
||||
path.push("Cargo.toml");
|
||||
if let Some(mut file) = File::open_if_exists(&path, OpenOptions::new().read(true)) {
|
||||
let mut in_package = false;
|
||||
let mut is_clippy = false;
|
||||
let mut version: Option<Version> = None;
|
||||
|
||||
// Ad-hoc parsing to avoid dependencies. We control all the file so this
|
||||
// isn't actually a problem
|
||||
for line in file.read_to_cleared_string(&mut buf).lines() {
|
||||
if line.starts_with('[') {
|
||||
in_package = line.starts_with("[package]");
|
||||
} else if in_package && let Some((name, value)) = line.split_once('=') {
|
||||
match name.trim() {
|
||||
"name" => is_clippy = value.trim() == "\"clippy\"",
|
||||
"version"
|
||||
if let Some(value) = value.trim().strip_prefix('"')
|
||||
&& let Some(value) = value.strip_suffix('"') =>
|
||||
{
|
||||
version = value.parse().ok();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
file.read_to_cleared_string(&mut buf);
|
||||
let package = parse_cargo_package(&buf);
|
||||
if package.name == "\"clippy\"" {
|
||||
if let Some(version) = buf[package.version_range].strip_prefix('"')
|
||||
&& let Some(version) = version.strip_suffix('"')
|
||||
&& let Ok(version) = version.parse()
|
||||
{
|
||||
path.pop();
|
||||
return ClippyInfo {
|
||||
path,
|
||||
version,
|
||||
has_intellij_hook: !package.not_a_platform_range.is_empty(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if is_clippy {
|
||||
let Some(version) = version else {
|
||||
panic!("error reading clippy version from {}", file.path.display());
|
||||
};
|
||||
path.pop();
|
||||
return ClippyInfo { path, version };
|
||||
panic!("error reading clippy version from `{}`", file.path.display());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -258,6 +328,11 @@ impl UpdateMode {
|
|||
pub fn from_check(check: bool) -> Self {
|
||||
if check { Self::Check } else { Self::Change }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_check(self) -> bool {
|
||||
matches!(self, Self::Check)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -366,53 +441,11 @@ pub fn update_text_region_fn(
|
|||
move |path, src, dst| update_text_region(path, start, end, src, dst, &mut insert)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_ident_char(c: u8) -> bool {
|
||||
matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')
|
||||
}
|
||||
|
||||
pub struct StringReplacer<'a> {
|
||||
searcher: AhoCorasick,
|
||||
replacements: &'a [(&'a str, &'a str)],
|
||||
}
|
||||
impl<'a> StringReplacer<'a> {
|
||||
#[must_use]
|
||||
pub fn new(replacements: &'a [(&'a str, &'a str)]) -> Self {
|
||||
Self {
|
||||
searcher: AhoCorasickBuilder::new()
|
||||
.match_kind(aho_corasick::MatchKind::LeftmostLongest)
|
||||
.build(replacements.iter().map(|&(x, _)| x))
|
||||
.unwrap(),
|
||||
replacements,
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace substrings if they aren't bordered by identifier characters.
|
||||
pub fn replace_ident_fn(&self) -> impl Fn(&Path, &str, &mut String) -> UpdateStatus {
|
||||
move |_, src, dst| {
|
||||
let mut pos = 0;
|
||||
let mut changed = false;
|
||||
for m in self.searcher.find_iter(src) {
|
||||
if !is_ident_char(src.as_bytes().get(m.start().wrapping_sub(1)).copied().unwrap_or(0))
|
||||
&& !is_ident_char(src.as_bytes().get(m.end()).copied().unwrap_or(0))
|
||||
{
|
||||
changed = true;
|
||||
dst.push_str(&src[pos..m.start()]);
|
||||
dst.push_str(self.replacements[m.pattern()].1);
|
||||
pos = m.end();
|
||||
}
|
||||
}
|
||||
dst.push_str(&src[pos..]);
|
||||
UpdateStatus::from_changed(changed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Token {
|
||||
/// Matches any number of doc comments.
|
||||
AnyDoc,
|
||||
Ident(&'static str),
|
||||
pub enum Token<'a> {
|
||||
/// Matches any number of comments / doc comments.
|
||||
AnyComment,
|
||||
Ident(&'a str),
|
||||
CaptureIdent,
|
||||
LitStr,
|
||||
CaptureLitStr,
|
||||
|
|
@ -431,29 +464,26 @@ pub enum Token {
|
|||
OpenBracket,
|
||||
OpenParen,
|
||||
Pound,
|
||||
Semi,
|
||||
Slash,
|
||||
}
|
||||
|
||||
pub struct RustSearcher<'txt> {
|
||||
text: &'txt str,
|
||||
cursor: lexer::Cursor<'txt>,
|
||||
pos: u32,
|
||||
|
||||
// Either the next token or a zero-sized whitespace sentinel.
|
||||
next_token: lexer::Token,
|
||||
}
|
||||
impl<'txt> RustSearcher<'txt> {
|
||||
#[must_use]
|
||||
#[expect(clippy::inconsistent_struct_constructor)]
|
||||
pub fn new(text: &'txt str) -> Self {
|
||||
let mut cursor = lexer::Cursor::new(text, FrontmatterAllowed::Yes);
|
||||
Self {
|
||||
text,
|
||||
cursor: lexer::Cursor::new(text, FrontmatterAllowed::Yes),
|
||||
pos: 0,
|
||||
|
||||
// Sentinel value indicating there is no read token.
|
||||
next_token: lexer::Token {
|
||||
len: 0,
|
||||
kind: lexer::TokenKind::Whitespace,
|
||||
},
|
||||
next_token: cursor.advance_token(),
|
||||
cursor,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -462,6 +492,11 @@ impl<'txt> RustSearcher<'txt> {
|
|||
&self.text[self.pos as usize..(self.pos + self.next_token.len) as usize]
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn peek_len(&self) -> u32 {
|
||||
self.next_token.len
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn peek(&self) -> lexer::TokenKind {
|
||||
self.next_token.kind
|
||||
|
|
@ -485,37 +520,15 @@ impl<'txt> RustSearcher<'txt> {
|
|||
|
||||
/// Consumes the next token if it matches the requested value and captures the value if
|
||||
/// requested. Returns true if a token was matched.
|
||||
fn read_token(&mut self, token: Token, captures: &mut slice::IterMut<'_, &mut &'txt str>) -> bool {
|
||||
fn read_token(&mut self, token: Token<'_>, captures: &mut slice::IterMut<'_, &mut &'txt str>) -> bool {
|
||||
loop {
|
||||
match (token, self.next_token.kind) {
|
||||
// Has to be the first match arm so the empty sentinel token will be handled.
|
||||
// This will also skip all whitespace/comments preceding any tokens.
|
||||
(
|
||||
_,
|
||||
lexer::TokenKind::Whitespace
|
||||
| lexer::TokenKind::LineComment { doc_style: None }
|
||||
| lexer::TokenKind::BlockComment {
|
||||
doc_style: None,
|
||||
terminated: true,
|
||||
},
|
||||
) => {
|
||||
self.step();
|
||||
if self.at_end() {
|
||||
// `AnyDoc` always matches.
|
||||
return matches!(token, Token::AnyDoc);
|
||||
}
|
||||
},
|
||||
(
|
||||
Token::AnyDoc,
|
||||
(_, lexer::TokenKind::Whitespace)
|
||||
| (
|
||||
Token::AnyComment,
|
||||
lexer::TokenKind::BlockComment { terminated: true, .. } | lexer::TokenKind::LineComment { .. },
|
||||
) => {
|
||||
self.step();
|
||||
if self.at_end() {
|
||||
// `AnyDoc` always matches.
|
||||
return true;
|
||||
}
|
||||
},
|
||||
(Token::AnyDoc, _) => return true,
|
||||
) => self.step(),
|
||||
(Token::AnyComment, _) => return true,
|
||||
(Token::Bang, lexer::TokenKind::Bang)
|
||||
| (Token::CloseBrace, lexer::TokenKind::CloseBrace)
|
||||
| (Token::CloseBracket, lexer::TokenKind::CloseBracket)
|
||||
|
|
@ -529,6 +542,8 @@ impl<'txt> RustSearcher<'txt> {
|
|||
| (Token::OpenBracket, lexer::TokenKind::OpenBracket)
|
||||
| (Token::OpenParen, lexer::TokenKind::OpenParen)
|
||||
| (Token::Pound, lexer::TokenKind::Pound)
|
||||
| (Token::Semi, lexer::TokenKind::Semi)
|
||||
| (Token::Slash, lexer::TokenKind::Slash)
|
||||
| (
|
||||
Token::LitStr,
|
||||
lexer::TokenKind::Literal {
|
||||
|
|
@ -569,7 +584,7 @@ impl<'txt> RustSearcher<'txt> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn find_token(&mut self, token: Token) -> bool {
|
||||
pub fn find_token(&mut self, token: Token<'_>) -> bool {
|
||||
let mut capture = [].iter_mut();
|
||||
while !self.read_token(token, &mut capture) {
|
||||
self.step();
|
||||
|
|
@ -581,7 +596,7 @@ impl<'txt> RustSearcher<'txt> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn find_capture_token(&mut self, token: Token) -> Option<&'txt str> {
|
||||
pub fn find_capture_token(&mut self, token: Token<'_>) -> Option<&'txt str> {
|
||||
let mut res = "";
|
||||
let mut capture = &mut res;
|
||||
let mut capture = slice::from_mut(&mut capture).iter_mut();
|
||||
|
|
@ -595,7 +610,7 @@ impl<'txt> RustSearcher<'txt> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn match_tokens(&mut self, tokens: &[Token], captures: &mut [&mut &'txt str]) -> bool {
|
||||
pub fn match_tokens(&mut self, tokens: &[Token<'_>], captures: &mut [&mut &'txt str]) -> bool {
|
||||
let mut captures = captures.iter_mut();
|
||||
tokens.iter().all(|&t| self.read_token(t, &mut captures))
|
||||
}
|
||||
|
|
@ -606,21 +621,160 @@ 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(e) => panic_file(&e, FileAction::Create, new_name),
|
||||
Err(ref e) => panic_action(e, ErrAction::Create, new_name),
|
||||
}
|
||||
match fs::rename(old_name, new_name) {
|
||||
Ok(()) => true,
|
||||
Err(e) => {
|
||||
Err(ref e) => {
|
||||
drop(fs::remove_file(new_name));
|
||||
if e.kind() == io::ErrorKind::NotFound {
|
||||
// `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_file(&e, FileAction::Rename, old_name);
|
||||
panic_action(e, ErrAction::Rename, old_name);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::must_use_candidate)]
|
||||
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 write_file(path: &Path, contents: &str) {
|
||||
fs::write(path, contents).unwrap_or_else(|e| panic_file(&e, FileAction::Write, path));
|
||||
expect_action(fs::write(path, contents), ErrAction::Write, path);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn run_with_output(path: &(impl AsRef<Path> + ?Sized), cmd: &mut Command) -> Vec<u8> {
|
||||
fn f(path: &Path, cmd: &mut Command) -> Vec<u8> {
|
||||
let output = expect_action(
|
||||
cmd.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit())
|
||||
.output(),
|
||||
ErrAction::Run,
|
||||
path,
|
||||
);
|
||||
expect_action(output.status.exit_ok(), ErrAction::Run, path);
|
||||
output.stdout
|
||||
}
|
||||
f(path.as_ref(), cmd)
|
||||
}
|
||||
|
||||
/// Splits an argument list across multiple `Command` invocations.
|
||||
///
|
||||
/// The argument list will be split into a number of batches based on
|
||||
/// `thread::available_parallelism`, with `min_batch_size` setting a lower bound on the size of each
|
||||
/// batch.
|
||||
///
|
||||
/// If the size of the arguments would exceed the system limit additional batches will be created.
|
||||
pub fn split_args_for_threads(
|
||||
min_batch_size: usize,
|
||||
make_cmd: impl FnMut() -> Command,
|
||||
args: impl ExactSizeIterator<Item: AsRef<OsStr>>,
|
||||
) -> impl Iterator<Item = Command> {
|
||||
struct Iter<F, I> {
|
||||
make_cmd: F,
|
||||
args: I,
|
||||
min_batch_size: usize,
|
||||
batch_size: usize,
|
||||
thread_count: usize,
|
||||
}
|
||||
impl<F, I> Iterator for Iter<F, I>
|
||||
where
|
||||
F: FnMut() -> Command,
|
||||
I: ExactSizeIterator<Item: AsRef<OsStr>>,
|
||||
{
|
||||
type Item = Command;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.thread_count > 1 {
|
||||
self.thread_count -= 1;
|
||||
}
|
||||
let mut cmd = (self.make_cmd)();
|
||||
let mut cmd_len = 0usize;
|
||||
for arg in self.args.by_ref().take(self.batch_size) {
|
||||
cmd.arg(arg.as_ref());
|
||||
// `+ 8` to account for the `argv` pointer on unix.
|
||||
// Windows is complicated since the arguments are first converted to UTF-16ish,
|
||||
// but this needs to account for the space between arguments and whatever additional
|
||||
// is needed to escape within an argument.
|
||||
cmd_len += arg.as_ref().len() + 8;
|
||||
cmd_len += 8;
|
||||
|
||||
// Windows has a command length limit of 32767. For unix systems this is more
|
||||
// complicated since the limit includes environment variables and room needs to be
|
||||
// left to edit them once the program starts, but the total size comes from
|
||||
// `getconf ARG_MAX`.
|
||||
//
|
||||
// For simplicity we use 30000 here under a few assumptions.
|
||||
// * Individual arguments aren't super long (the final argument is still added)
|
||||
// * `ARG_MAX` is set to a reasonable amount. Basically every system will be configured way above
|
||||
// what windows supports, but POSIX only requires `4096`.
|
||||
if cmd_len > 30000 {
|
||||
self.batch_size = self.args.len().div_ceil(self.thread_count).max(self.min_batch_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
(cmd_len != 0).then_some(cmd)
|
||||
}
|
||||
}
|
||||
let thread_count = thread::available_parallelism().map_or(1, NonZero::get);
|
||||
let batch_size = args.len().div_ceil(thread_count).max(min_batch_size);
|
||||
Iter {
|
||||
make_cmd,
|
||||
args,
|
||||
min_batch_size,
|
||||
batch_size,
|
||||
thread_count,
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::must_use_candidate)]
|
||||
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_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),
|
||||
}
|
||||
}
|
||||
|
||||
/// Walks all items excluding top-level dot files/directories and any target directories.
|
||||
pub fn walk_dir_no_dot_or_target() -> impl Iterator<Item = ::walkdir::Result<::walkdir::DirEntry>> {
|
||||
WalkDir::new(".").into_iter().filter_entry(|e| {
|
||||
e.path()
|
||||
.file_name()
|
||||
.is_none_or(|x| x != "target" && x.as_encoded_bytes().first().copied() != Some(b'.'))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_lints"
|
||||
# begin autogenerated version
|
||||
version = "0.1.89"
|
||||
# end autogenerated version
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ use clippy_config::types::{
|
|||
};
|
||||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use clippy_utils::is_cfg_test;
|
||||
use rustc_attr_data_structures::AttributeKind;
|
||||
use rustc_hir::{
|
||||
AssocItemKind, FieldDef, HirId, ImplItemRef, IsAuto, Item, ItemKind, Mod, QPath, TraitItemRef, TyKind, Variant,
|
||||
VariantData,
|
||||
AssocItemKind, Attribute, FieldDef, HirId, ImplItemRef, IsAuto, Item, ItemKind, Mod, QPath, TraitItemRef, TyKind,
|
||||
Variant, VariantData,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
|
@ -28,6 +29,11 @@ declare_clippy_lint! {
|
|||
/// implemented in the code. Sometimes this will be referred to as
|
||||
/// "bikeshedding".
|
||||
///
|
||||
/// The content of items with a representation clause attribute, such as
|
||||
/// `#[repr(C)]` will not be checked, as the order of their fields or
|
||||
/// variants might be dictated by an external API (application binary
|
||||
/// interface).
|
||||
///
|
||||
/// ### Default Ordering and Configuration
|
||||
///
|
||||
/// As there is no generally applicable rule, and each project may have
|
||||
|
|
@ -256,8 +262,17 @@ impl ArbitrarySourceItemOrdering {
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
|
||||
if cx
|
||||
.tcx
|
||||
.hir_attrs(item.hir_id())
|
||||
.iter()
|
||||
.any(|attr| matches!(attr, Attribute::Parsed(AttributeKind::Repr(..))))
|
||||
{
|
||||
// Do not lint items with a `#[repr]` attribute as their layout may be imposed by an external API.
|
||||
return;
|
||||
}
|
||||
match &item.kind {
|
||||
ItemKind::Enum(_, enum_def, _generics) if self.enable_ordering_for_enum => {
|
||||
ItemKind::Enum(_, _generics, enum_def) if self.enable_ordering_for_enum => {
|
||||
let mut cur_v: Option<&Variant<'_>> = None;
|
||||
for variant in enum_def.variants {
|
||||
if variant.span.in_external_macro(cx.sess().source_map()) {
|
||||
|
|
@ -273,7 +288,7 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
|
|||
cur_v = Some(variant);
|
||||
}
|
||||
},
|
||||
ItemKind::Struct(_, VariantData::Struct { fields, .. }, _generics) if self.enable_ordering_for_struct => {
|
||||
ItemKind::Struct(_, _generics, VariantData::Struct { fields, .. }) if self.enable_ordering_for_struct => {
|
||||
let mut cur_f: Option<&FieldDef<'_>> = None;
|
||||
for field in *fields {
|
||||
if field.span.in_external_macro(cx.sess().source_map()) {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for ArcWithNonSendSync {
|
|||
&& let arg_ty = cx.typeck_results().expr_ty(arg)
|
||||
// make sure that the type is not and does not contain any type parameters
|
||||
&& arg_ty.walk().all(|arg| {
|
||||
!matches!(arg.unpack(), GenericArgKind::Type(ty) if matches!(ty.kind(), ty::Param(_)))
|
||||
!matches!(arg.kind(), GenericArgKind::Type(ty) if matches!(ty.kind(), ty::Param(_)))
|
||||
})
|
||||
&& let Some(send) = cx.tcx.get_diagnostic_item(sym::Send)
|
||||
&& let Some(sync) = cx.tcx.lang_items().sync_trait()
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@ use clippy_utils::macros::{PanicExpn, find_assert_args, root_macro_call_first_no
|
|||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::{has_debug_impl, is_copy, is_type_diagnostic_item};
|
||||
use clippy_utils::usage::local_used_after_expr;
|
||||
use clippy_utils::{is_expr_final_block_expr, path_res};
|
||||
use clippy_utils::{is_expr_final_block_expr, path_res, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -68,11 +67,11 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates {
|
|||
return;
|
||||
}
|
||||
}
|
||||
let (message, replacement) = match method_segment.ident.as_str() {
|
||||
"is_ok" if type_suitable_to_unwrap(cx, args.type_at(1)) => {
|
||||
let (message, replacement) = match method_segment.ident.name {
|
||||
sym::is_ok if type_suitable_to_unwrap(cx, args.type_at(1)) => {
|
||||
("called `assert!` with `Result::is_ok`", "unwrap")
|
||||
},
|
||||
"is_err" if type_suitable_to_unwrap(cx, args.type_at(0)) => {
|
||||
sym::is_err if type_suitable_to_unwrap(cx, args.type_at(0)) => {
|
||||
("called `assert!` with `Result::is_err`", "unwrap_err")
|
||||
},
|
||||
_ => return,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
|
|||
if signed {
|
||||
return nbits;
|
||||
}
|
||||
let max_bits = if method.ident.as_str() == "min" {
|
||||
let max_bits = if method.ident.name == sym::min {
|
||||
get_constant_bits(cx, right)
|
||||
} else {
|
||||
None
|
||||
|
|
@ -64,7 +64,7 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
|
|||
apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::MAX))
|
||||
},
|
||||
ExprKind::MethodCall(method, _, [lo, hi], _) => {
|
||||
if method.ident.as_str() == "clamp"
|
||||
if method.ident.name == sym::clamp
|
||||
//FIXME: make this a diagnostic item
|
||||
&& let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ use std::ops::ControlFlow;
|
|||
use clippy_utils::consts::{ConstEvalCtxt, Constant};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
|
||||
use clippy_utils::{method_chain_args, sext};
|
||||
use clippy_utils::{method_chain_args, sext, sym};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::Symbol;
|
||||
|
||||
use super::CAST_SIGN_LOSS;
|
||||
|
||||
|
|
@ -16,24 +17,24 @@ use super::CAST_SIGN_LOSS;
|
|||
///
|
||||
/// Methods that can overflow and return a negative value must not be included in this list,
|
||||
/// because casting their return values can still result in sign loss.
|
||||
const METHODS_RET_POSITIVE: &[&str] = &[
|
||||
"checked_abs",
|
||||
"saturating_abs",
|
||||
"isqrt",
|
||||
"checked_isqrt",
|
||||
"rem_euclid",
|
||||
"checked_rem_euclid",
|
||||
"wrapping_rem_euclid",
|
||||
const METHODS_RET_POSITIVE: &[Symbol] = &[
|
||||
sym::checked_abs,
|
||||
sym::saturating_abs,
|
||||
sym::isqrt,
|
||||
sym::checked_isqrt,
|
||||
sym::rem_euclid,
|
||||
sym::checked_rem_euclid,
|
||||
sym::wrapping_rem_euclid,
|
||||
];
|
||||
|
||||
/// A list of methods that act like `pow()`. See `pow_call_result_sign()` for details.
|
||||
///
|
||||
/// Methods that can overflow and return a negative value must not be included in this list,
|
||||
/// because casting their return values can still result in sign loss.
|
||||
const METHODS_POW: &[&str] = &["pow", "saturating_pow", "checked_pow"];
|
||||
const METHODS_POW: &[Symbol] = &[sym::pow, sym::saturating_pow, sym::checked_pow];
|
||||
|
||||
/// A list of methods that act like `unwrap()`, and don't change the sign of the inner value.
|
||||
const METHODS_UNWRAP: &[&str] = &["unwrap", "unwrap_unchecked", "expect", "into_ok"];
|
||||
const METHODS_UNWRAP: &[Symbol] = &[sym::unwrap, sym::unwrap_unchecked, sym::expect, sym::into_ok];
|
||||
|
||||
pub(super) fn check<'cx>(
|
||||
cx: &LateContext<'cx>,
|
||||
|
|
@ -129,7 +130,7 @@ fn expr_sign<'cx, 'tcx>(cx: &LateContext<'cx>, mut expr: &'tcx Expr<'tcx>, ty: i
|
|||
|
||||
// Calling on methods that always return non-negative values.
|
||||
if let ExprKind::MethodCall(path, caller, args, ..) = expr.kind {
|
||||
let mut method_name = path.ident.name.as_str();
|
||||
let mut method_name = path.ident.name;
|
||||
|
||||
// Peel unwrap(), expect(), etc.
|
||||
while let Some(&found_name) = METHODS_UNWRAP.iter().find(|&name| &method_name == name)
|
||||
|
|
@ -138,7 +139,7 @@ fn expr_sign<'cx, 'tcx>(cx: &LateContext<'cx>, mut expr: &'tcx Expr<'tcx>, ty: i
|
|||
{
|
||||
// The original type has changed, but we can't use `ty` here anyway, because it has been
|
||||
// moved.
|
||||
method_name = inner_path.ident.name.as_str();
|
||||
method_name = inner_path.ident.name;
|
||||
expr = recv;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sym;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, GenericArg, Ty};
|
||||
use rustc_span::Symbol;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::{Symbol, sym};
|
||||
|
||||
use super::CONFUSING_METHOD_TO_NUMERIC_CAST;
|
||||
|
||||
|
|
@ -25,7 +26,6 @@ fn get_const_name_and_ty_name(
|
|||
method_def_id: DefId,
|
||||
generics: &[GenericArg<'_>],
|
||||
) -> Option<(&'static str, &'static str)> {
|
||||
let method_name = method_name.as_str();
|
||||
let diagnostic_name = cx.tcx.get_diagnostic_name(method_def_id);
|
||||
|
||||
let ty_name = if diagnostic_name.is_some_and(|diag| diag == sym::cmp_ord_min || diag == sym::cmp_ord_max) {
|
||||
|
|
@ -39,14 +39,21 @@ fn get_const_name_and_ty_name(
|
|||
}
|
||||
} else if let Some(impl_id) = cx.tcx.impl_of_method(method_def_id)
|
||||
&& let Some(ty_name) = get_primitive_ty_name(cx.tcx.type_of(impl_id).instantiate_identity())
|
||||
&& ["min", "max", "minimum", "maximum", "min_value", "max_value"].contains(&method_name)
|
||||
&& matches!(
|
||||
method_name,
|
||||
sym::min | sym::max | sym::minimum | sym::maximum | sym::min_value | sym::max_value
|
||||
)
|
||||
{
|
||||
ty_name
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let const_name = if method_name.starts_with("max") { "MAX" } else { "MIN" };
|
||||
let const_name = if matches!(method_name, sym::max | sym::maximum | sym::max_value) {
|
||||
"MAX"
|
||||
} else {
|
||||
"MIN"
|
||||
};
|
||||
Some((const_name, ty_name))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::std_or_core;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{std_or_core, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Mutability, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
|
||||
use rustc_span::sym;
|
||||
|
||||
use super::PTR_CAST_CONSTNESS;
|
||||
|
||||
|
|
@ -78,9 +77,9 @@ pub(super) fn check_null_ptr_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>)
|
|||
&& let ExprKind::Call(func, []) = cast_expr.kind
|
||||
&& let ExprKind::Path(QPath::Resolved(None, path)) = func.kind
|
||||
&& let Some(defid) = path.res.opt_def_id()
|
||||
&& let method = match (cx.tcx.get_diagnostic_name(defid), method.ident.as_str()) {
|
||||
(Some(sym::ptr_null), "cast_mut") => "null_mut",
|
||||
(Some(sym::ptr_null_mut), "cast_const") => "null",
|
||||
&& let method = match (cx.tcx.get_diagnostic_name(defid), method.ident.name) {
|
||||
(Some(sym::ptr_null), sym::cast_mut) => "null_mut",
|
||||
(Some(sym::ptr_null_mut), sym::cast_const) => "null",
|
||||
_ => return,
|
||||
}
|
||||
&& let Some(prefix) = std_or_core(cx)
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ use clippy_utils::diagnostics::span_lint_and_help;
|
|||
use clippy_utils::source::{IntoSpan, SpanRangeExt};
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||
use clippy_utils::{LimitStack, get_async_fn_body, is_async_fn};
|
||||
use clippy_utils::{LimitStack, get_async_fn_body, is_async_fn, sym};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{Attribute, Body, Expr, ExprKind, FnDecl};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -104,7 +104,7 @@ impl CognitiveComplexity {
|
|||
FnKind::Closure => {
|
||||
let header_span = body_span.with_hi(decl.output.span().lo());
|
||||
#[expect(clippy::range_plus_one)]
|
||||
if let Some(range) = header_span.map_range(cx, |src, range| {
|
||||
if let Some(range) = header_span.map_range(cx, |_, src, range| {
|
||||
let mut idxs = src.get(range.clone())?.match_indices('|');
|
||||
Some(range.start + idxs.next()?.0..range.start + idxs.next()?.0 + 1)
|
||||
}) {
|
||||
|
|
@ -157,9 +157,9 @@ impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity {
|
|||
}
|
||||
|
||||
fn check_attributes(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
|
||||
self.limit.push_attrs(cx.sess(), attrs, "cognitive_complexity");
|
||||
self.limit.push_attrs(cx.sess(), attrs, sym::cognitive_complexity);
|
||||
}
|
||||
fn check_attributes_post(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
|
||||
self.limit.pop_attrs(cx.sess(), attrs, "cognitive_complexity");
|
||||
self.limit.pop_attrs(cx.sess(), attrs, sym::cognitive_complexity);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,11 +75,15 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
|
|||
}
|
||||
|
||||
// Check that there exists at least one explicit else condition
|
||||
let (conds, _) = if_sequence(expr);
|
||||
let (conds, blocks) = if_sequence(expr);
|
||||
if conds.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
if blocks.len() < 3 {
|
||||
return;
|
||||
}
|
||||
|
||||
for cond in conds.windows(2) {
|
||||
if let (&ExprKind::Binary(ref kind1, lhs1, rhs1), &ExprKind::Binary(ref kind2, lhs2, rhs2)) =
|
||||
(&cond[0].kind, &cond[1].kind)
|
||||
|
|
@ -125,6 +129,7 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
|
|||
let ExprKind::Binary(_, lhs, rhs) = conds[0].kind else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let lhs = Sugg::hir(cx, lhs, "..").maybe_paren();
|
||||
let rhs = Sugg::hir(cx, rhs, "..").addr();
|
||||
span_lint_and_sugg(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_note, span_lint_and_then};
|
||||
use clippy_utils::source::{IntoSpan, SpanRangeExt, first_line_of_span, indent_of, reindent_multiline, snippet};
|
||||
use clippy_utils::ty::{InteriorMut, needs_ordered_drop};
|
||||
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||
|
|
@ -258,7 +258,7 @@ fn lint_branches_sharing_code<'tcx>(
|
|||
let span = span.with_hi(last_block.span.hi());
|
||||
// Improve formatting if the inner block has indentation (i.e. normal Rust formatting)
|
||||
let span = span
|
||||
.map_range(cx, |src, range| {
|
||||
.map_range(cx, |_, src, range| {
|
||||
(range.start > 4 && src.get(range.start - 4..range.start)? == " ")
|
||||
.then_some(range.start - 4..range.end)
|
||||
})
|
||||
|
|
@ -567,7 +567,7 @@ fn method_caller_is_mutable<'tcx>(
|
|||
|
||||
/// Implementation of `IFS_SAME_COND`.
|
||||
fn lint_same_cond<'tcx>(cx: &LateContext<'tcx>, conds: &[&Expr<'_>], interior_mut: &mut InteriorMut<'tcx>) {
|
||||
for (i, j) in search_same(
|
||||
for group in search_same(
|
||||
conds,
|
||||
|e| hash_expr(cx, e),
|
||||
|lhs, rhs| {
|
||||
|
|
@ -584,14 +584,8 @@ fn lint_same_cond<'tcx>(cx: &LateContext<'tcx>, conds: &[&Expr<'_>], interior_mu
|
|||
}
|
||||
},
|
||||
) {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
IFS_SAME_COND,
|
||||
j.span,
|
||||
"this `if` has the same condition as a previous `if`",
|
||||
Some(i.span),
|
||||
"same as this",
|
||||
);
|
||||
let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect();
|
||||
span_lint(cx, IFS_SAME_COND, spans, "these `if` branches have the same condition");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -609,14 +603,13 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
|
|||
SpanlessEq::new(cx).eq_expr(lhs, rhs)
|
||||
};
|
||||
|
||||
for (i, j) in search_same(conds, |e| hash_expr(cx, e), eq) {
|
||||
span_lint_and_note(
|
||||
for group in search_same(conds, |e| hash_expr(cx, e), eq) {
|
||||
let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect();
|
||||
span_lint(
|
||||
cx,
|
||||
SAME_FUNCTIONS_IN_IF_CONDITION,
|
||||
j.span,
|
||||
"this `if` has the same function call as a previous `if`",
|
||||
Some(i.span),
|
||||
"same as this",
|
||||
spans,
|
||||
"these `if` branches have the same function call",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use clippy_utils::macros::{MacroCall, macro_backtrace};
|
|||
use clippy_utils::source::snippet_with_applicability;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Node};
|
||||
use rustc_hir::{Closure, ClosureKind, CoroutineKind, Expr, ExprKind, LetStmt, LocalSource, Node, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::{Span, SyntaxContext, sym};
|
||||
|
|
@ -60,6 +60,8 @@ impl LateLintPass<'_> for DbgMacro {
|
|||
if cur_syntax_ctxt != self.prev_ctxt &&
|
||||
let Some(macro_call) = first_dbg_macro_in_expansion(cx, expr.span) &&
|
||||
!macro_call.span.in_external_macro(cx.sess().source_map()) &&
|
||||
// avoids exprs generated by the desugaring of coroutines
|
||||
!is_coroutine_desugar(expr) &&
|
||||
self.checked_dbg_call_site.insert(macro_call.span) &&
|
||||
// allows `dbg!` in test code if allow-dbg-in-test is set to true in clippy.toml
|
||||
!(self.allow_dbg_in_tests && is_in_test(cx.tcx, expr.hir_id))
|
||||
|
|
@ -73,50 +75,51 @@ impl LateLintPass<'_> for DbgMacro {
|
|||
"the `dbg!` macro is intended as a debugging tool",
|
||||
|diag| {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
let (sugg_span, suggestion) = match expr.peel_drop_temps().kind {
|
||||
// dbg!()
|
||||
ExprKind::Block(..) => {
|
||||
// If the `dbg!` macro is a "free" statement and not contained within other expressions,
|
||||
// remove the whole statement.
|
||||
if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id)
|
||||
&& let Some(semi_span) = cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span)
|
||||
{
|
||||
(macro_call.span.to(semi_span), String::new())
|
||||
} else {
|
||||
(macro_call.span, String::from("()"))
|
||||
}
|
||||
},
|
||||
// dbg!(1)
|
||||
ExprKind::Match(val, ..) => (
|
||||
macro_call.span,
|
||||
snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability)
|
||||
.to_string(),
|
||||
),
|
||||
// dbg!(2, 3)
|
||||
ExprKind::Tup(
|
||||
[
|
||||
Expr {
|
||||
kind: ExprKind::Match(first, ..),
|
||||
..
|
||||
},
|
||||
..,
|
||||
Expr {
|
||||
kind: ExprKind::Match(last, ..),
|
||||
..
|
||||
},
|
||||
],
|
||||
) => {
|
||||
let snippet = snippet_with_applicability(
|
||||
cx,
|
||||
first.span.source_callsite().to(last.span.source_callsite()),
|
||||
"..",
|
||||
&mut applicability,
|
||||
);
|
||||
(macro_call.span, format!("({snippet})"))
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let (sugg_span, suggestion) =
|
||||
match is_async_move_desugar(expr).unwrap_or(expr).peel_drop_temps().kind {
|
||||
// dbg!()
|
||||
ExprKind::Block(..) => {
|
||||
// If the `dbg!` macro is a "free" statement and not contained within other expressions,
|
||||
// remove the whole statement.
|
||||
if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id)
|
||||
&& let Some(semi_span) =
|
||||
cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span)
|
||||
{
|
||||
(macro_call.span.to(semi_span), String::new())
|
||||
} else {
|
||||
(macro_call.span, String::from("()"))
|
||||
}
|
||||
},
|
||||
// dbg!(1)
|
||||
ExprKind::Match(val, ..) => (
|
||||
macro_call.span,
|
||||
snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability)
|
||||
.to_string(),
|
||||
),
|
||||
// dbg!(2, 3)
|
||||
ExprKind::Tup(
|
||||
[
|
||||
Expr {
|
||||
kind: ExprKind::Match(first, ..),
|
||||
..
|
||||
},
|
||||
..,
|
||||
Expr {
|
||||
kind: ExprKind::Match(last, ..),
|
||||
..
|
||||
},
|
||||
],
|
||||
) => {
|
||||
let snippet = snippet_with_applicability(
|
||||
cx,
|
||||
first.span.source_callsite().to(last.span.source_callsite()),
|
||||
"..",
|
||||
&mut applicability,
|
||||
);
|
||||
(macro_call.span, format!("({snippet})"))
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
diag.span_suggestion(
|
||||
sugg_span,
|
||||
|
|
@ -134,6 +137,35 @@ impl LateLintPass<'_> for DbgMacro {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_coroutine_desugar(expr: &Expr<'_>) -> bool {
|
||||
matches!(
|
||||
expr.kind,
|
||||
ExprKind::Closure(Closure {
|
||||
kind: ClosureKind::Coroutine(CoroutineKind::Desugared(..)) | ClosureKind::CoroutineClosure(..),
|
||||
..
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
fn is_async_move_desugar<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
if let ExprKind::Block(block, _) = expr.kind
|
||||
&& let [
|
||||
Stmt {
|
||||
kind:
|
||||
StmtKind::Let(LetStmt {
|
||||
source: LocalSource::AsyncFn,
|
||||
..
|
||||
}),
|
||||
..
|
||||
},
|
||||
] = block.stmts
|
||||
{
|
||||
return block.expr;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn first_dbg_macro_in_expansion(cx: &LateContext<'_>, span: Span) -> Option<MacroCall> {
|
||||
macro_backtrace(span).find(|mc| cx.tcx.is_diagnostic_item(sym::dbg_macro, mc.def_id))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -764,6 +764,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::unwrap_in_result::UNWRAP_IN_RESULT_INFO,
|
||||
crate::upper_case_acronyms::UPPER_CASE_ACRONYMS_INFO,
|
||||
crate::use_self::USE_SELF_INFO,
|
||||
crate::useless_concat::USELESS_CONCAT_INFO,
|
||||
crate::useless_conversion::USELESS_CONVERSION_INFO,
|
||||
crate::vec::USELESS_VEC_INFO,
|
||||
crate::vec_init_then_push::VEC_INIT_THEN_PUSH_INFO,
|
||||
|
|
|
|||
|
|
@ -14,36 +14,36 @@ macro_rules! declare_with_version {
|
|||
|
||||
#[rustfmt::skip]
|
||||
declare_with_version! { DEPRECATED(DEPRECATED_VERSION) = [
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::should_assert_eq", "`assert!(a == b)` can now print the values the same way `assert_eq!(a, b) can"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::extend_from_slice", "`Vec::extend_from_slice` is no longer faster than `Vec::extend` due to specialization"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::range_step_by_zero", "`Iterator::step_by(0)` now panics and is no longer an infinite iterator"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::unstable_as_slice", "`Vec::as_slice` is now stable"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::unstable_as_mut_slice", "`Vec::as_mut_slice` is now stable"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::misaligned_transmute", "split into `clippy::cast_ptr_alignment` and `clippy::transmute_ptr_to_ptr`"),
|
||||
#[clippy::version = "1.30.0"]
|
||||
("clippy::assign_ops", "compound operators are harmless and linting on them is not in scope for clippy"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::unsafe_vector_initialization", "the suggested alternative could be substantially slower"),
|
||||
#[clippy::version = "1.39.0"]
|
||||
("clippy::unused_collect", "`Iterator::collect` is now marked as `#[must_use]`"),
|
||||
#[clippy::version = "1.44.0"]
|
||||
("clippy::replace_consts", "`min_value` and `max_value` are now deprecated"),
|
||||
#[clippy::version = "1.47.0"]
|
||||
("clippy::regex_macro", "the `regex!` macro was removed from the regex crate in 2018"),
|
||||
#[clippy::version = "1.54.0"]
|
||||
("clippy::pub_enum_variant_names", "`clippy::enum_variant_names` now covers this case via the `avoid-breaking-exported-api` config"),
|
||||
#[clippy::version = "1.54.0"]
|
||||
("clippy::wrong_pub_self_convention", "`clippy::wrong_self_convention` now covers this case via the `avoid-breaking-exported-api` config"),
|
||||
#[clippy::version = "1.86.0"]
|
||||
("clippy::option_map_or_err_ok", "`clippy::manual_ok_or` covers this case"),
|
||||
("clippy::extend_from_slice", "`Vec::extend_from_slice` is no longer faster than `Vec::extend` due to specialization"),
|
||||
#[clippy::version = "1.86.0"]
|
||||
("clippy::match_on_vec_items", "`clippy::indexing_slicing` covers indexing and slicing on `Vec<_>`"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::misaligned_transmute", "split into `clippy::cast_ptr_alignment` and `clippy::transmute_ptr_to_ptr`"),
|
||||
#[clippy::version = "1.86.0"]
|
||||
("clippy::option_map_or_err_ok", "`clippy::manual_ok_or` covers this case"),
|
||||
#[clippy::version = "1.54.0"]
|
||||
("clippy::pub_enum_variant_names", "`clippy::enum_variant_names` now covers this case via the `avoid-breaking-exported-api` config"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::range_step_by_zero", "`Iterator::step_by(0)` now panics and is no longer an infinite iterator"),
|
||||
#[clippy::version = "1.47.0"]
|
||||
("clippy::regex_macro", "the `regex!` macro was removed from the regex crate in 2018"),
|
||||
#[clippy::version = "1.44.0"]
|
||||
("clippy::replace_consts", "`min_value` and `max_value` are now deprecated"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::should_assert_eq", "`assert!(a == b)` can now print the values the same way `assert_eq!(a, b) can"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::unsafe_vector_initialization", "the suggested alternative could be substantially slower"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::unstable_as_mut_slice", "`Vec::as_mut_slice` is now stable"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::unstable_as_slice", "`Vec::as_slice` is now stable"),
|
||||
#[clippy::version = "1.39.0"]
|
||||
("clippy::unused_collect", "`Iterator::collect` is now marked as `#[must_use]`"),
|
||||
#[clippy::version = "1.54.0"]
|
||||
("clippy::wrong_pub_self_convention", "`clippy::wrong_self_convention` now covers this case via the `avoid-breaking-exported-api` config"),
|
||||
]}
|
||||
|
||||
#[rustfmt::skip]
|
||||
|
|
@ -61,6 +61,12 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [
|
|||
#[clippy::version = ""]
|
||||
("clippy::box_vec", "clippy::box_collection"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::cast_ref_to_mut", "invalid_reference_casting"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::clone_double_ref", "suspicious_double_ref_op"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::cmp_nan", "invalid_nan_comparisons"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"),
|
||||
|
|
@ -70,15 +76,35 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [
|
|||
("clippy::disallowed_method", "clippy::disallowed_methods"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::disallowed_type", "clippy::disallowed_types"),
|
||||
#[clippy::version = "1.86.0"]
|
||||
("clippy::double_neg", "double_negations"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::drop_bounds", "drop_bounds"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::drop_copy", "dropping_copy_types"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::drop_ref", "dropping_references"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::eval_order_dependence", "clippy::mixed_read_write_in_expression"),
|
||||
#[clippy::version = "1.51.0"]
|
||||
("clippy::find_map", "clippy::manual_find_map"),
|
||||
#[clippy::version = "1.53.0"]
|
||||
("clippy::filter_map", "clippy::manual_filter_map"),
|
||||
#[clippy::version = "1.51.0"]
|
||||
("clippy::find_map", "clippy::manual_find_map"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::fn_address_comparisons", "unpredictable_function_pointer_comparisons"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::fn_null_check", "useless_ptr_null_checks"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::for_loop_over_option", "for_loops_over_fallibles"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::for_loop_over_result", "for_loops_over_fallibles"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::for_loops_over_fallibles", "for_loops_over_fallibles"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::forget_copy", "forgetting_copy_types"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::forget_ref", "forgetting_references"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::identity_conversion", "clippy::useless_conversion"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::if_let_redundant_pattern_matching", "clippy::redundant_pattern_matching"),
|
||||
|
|
@ -91,7 +117,25 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [
|
|||
#[clippy::version = ""]
|
||||
("clippy::integer_arithmetic", "clippy::arithmetic_side_effects"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::into_iter_on_array", "array_into_iter"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::invalid_atomic_ordering", "invalid_atomic_ordering"),
|
||||
#[clippy::version = "CURRENT_RUSTC_VERSION"]
|
||||
("clippy::invalid_null_ptr_usage", "invalid_null_arguments"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::invalid_ref", "invalid_value"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::invalid_utf8_in_unchecked", "invalid_from_utf8_unchecked"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::let_underscore_drop", "let_underscore_drop"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::logic_bug", "clippy::overly_complex_bool_expr"),
|
||||
#[clippy::version = "1.80.0"]
|
||||
("clippy::maybe_misused_cfg", "unexpected_cfgs"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums"),
|
||||
#[clippy::version = "1.80.0"]
|
||||
("clippy::mismatched_target_os", "unexpected_cfgs"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::new_without_default_derive", "clippy::new_without_default"),
|
||||
#[clippy::version = ""]
|
||||
|
|
@ -107,6 +151,10 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [
|
|||
#[clippy::version = ""]
|
||||
("clippy::overflow_check_conditional", "clippy::panicking_overflow_checks"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::panic_params", "non_fmt_panics"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::positional_named_format_parameters", "named_arguments_used_positionally"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::ref_in_deref", "clippy::needless_borrow"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::result_expect_used", "clippy::expect_used"),
|
||||
|
|
@ -115,67 +163,25 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [
|
|||
#[clippy::version = ""]
|
||||
("clippy::result_unwrap_used", "clippy::unwrap_used"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::reverse_range_loop", "clippy::reversed_empty_ranges"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::single_char_push_str", "clippy::single_char_add_str"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::stutter", "clippy::module_name_repetitions"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::temporary_cstring_as_ptr", "dangling_pointers_from_temporaries"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::thread_local_initializer_can_be_made_const", "clippy::missing_const_for_thread_local"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::to_string_in_display", "clippy::recursive_format_impl"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::unwrap_or_else_default", "clippy::unwrap_or_default"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::zero_width_space", "clippy::invisible_characters"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::cast_ref_to_mut", "invalid_reference_casting"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::clone_double_ref", "suspicious_double_ref_op"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::cmp_nan", "invalid_nan_comparisons"),
|
||||
#[clippy::version = "CURRENT_RUSTC_VERSION"]
|
||||
("clippy::invalid_null_ptr_usage", "invalid_null_arguments"),
|
||||
#[clippy::version = "1.86.0"]
|
||||
("clippy::double_neg", "double_negations"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::drop_bounds", "drop_bounds"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::drop_copy", "dropping_copy_types"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::drop_ref", "dropping_references"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::fn_null_check", "useless_ptr_null_checks"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::for_loop_over_option", "for_loops_over_fallibles"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::for_loop_over_result", "for_loops_over_fallibles"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::for_loops_over_fallibles", "for_loops_over_fallibles"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::forget_copy", "forgetting_copy_types"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::forget_ref", "forgetting_references"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::into_iter_on_array", "array_into_iter"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::invalid_atomic_ordering", "invalid_atomic_ordering"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::invalid_ref", "invalid_value"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::invalid_utf8_in_unchecked", "invalid_from_utf8_unchecked"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::let_underscore_drop", "let_underscore_drop"),
|
||||
#[clippy::version = "1.80.0"]
|
||||
("clippy::maybe_misused_cfg", "unexpected_cfgs"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums"),
|
||||
#[clippy::version = "1.80.0"]
|
||||
("clippy::mismatched_target_os", "unexpected_cfgs"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::panic_params", "non_fmt_panics"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::positional_named_format_parameters", "named_arguments_used_positionally"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::temporary_cstring_as_ptr", "dangling_pointers_from_temporaries"),
|
||||
#[clippy::version = "1.88.0"]
|
||||
("clippy::transmute_float_to_int", "unnecessary_transmutes"),
|
||||
#[clippy::version = "1.88.0"]
|
||||
("clippy::transmute_int_to_char", "unnecessary_transmutes"),
|
||||
#[clippy::version = "1.88.0"]
|
||||
("clippy::transmute_int_to_float", "unnecessary_transmutes"),
|
||||
#[clippy::version = "1.88.0"]
|
||||
("clippy::transmute_num_to_bytes", "unnecessary_transmutes"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::undropped_manually_drops", "undropped_manually_drops"),
|
||||
#[clippy::version = ""]
|
||||
|
|
@ -183,15 +189,9 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [
|
|||
#[clippy::version = ""]
|
||||
("clippy::unused_label", "unused_labels"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::unwrap_or_else_default", "clippy::unwrap_or_default"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::vtable_address_comparisons", "ambiguous_wide_pointer_comparisons"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::reverse_range_loop", "clippy::reversed_empty_ranges"),
|
||||
#[clippy::version = "1.88.0"]
|
||||
("clippy::transmute_int_to_float", "unnecessary_transmutes"),
|
||||
#[clippy::version = "1.88.0"]
|
||||
("clippy::transmute_int_to_char", "unnecessary_transmutes"),
|
||||
#[clippy::version = "1.88.0"]
|
||||
("clippy::transmute_float_to_int", "unnecessary_transmutes"),
|
||||
#[clippy::version = "1.88.0"]
|
||||
("clippy::transmute_num_to_bytes", "unnecessary_transmutes"),
|
||||
("clippy::zero_width_space", "clippy::invisible_characters"),
|
||||
]}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, TypeckResults};
|
|||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::{Span, Symbol};
|
||||
use std::borrow::Cow;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -252,13 +253,14 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
|||
}
|
||||
|
||||
let typeck = cx.typeck_results();
|
||||
let Some((kind, sub_expr)) = try_parse_ref_op(cx.tcx, typeck, expr) else {
|
||||
let Some((kind, sub_expr, skip_expr)) = try_parse_ref_op(cx.tcx, typeck, expr) else {
|
||||
// The whole chain of reference operations has been seen
|
||||
if let Some((state, data)) = self.state.take() {
|
||||
report(cx, expr, state, data, typeck);
|
||||
}
|
||||
return;
|
||||
};
|
||||
self.skip_expr = skip_expr;
|
||||
|
||||
match (self.state.take(), kind) {
|
||||
(None, kind) => {
|
||||
|
|
@ -303,7 +305,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
|||
RefOp::Method { mutbl, is_ufcs }
|
||||
if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id)
|
||||
// Allow explicit deref in method chains. e.g. `foo.deref().bar()`
|
||||
&& (is_ufcs || !in_postfix_position(cx, expr)) =>
|
||||
&& (is_ufcs || !is_in_method_chain(cx, expr)) =>
|
||||
{
|
||||
let ty_changed_count = usize::from(!deref_method_same_type(expr_ty, typeck.expr_ty(sub_expr)));
|
||||
self.state = Some((
|
||||
|
|
@ -671,42 +673,38 @@ fn try_parse_ref_op<'tcx>(
|
|||
tcx: TyCtxt<'tcx>,
|
||||
typeck: &'tcx TypeckResults<'_>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
) -> Option<(RefOp, &'tcx Expr<'tcx>)> {
|
||||
let (is_ufcs, def_id, arg) = match expr.kind {
|
||||
ExprKind::MethodCall(_, arg, [], _) => (false, typeck.type_dependent_def_id(expr.hir_id)?, arg),
|
||||
) -> Option<(RefOp, &'tcx Expr<'tcx>, Option<HirId>)> {
|
||||
let (call_path_id, def_id, arg) = match expr.kind {
|
||||
ExprKind::MethodCall(_, arg, [], _) => (None, typeck.type_dependent_def_id(expr.hir_id)?, arg),
|
||||
ExprKind::Call(
|
||||
Expr {
|
||||
kind: ExprKind::Path(path),
|
||||
&Expr {
|
||||
kind: ExprKind::Path(QPath::Resolved(None, path)),
|
||||
hir_id,
|
||||
..
|
||||
},
|
||||
[arg],
|
||||
) => (true, typeck.qpath_res(path, *hir_id).opt_def_id()?, arg),
|
||||
) => (Some(hir_id), path.res.opt_def_id()?, arg),
|
||||
ExprKind::Unary(UnOp::Deref, sub_expr) if !typeck.expr_ty(sub_expr).is_raw_ptr() => {
|
||||
return Some((RefOp::Deref, sub_expr));
|
||||
return Some((RefOp::Deref, sub_expr, None));
|
||||
},
|
||||
ExprKind::AddrOf(BorrowKind::Ref, mutability, sub_expr) => {
|
||||
return Some((RefOp::AddrOf(mutability), sub_expr, None));
|
||||
},
|
||||
ExprKind::AddrOf(BorrowKind::Ref, mutability, sub_expr) => return Some((RefOp::AddrOf(mutability), sub_expr)),
|
||||
_ => return None,
|
||||
};
|
||||
if tcx.is_diagnostic_item(sym::deref_method, def_id) {
|
||||
Some((
|
||||
RefOp::Method {
|
||||
mutbl: Mutability::Not,
|
||||
is_ufcs,
|
||||
},
|
||||
arg,
|
||||
))
|
||||
} else if tcx.trait_of_item(def_id)? == tcx.lang_items().deref_mut_trait()? {
|
||||
Some((
|
||||
RefOp::Method {
|
||||
mutbl: Mutability::Mut,
|
||||
is_ufcs,
|
||||
},
|
||||
arg,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let mutbl = match tcx.get_diagnostic_name(def_id) {
|
||||
Some(sym::deref_method) => Mutability::Not,
|
||||
Some(sym::deref_mut_method) => Mutability::Mut,
|
||||
_ => return None,
|
||||
};
|
||||
Some((
|
||||
RefOp::Method {
|
||||
mutbl,
|
||||
is_ufcs: call_path_id.is_some(),
|
||||
},
|
||||
arg,
|
||||
call_path_id,
|
||||
))
|
||||
}
|
||||
|
||||
// Checks if the adjustments contains a deref of `ManuallyDrop<_>`
|
||||
|
|
@ -730,7 +728,13 @@ fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
fn in_postfix_position<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
|
||||
fn is_in_method_chain<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
|
||||
if let ExprKind::MethodCall(_, recv, _, _) = e.kind
|
||||
&& matches!(recv.kind, ExprKind::MethodCall(..))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(parent) = get_parent_expr(cx, e)
|
||||
&& parent.span.eq_ctxt(e.span)
|
||||
{
|
||||
|
|
@ -944,7 +948,7 @@ fn report<'tcx>(
|
|||
mutbl,
|
||||
} => {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let (expr_str, _expr_is_macro_call) =
|
||||
let (expr_str, expr_is_macro_call) =
|
||||
snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app);
|
||||
let ty = typeck.expr_ty(expr);
|
||||
let (_, ref_count) = peel_middle_ty_refs(ty);
|
||||
|
|
@ -968,20 +972,11 @@ fn report<'tcx>(
|
|||
"&"
|
||||
};
|
||||
|
||||
// expr_str (the suggestion) is never shown if is_final_ufcs is true, since it's
|
||||
// `expr.kind == ExprKind::Call`. Therefore, this is, afaik, always unnecessary.
|
||||
/*
|
||||
expr_str = if !expr_is_macro_call && is_final_ufcs && expr.precedence() < ExprPrecedence::Prefix {
|
||||
let expr_str = if !expr_is_macro_call && is_ufcs && expr.precedence() < ExprPrecedence::Prefix {
|
||||
Cow::Owned(format!("({expr_str})"))
|
||||
} else {
|
||||
expr_str
|
||||
};
|
||||
*/
|
||||
|
||||
// Fix #10850, do not lint if it's `Foo::deref` instead of `foo.deref()`.
|
||||
if is_ufcs {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
|
@ -997,6 +992,15 @@ fn report<'tcx>(
|
|||
);
|
||||
},
|
||||
State::DerefedBorrow(state) => {
|
||||
// Do not suggest removing a non-mandatory `&` in `&*rawptr` in an `unsafe` context,
|
||||
// as this may make rustc trigger its `dangerous_implicit_autorefs` lint.
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, subexpr) = data.first_expr.kind
|
||||
&& let ExprKind::Unary(UnOp::Deref, subsubexpr) = subexpr.kind
|
||||
&& cx.typeck_results().expr_ty_adjusted(subsubexpr).is_raw_ptr()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let (snip, snip_is_macro) =
|
||||
snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app);
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &h
|
|||
if ty_adt.repr().packed()
|
||||
&& ty_subs
|
||||
.iter()
|
||||
.any(|arg| matches!(arg.unpack(), GenericArgKind::Type(_) | GenericArgKind::Const(_)))
|
||||
.any(|arg| matches!(arg.kind(), GenericArgKind::Type(_) | GenericArgKind::Const(_)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,8 +106,8 @@ impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]);
|
|||
impl<'tcx> LateLintPass<'tcx> for DisallowedTypes {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
|
||||
if let ItemKind::Use(path, UseKind::Single(_)) = &item.kind {
|
||||
for res in &path.res {
|
||||
self.check_res_emit(cx, res, item.span);
|
||||
if let Some(res) = path.res.type_ns {
|
||||
self.check_res_emit(cx, &res, item.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
|||
use itertools::Itertools;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{BytePos, Span};
|
||||
use std::cmp::Ordering;
|
||||
use rustc_span::BytePos;
|
||||
use std::ops::Range;
|
||||
|
||||
use super::{DOC_LAZY_CONTINUATION, DOC_OVERINDENTED_LIST_ITEMS};
|
||||
use super::{DOC_LAZY_CONTINUATION, DOC_OVERINDENTED_LIST_ITEMS, Fragments};
|
||||
|
||||
fn map_container_to_text(c: &super::Container) -> &'static str {
|
||||
match c {
|
||||
|
|
@ -19,29 +18,27 @@ fn map_container_to_text(c: &super::Container) -> &'static str {
|
|||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
doc: &str,
|
||||
range: Range<usize>,
|
||||
mut span: Span,
|
||||
cooked_range: Range<usize>,
|
||||
fragments: &Fragments<'_>,
|
||||
containers: &[super::Container],
|
||||
) {
|
||||
if doc[range.clone()].contains('\t') {
|
||||
// We don't do tab stops correctly.
|
||||
return;
|
||||
}
|
||||
|
||||
// Blockquote
|
||||
let ccount = doc[range.clone()].chars().filter(|c| *c == '>').count();
|
||||
// Get blockquotes
|
||||
let ccount = doc[cooked_range.clone()].chars().filter(|c| *c == '>').count();
|
||||
let blockquote_level = containers
|
||||
.iter()
|
||||
.filter(|c| matches!(c, super::Container::Blockquote))
|
||||
.count();
|
||||
if ccount < blockquote_level {
|
||||
|
||||
if ccount < blockquote_level
|
||||
&& let Some(mut span) = fragments.span(cx, cooked_range.clone())
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DOC_LAZY_CONTINUATION,
|
||||
span,
|
||||
"doc quote line without `>` marker",
|
||||
|diag| {
|
||||
let mut doc_start_range = &doc[range];
|
||||
let mut doc_start_range = &doc[cooked_range];
|
||||
let mut suggested = String::new();
|
||||
for c in containers {
|
||||
let text = map_container_to_text(c);
|
||||
|
|
@ -78,7 +75,7 @@ pub(super) fn check(
|
|||
}
|
||||
|
||||
// List
|
||||
let leading_spaces = doc[range].chars().filter(|c| *c == ' ').count();
|
||||
let leading_spaces = doc[cooked_range.clone()].chars().filter(|c| *c == ' ').count();
|
||||
let list_indentation = containers
|
||||
.iter()
|
||||
.map(|c| {
|
||||
|
|
@ -89,36 +86,41 @@ pub(super) fn check(
|
|||
}
|
||||
})
|
||||
.sum();
|
||||
match leading_spaces.cmp(&list_indentation) {
|
||||
Ordering::Less => span_lint_and_then(
|
||||
cx,
|
||||
DOC_LAZY_CONTINUATION,
|
||||
span,
|
||||
"doc list item without indentation",
|
||||
|diag| {
|
||||
// simpler suggestion style for indentation
|
||||
let indent = list_indentation - leading_spaces;
|
||||
diag.span_suggestion_verbose(
|
||||
span.shrink_to_hi(),
|
||||
"indent this line",
|
||||
std::iter::repeat_n(" ", indent).join(""),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
diag.help("if this is supposed to be its own paragraph, add a blank line");
|
||||
},
|
||||
),
|
||||
Ordering::Greater => {
|
||||
let sugg = std::iter::repeat_n(" ", list_indentation).join("");
|
||||
span_lint_and_sugg(
|
||||
|
||||
if leading_spaces != list_indentation
|
||||
&& let Some(span) = fragments.span(cx, cooked_range.clone())
|
||||
{
|
||||
if leading_spaces < list_indentation {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DOC_OVERINDENTED_LIST_ITEMS,
|
||||
DOC_LAZY_CONTINUATION,
|
||||
span,
|
||||
"doc list item overindented",
|
||||
format!("try using `{sugg}` ({list_indentation} spaces)"),
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
"doc list item without indentation",
|
||||
|diag| {
|
||||
// simpler suggestion style for indentation
|
||||
let indent = list_indentation - leading_spaces;
|
||||
diag.span_suggestion_verbose(
|
||||
span.shrink_to_hi(),
|
||||
"indent this line",
|
||||
std::iter::repeat_n(" ", indent).join(""),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
diag.help("if this is supposed to be its own paragraph, add a blank line");
|
||||
},
|
||||
);
|
||||
},
|
||||
Ordering::Equal => {},
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let sugg = std::iter::repeat_n(" ", list_indentation).join("");
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DOC_OVERINDENTED_LIST_ITEMS,
|
||||
span,
|
||||
"doc list item overindented",
|
||||
format!("try using `{sugg}` ({list_indentation} spaces)"),
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,15 @@ use rustc_lint::LateContext;
|
|||
use rustc_span::{BytePos, Pos, Span};
|
||||
use url::Url;
|
||||
|
||||
use crate::doc::DOC_MARKDOWN;
|
||||
use crate::doc::{DOC_MARKDOWN, Fragments};
|
||||
use std::ops::Range;
|
||||
|
||||
pub fn check(
|
||||
cx: &LateContext<'_>,
|
||||
valid_idents: &FxHashSet<String>,
|
||||
text: &str,
|
||||
span: Span,
|
||||
fragments: &Fragments<'_>,
|
||||
fragment_range: Range<usize>,
|
||||
code_level: isize,
|
||||
blockquote_level: isize,
|
||||
) {
|
||||
|
|
@ -64,20 +66,31 @@ pub fn check(
|
|||
close_parens += 1;
|
||||
}
|
||||
|
||||
// Adjust for the current word
|
||||
let offset = word.as_ptr() as usize - text.as_ptr() as usize;
|
||||
let span = Span::new(
|
||||
span.lo() + BytePos::from_usize(offset),
|
||||
span.lo() + BytePos::from_usize(offset + word.len()),
|
||||
span.ctxt(),
|
||||
span.parent(),
|
||||
);
|
||||
// We'll use this offset to calculate the span to lint.
|
||||
let fragment_offset = word.as_ptr() as usize - text.as_ptr() as usize;
|
||||
|
||||
check_word(cx, word, span, code_level, blockquote_level);
|
||||
// Adjust for the current word
|
||||
check_word(
|
||||
cx,
|
||||
word,
|
||||
fragments,
|
||||
&fragment_range,
|
||||
fragment_offset,
|
||||
code_level,
|
||||
blockquote_level,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, blockquote_level: isize) {
|
||||
fn check_word(
|
||||
cx: &LateContext<'_>,
|
||||
word: &str,
|
||||
fragments: &Fragments<'_>,
|
||||
range: &Range<usize>,
|
||||
fragment_offset: usize,
|
||||
code_level: isize,
|
||||
blockquote_level: isize,
|
||||
) {
|
||||
/// Checks if a string is upper-camel-case, i.e., starts with an uppercase and
|
||||
/// contains at least two uppercase letters (`Clippy` is ok) and one lower-case
|
||||
/// letter (`NASA` is ok).
|
||||
|
|
@ -117,6 +130,16 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, b
|
|||
// try to get around the fact that `foo::bar` parses as a valid URL
|
||||
&& !url.cannot_be_a_base()
|
||||
{
|
||||
let Some(fragment_span) = fragments.span(cx, range.clone()) else {
|
||||
return;
|
||||
};
|
||||
let span = Span::new(
|
||||
fragment_span.lo() + BytePos::from_usize(fragment_offset),
|
||||
fragment_span.lo() + BytePos::from_usize(fragment_offset + word.len()),
|
||||
fragment_span.ctxt(),
|
||||
fragment_span.parent(),
|
||||
);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DOC_MARKDOWN,
|
||||
|
|
@ -137,6 +160,17 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, b
|
|||
}
|
||||
|
||||
if has_underscore(word) || word.contains("::") || is_camel_case(word) || word.ends_with("()") {
|
||||
let Some(fragment_span) = fragments.span(cx, range.clone()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let span = Span::new(
|
||||
fragment_span.lo() + BytePos::from_usize(fragment_offset),
|
||||
fragment_span.lo() + BytePos::from_usize(fragment_offset + word.len()),
|
||||
fragment_span.ctxt(),
|
||||
fragment_span.parent(),
|
||||
);
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DOC_MARKDOWN,
|
||||
|
|
|
|||
|
|
@ -113,7 +113,8 @@ fn find_panic(cx: &LateContext<'_>, body_id: BodyId) -> Option<Span> {
|
|||
}
|
||||
|
||||
// check for `unwrap` and `expect` for both `Option` and `Result`
|
||||
if let Some(arglists) = method_chain_args(expr, &["unwrap"]).or_else(|| method_chain_args(expr, &["expect"]))
|
||||
if let Some(arglists) =
|
||||
method_chain_args(expr, &[sym::unwrap]).or_else(|| method_chain_args(expr, &[sym::expect]))
|
||||
&& let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs()
|
||||
&& matches!(
|
||||
get_type_diagnostic_name(cx, receiver_ty),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::attrs::is_doc_hidden;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::{is_entrypoint_fn, is_trait_impl_item};
|
||||
use pulldown_cmark::Event::{
|
||||
Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start,
|
||||
|
|
@ -730,7 +729,10 @@ struct Fragments<'a> {
|
|||
}
|
||||
|
||||
impl Fragments<'_> {
|
||||
fn span(self, cx: &LateContext<'_>, range: Range<usize>) -> Option<Span> {
|
||||
/// get the span for the markdown range. Note that this function is not cheap, use it with
|
||||
/// caution.
|
||||
#[must_use]
|
||||
fn span(&self, cx: &LateContext<'_>, range: Range<usize>) -> Option<Span> {
|
||||
source_span_for_markdown_range(cx.tcx, self.doc, &range, self.fragments)
|
||||
}
|
||||
}
|
||||
|
|
@ -1068,9 +1070,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
|
|||
);
|
||||
} else {
|
||||
for (text, range, assoc_code_level) in text_to_check {
|
||||
if let Some(span) = fragments.span(cx, range) {
|
||||
markdown::check(cx, valid_idents, &text, span, assoc_code_level, blockquote_level);
|
||||
}
|
||||
markdown::check(cx, valid_idents, &text, &fragments, range, assoc_code_level, blockquote_level);
|
||||
}
|
||||
}
|
||||
text_to_check = Vec::new();
|
||||
|
|
@ -1081,26 +1081,27 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
|
|||
| TaskListMarker(_) | Code(_) | Rule | InlineMath(..) | DisplayMath(..) => (),
|
||||
SoftBreak | HardBreak => {
|
||||
if !containers.is_empty()
|
||||
&& let Some((next_event, next_range)) = events.peek()
|
||||
&& let Some(next_span) = fragments.span(cx, next_range.clone())
|
||||
&& let Some(span) = fragments.span(cx, range.clone())
|
||||
&& !in_footnote_definition
|
||||
// Tabs aren't handled correctly vvvv
|
||||
&& !doc[range.clone()].contains('\t')
|
||||
&& let Some((next_event, next_range)) = events.peek()
|
||||
&& !matches!(next_event, End(_))
|
||||
{
|
||||
lazy_continuation::check(
|
||||
cx,
|
||||
doc,
|
||||
range.end..next_range.start,
|
||||
Span::new(span.hi(), next_span.lo(), span.ctxt(), span.parent()),
|
||||
&fragments,
|
||||
&containers[..],
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(span) = fragments.span(cx, range.clone())
|
||||
|
||||
if event == HardBreak
|
||||
&& !doc[range.clone()].trim().starts_with('\\')
|
||||
&& let Some(span) = fragments.span(cx, range.clone())
|
||||
&& !span.from_expansion()
|
||||
&& let Some(snippet) = snippet_opt(cx, span)
|
||||
&& !snippet.trim().starts_with('\\')
|
||||
&& event == HardBreak {
|
||||
{
|
||||
collected_breaks.push(span);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -92,7 +92,8 @@ impl_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VA
|
|||
|
||||
impl LateLintPass<'_> for EmptyWithBrackets {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
if let ItemKind::Struct(ident, var_data, _) = &item.kind
|
||||
if let ItemKind::Struct(ident, _, var_data) = &item.kind
|
||||
&& !item.span.from_expansion()
|
||||
&& has_brackets(var_data)
|
||||
&& let span_after_ident = item.span.with_lo(ident.span.hi())
|
||||
&& has_no_fields(cx, var_data, span_after_ident)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant {
|
|||
if cx.tcx.data_layout.pointer_size.bits() != 64 {
|
||||
return;
|
||||
}
|
||||
if let ItemKind::Enum(_, def, _) = &item.kind {
|
||||
if let ItemKind::Enum(_, _, def) = &item.kind {
|
||||
for var in def.variants {
|
||||
if let Some(anon_const) = &var.disr_expr {
|
||||
let def_id = cx.tcx.hir_body_owner_def_id(anon_const.body);
|
||||
|
|
|
|||
|
|
@ -306,7 +306,7 @@ fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'
|
|||
return true;
|
||||
}
|
||||
for (from_arg, to_arg) in to_subs.iter().zip(from_subs) {
|
||||
match (from_arg.unpack(), to_arg.unpack()) {
|
||||
match (from_arg.kind(), to_arg.kind()) {
|
||||
(GenericArgKind::Lifetime(from_region), GenericArgKind::Lifetime(to_region)) => {
|
||||
if check_region(from_region, to_region) {
|
||||
return true;
|
||||
|
|
@ -354,5 +354,5 @@ fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'
|
|||
|
||||
fn ty_has_static(ty: Ty<'_>) -> bool {
|
||||
ty.walk()
|
||||
.any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(re) if re.is_static()))
|
||||
.any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(re) if re.is_static()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ fn check_fn_decl(cx: &LateContext<'_>, decl: &FnDecl<'_>, sp: Span, max: u64) {
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for ExcessiveBools {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
|
||||
if let ItemKind::Struct(_, variant_data, _) = &item.kind
|
||||
if let ItemKind::Struct(_, _, variant_data) = &item.kind
|
||||
&& variant_data.fields().len() as u64 > self.max_struct_bools
|
||||
&& has_n_bools(
|
||||
variant_data.fields().iter().map(|field| field.ty),
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ impl LateLintPass<'_> for ExhaustiveItems {
|
|||
"exported enums should not be exhaustive",
|
||||
[].as_slice(),
|
||||
),
|
||||
ItemKind::Struct(_, v, ..) => (
|
||||
ItemKind::Struct(_, _, v) => (
|
||||
EXHAUSTIVE_STRUCTS,
|
||||
"exported structs should not be exhaustive",
|
||||
v.fields(),
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::macros::{FormatArgsStorage, format_args_inputs_span};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{is_expn_of, path_def_id};
|
||||
use clippy_utils::{is_expn_of, path_def_id, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{BindingMode, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::{ExpnId, sym};
|
||||
use rustc_span::ExpnId;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -72,9 +72,9 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
|||
};
|
||||
|
||||
// ordering is important here, since `writeln!` uses `write!` internally
|
||||
let calling_macro = if is_expn_of(write_call.span, "writeln").is_some() {
|
||||
let calling_macro = if is_expn_of(write_call.span, sym::writeln).is_some() {
|
||||
Some("writeln")
|
||||
} else if is_expn_of(write_call.span, "write").is_some() {
|
||||
} else if is_expn_of(write_call.span, sym::write).is_some() {
|
||||
Some("write")
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters {
|
|||
// Only lint on inherent methods, not trait methods.
|
||||
if let ImplItemKind::Fn(.., body_id) = item.kind
|
||||
&& !item.generics.params.is_empty()
|
||||
&& trait_ref_of_method(cx, item.owner_id.def_id).is_none()
|
||||
&& trait_ref_of_method(cx, item.owner_id).is_none()
|
||||
&& !is_empty_body(cx, body_id)
|
||||
&& (!self.avoid_breaking_exported_api || !cx.effective_visibilities.is_exported(item.owner_id.def_id))
|
||||
&& !item.span.in_external_macro(cx.sess().source_map())
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ fn lint_impl_body(cx: &LateContext<'_>, impl_span: Span, impl_items: &[hir::Impl
|
|||
}
|
||||
|
||||
// check for `unwrap`
|
||||
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
|
||||
if let Some(arglists) = method_chain_args(expr, &[sym::unwrap]) {
|
||||
let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
|
||||
if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option)
|
||||
|| is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ declare_lint_pass!(FieldScopedVisibilityModifiers => [FIELD_SCOPED_VISIBILITY_MO
|
|||
|
||||
impl EarlyLintPass for FieldScopedVisibilityModifiers {
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
let ItemKind::Struct(_, ref st, _) = item.kind else {
|
||||
let ItemKind::Struct(_, _, ref st) = item.kind else {
|
||||
return;
|
||||
};
|
||||
for field in st.fields() {
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
|
|||
},
|
||||
);
|
||||
}
|
||||
} else if digits > max as usize && float_str.len() < sym_str.len() {
|
||||
} else if digits > max as usize && count_digits(&float_str) < count_digits(sym_str) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
EXCESSIVE_PRECISION,
|
||||
|
|
|
|||
|
|
@ -294,8 +294,8 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
|
|||
&& let Some(parent) = get_parent_expr(cx, expr)
|
||||
{
|
||||
if let Some(grandparent) = get_parent_expr(cx, parent)
|
||||
&& let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = grandparent.kind
|
||||
&& method_name.as_str() == "sqrt"
|
||||
&& let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = grandparent.kind
|
||||
&& method.name == sym::sqrt
|
||||
&& detect_hypot(cx, receiver).is_some()
|
||||
{
|
||||
return;
|
||||
|
|
@ -375,24 +375,10 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
|
|||
}
|
||||
|
||||
// check if expression of the form x.powi(2) + y.powi(2)
|
||||
if let ExprKind::MethodCall(
|
||||
PathSegment {
|
||||
ident: lmethod_name, ..
|
||||
},
|
||||
largs_0,
|
||||
[largs_1, ..],
|
||||
_,
|
||||
) = &add_lhs.kind
|
||||
&& let ExprKind::MethodCall(
|
||||
PathSegment {
|
||||
ident: rmethod_name, ..
|
||||
},
|
||||
rargs_0,
|
||||
[rargs_1, ..],
|
||||
_,
|
||||
) = &add_rhs.kind
|
||||
&& lmethod_name.as_str() == "powi"
|
||||
&& rmethod_name.as_str() == "powi"
|
||||
if let ExprKind::MethodCall(PathSegment { ident: lmethod, .. }, largs_0, [largs_1, ..], _) = &add_lhs.kind
|
||||
&& let ExprKind::MethodCall(PathSegment { ident: rmethod, .. }, rargs_0, [rargs_1, ..], _) = &add_rhs.kind
|
||||
&& lmethod.name == sym::powi
|
||||
&& rmethod.name == sym::powi
|
||||
&& let ecx = ConstEvalCtxt::new(cx)
|
||||
&& let Some(lvalue) = ecx.eval(largs_1)
|
||||
&& let Some(rvalue) = ecx.eval(rargs_1)
|
||||
|
|
@ -482,8 +468,8 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
) = &expr.kind
|
||||
{
|
||||
if let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = parent.kind
|
||||
&& method_name.as_str() == "sqrt"
|
||||
&& let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = parent.kind
|
||||
&& method.name == sym::sqrt
|
||||
&& detect_hypot(cx, receiver).is_some()
|
||||
{
|
||||
return;
|
||||
|
|
@ -623,27 +609,13 @@ fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
}
|
||||
|
||||
fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
|
||||
if let ExprKind::MethodCall(
|
||||
PathSegment {
|
||||
ident: method_name_a, ..
|
||||
},
|
||||
_,
|
||||
args_a,
|
||||
_,
|
||||
) = expr_a.kind
|
||||
&& let ExprKind::MethodCall(
|
||||
PathSegment {
|
||||
ident: method_name_b, ..
|
||||
},
|
||||
_,
|
||||
args_b,
|
||||
_,
|
||||
) = expr_b.kind
|
||||
if let ExprKind::MethodCall(PathSegment { ident: method_a, .. }, _, args_a, _) = expr_a.kind
|
||||
&& let ExprKind::MethodCall(PathSegment { ident: method_b, .. }, _, args_b, _) = expr_b.kind
|
||||
{
|
||||
return method_name_a.as_str() == method_name_b.as_str()
|
||||
return method_a.name == method_b.name
|
||||
&& args_a.len() == args_b.len()
|
||||
&& (["ln", "log2", "log10"].contains(&method_name_a.as_str())
|
||||
|| method_name_a.as_str() == "log" && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0]));
|
||||
&& (matches!(method_a.name, sym::ln | sym::log2 | sym::log10)
|
||||
|| method_a.name == sym::log && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0]));
|
||||
}
|
||||
|
||||
false
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp
|
|||
let attr = cx.tcx.get_attr(item.owner_id, sym::must_use);
|
||||
if let Some(attr) = attr {
|
||||
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, attrs, sig);
|
||||
} else if is_public && !is_proc_macro(attrs) && trait_ref_of_method(cx, item.owner_id.def_id).is_none() {
|
||||
} else if is_public && !is_proc_macro(attrs) && trait_ref_of_method(cx, item.owner_id).is_none() {
|
||||
check_must_use_candidate(
|
||||
cx,
|
||||
sig.decl,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a
|
|||
&& is_type_diagnostic_item(cx, *opt_ty, sym::Option)
|
||||
&& let ty::Adt(_, opt_gen_args) = opt_ty.kind()
|
||||
&& let [gen_arg] = opt_gen_args.as_slice()
|
||||
&& let GenericArgKind::Type(gen_ty) = gen_arg.unpack()
|
||||
&& let GenericArgKind::Type(gen_ty) = gen_arg.kind()
|
||||
&& !gen_ty.is_ref()
|
||||
// Need to gen the original spans, so first parsing mid, and hir parsing afterward
|
||||
&& let hir::TyKind::Ref(lifetime, hir::MutTy { ty, .. }) = param.kind
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ pub(super) fn check_impl_item<'tcx>(
|
|||
// Don't lint if method is a trait's implementation, we can't do anything about those
|
||||
if let hir::ImplItemKind::Fn(ref sig, _) = item.kind
|
||||
&& let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.owner_id.def_id, item.span)
|
||||
&& trait_ref_of_method(cx, item.owner_id.def_id).is_none()
|
||||
&& trait_ref_of_method(cx, item.owner_id).is_none()
|
||||
{
|
||||
if cx.effective_visibilities.is_exported(item.owner_id.def_id) {
|
||||
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
|
||||
|
|
@ -103,7 +103,7 @@ fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty
|
|||
.did()
|
||||
.as_local()
|
||||
&& let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(local_def_id)
|
||||
&& let hir::ItemKind::Enum(_, ref def, _) = item.kind
|
||||
&& let hir::ItemKind::Enum(_, _, ref def) = item.kind
|
||||
{
|
||||
let variants_size = AdtVariantInfo::new(cx, *adt, subst);
|
||||
if let Some((first_variant, variants)) = variants_size.split_first()
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
|
|||
}
|
||||
|
||||
let generics_suggestion_span = impl_.generics.span.substitute_dummy({
|
||||
let range = (item.span.lo()..target.span().lo()).map_range(cx, |src, range| {
|
||||
let range = (item.span.lo()..target.span().lo()).map_range(cx, |_, src, range| {
|
||||
Some(src.get(range.clone())?.find("impl")? + 4..range.end)
|
||||
});
|
||||
if let Some(range) = range {
|
||||
|
|
@ -165,11 +165,12 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
|
|||
continue;
|
||||
}
|
||||
let generics_suggestion_span = generics.span.substitute_dummy({
|
||||
let range = (item.span.lo()..body.params[0].pat.span.lo()).map_range(cx, |src, range| {
|
||||
let (pre, post) = src.get(range.clone())?.split_once("fn")?;
|
||||
let pos = post.find('(')? + pre.len() + 2;
|
||||
Some(pos..pos)
|
||||
});
|
||||
let range =
|
||||
(item.span.lo()..body.params[0].pat.span.lo()).map_range(cx, |_, src, range| {
|
||||
let (pre, post) = src.get(range.clone())?.split_once("fn")?;
|
||||
let pos = post.find('(')? + pre.len() + 2;
|
||||
Some(pos..pos)
|
||||
});
|
||||
if let Some(range) = range {
|
||||
range.with_ctxt(item.span.ctxt())
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
|||
use clippy_utils::higher::IfLet;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::ty::is_copy;
|
||||
use clippy_utils::{is_expn_of, is_lint_allowed, path_to_local};
|
||||
use clippy_utils::{is_expn_of, is_lint_allowed, path_to_local, sym};
|
||||
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
|
|
@ -12,7 +12,6 @@ use rustc_hir::HirId;
|
|||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::symbol::Ident;
|
||||
|
|
@ -72,7 +71,7 @@ impl_lint_pass!(IndexRefutableSlice => [INDEX_REFUTABLE_SLICE]);
|
|||
impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
|
||||
if let Some(IfLet { let_pat, if_then, .. }) = IfLet::hir(cx, expr)
|
||||
&& (!expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some())
|
||||
&& (!expr.span.from_expansion() || is_expn_of(expr.span, sym::if_chain).is_some())
|
||||
&& !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id)
|
||||
&& let found_slices = find_slice_values(cx, let_pat)
|
||||
&& !found_slices.is_empty()
|
||||
|
|
@ -109,11 +108,11 @@ fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxIndexMap<Hir
|
|||
}
|
||||
|
||||
let bound_ty = cx.typeck_results().node_type(pat.hir_id);
|
||||
if let ty::Slice(inner_ty) | ty::Array(inner_ty, _) = bound_ty.peel_refs().kind() {
|
||||
if let Some(inner_ty) = bound_ty.peel_refs().builtin_index() {
|
||||
// The values need to use the `ref` keyword if they can't be copied.
|
||||
// This will need to be adjusted if the lint want to support mutable access in the future
|
||||
let src_is_ref = bound_ty.is_ref() && by_ref == hir::ByRef::No;
|
||||
let needs_ref = !(src_is_ref || is_copy(cx, *inner_ty));
|
||||
let needs_ref = !(src_is_ref || is_copy(cx, inner_ty));
|
||||
|
||||
let slice_info = slices
|
||||
.entry(value_hir_id)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use crate::methods::method_call;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::peel_blocks;
|
||||
use clippy_utils::{peel_blocks, sym};
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::{BytePos, Span, sym};
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -57,7 +57,7 @@ fn index_if_arg_is_boolean(args: &[Expr<'_>], call_span: Span) -> Option<Span> {
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for IneffectiveOpenOptions {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let Some(("open", mut receiver, [_arg], _, _)) = method_call(expr) else {
|
||||
let Some((sym::open, mut receiver, [_arg], _, _)) = method_call(expr) else {
|
||||
return;
|
||||
};
|
||||
let receiver_ty = cx.typeck_results().expr_ty(receiver);
|
||||
|
|
@ -70,9 +70,9 @@ impl<'tcx> LateLintPass<'tcx> for IneffectiveOpenOptions {
|
|||
let mut write = None;
|
||||
|
||||
while let Some((name, recv, args, _, span)) = method_call(receiver) {
|
||||
if name == "append" {
|
||||
if name == sym::append {
|
||||
append = index_if_arg_is_boolean(args, span);
|
||||
} else if name == "write" {
|
||||
} else if name == sym::write {
|
||||
write = index_if_arg_is_boolean(args, span);
|
||||
}
|
||||
receiver = recv;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use clippy_utils::{higher, sym};
|
|||
use rustc_hir::{BorrowKind, Closure, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Symbol;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -119,33 +120,33 @@ use self::Heuristic::{All, Always, Any, First};
|
|||
/// returns an infinite or possibly infinite iterator. The finiteness
|
||||
/// is an upper bound, e.g., some methods can return a possibly
|
||||
/// infinite iterator at worst, e.g., `take_while`.
|
||||
const HEURISTICS: [(&str, usize, Heuristic, Finiteness); 19] = [
|
||||
("zip", 1, All, Infinite),
|
||||
("chain", 1, Any, Infinite),
|
||||
("cycle", 0, Always, Infinite),
|
||||
("map", 1, First, Infinite),
|
||||
("by_ref", 0, First, Infinite),
|
||||
("cloned", 0, First, Infinite),
|
||||
("rev", 0, First, Infinite),
|
||||
("inspect", 0, First, Infinite),
|
||||
("enumerate", 0, First, Infinite),
|
||||
("peekable", 1, First, Infinite),
|
||||
("fuse", 0, First, Infinite),
|
||||
("skip", 1, First, Infinite),
|
||||
("skip_while", 0, First, Infinite),
|
||||
("filter", 1, First, Infinite),
|
||||
("filter_map", 1, First, Infinite),
|
||||
("flat_map", 1, First, Infinite),
|
||||
("unzip", 0, First, Infinite),
|
||||
("take_while", 1, First, MaybeInfinite),
|
||||
("scan", 2, First, MaybeInfinite),
|
||||
const HEURISTICS: [(Symbol, usize, Heuristic, Finiteness); 19] = [
|
||||
(sym::zip, 1, All, Infinite),
|
||||
(sym::chain, 1, Any, Infinite),
|
||||
(sym::cycle, 0, Always, Infinite),
|
||||
(sym::map, 1, First, Infinite),
|
||||
(sym::by_ref, 0, First, Infinite),
|
||||
(sym::cloned, 0, First, Infinite),
|
||||
(sym::rev, 0, First, Infinite),
|
||||
(sym::inspect, 0, First, Infinite),
|
||||
(sym::enumerate, 0, First, Infinite),
|
||||
(sym::peekable, 1, First, Infinite),
|
||||
(sym::fuse, 0, First, Infinite),
|
||||
(sym::skip, 1, First, Infinite),
|
||||
(sym::skip_while, 0, First, Infinite),
|
||||
(sym::filter, 1, First, Infinite),
|
||||
(sym::filter_map, 1, First, Infinite),
|
||||
(sym::flat_map, 1, First, Infinite),
|
||||
(sym::unzip, 0, First, Infinite),
|
||||
(sym::take_while, 1, First, MaybeInfinite),
|
||||
(sym::scan, 2, First, MaybeInfinite),
|
||||
];
|
||||
|
||||
fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
|
||||
match expr.kind {
|
||||
ExprKind::MethodCall(method, receiver, args, _) => {
|
||||
for &(name, len, heuristic, cap) in &HEURISTICS {
|
||||
if method.ident.name.as_str() == name && args.len() == len {
|
||||
if method.ident.name == name && args.len() == len {
|
||||
return (match heuristic {
|
||||
Always => Infinite,
|
||||
First => is_infinite(cx, receiver),
|
||||
|
|
@ -183,36 +184,36 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
|
|||
|
||||
/// the names and argument lengths of methods that *may* exhaust their
|
||||
/// iterators
|
||||
const POSSIBLY_COMPLETING_METHODS: [(&str, usize); 6] = [
|
||||
("find", 1),
|
||||
("rfind", 1),
|
||||
("position", 1),
|
||||
("rposition", 1),
|
||||
("any", 1),
|
||||
("all", 1),
|
||||
const POSSIBLY_COMPLETING_METHODS: [(Symbol, usize); 6] = [
|
||||
(sym::find, 1),
|
||||
(sym::rfind, 1),
|
||||
(sym::position, 1),
|
||||
(sym::rposition, 1),
|
||||
(sym::any, 1),
|
||||
(sym::all, 1),
|
||||
];
|
||||
|
||||
/// the names and argument lengths of methods that *always* exhaust
|
||||
/// their iterators
|
||||
const COMPLETING_METHODS: [(&str, usize); 12] = [
|
||||
("count", 0),
|
||||
("fold", 2),
|
||||
("for_each", 1),
|
||||
("partition", 1),
|
||||
("max", 0),
|
||||
("max_by", 1),
|
||||
("max_by_key", 1),
|
||||
("min", 0),
|
||||
("min_by", 1),
|
||||
("min_by_key", 1),
|
||||
("sum", 0),
|
||||
("product", 0),
|
||||
const COMPLETING_METHODS: [(Symbol, usize); 12] = [
|
||||
(sym::count, 0),
|
||||
(sym::fold, 2),
|
||||
(sym::for_each, 1),
|
||||
(sym::partition, 1),
|
||||
(sym::max, 0),
|
||||
(sym::max_by, 1),
|
||||
(sym::max_by_key, 1),
|
||||
(sym::min, 0),
|
||||
(sym::min_by, 1),
|
||||
(sym::min_by_key, 1),
|
||||
(sym::sum, 0),
|
||||
(sym::product, 0),
|
||||
];
|
||||
|
||||
fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
|
||||
match expr.kind {
|
||||
ExprKind::MethodCall(method, receiver, args, _) => {
|
||||
let method_str = method.ident.name.as_str();
|
||||
let method_str = method.ident.name;
|
||||
for &(name, len) in &COMPLETING_METHODS {
|
||||
if method_str == name && args.len() == len {
|
||||
return is_infinite(cx, receiver);
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString {
|
|||
// Check if return type is String
|
||||
&& is_type_lang_item(cx, return_ty(cx, impl_item.owner_id), LangItem::String)
|
||||
// Filters instances of to_string which are required by a trait
|
||||
&& trait_ref_of_method(cx, impl_item.owner_id.def_id).is_none()
|
||||
&& trait_ref_of_method(cx, impl_item.owner_id).is_none()
|
||||
{
|
||||
show_lint(cx, impl_item);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -535,10 +535,10 @@ impl LateLintPass<'_> for ItemNameRepetitions {
|
|||
|
||||
if span_is_local(item.span) {
|
||||
match item.kind {
|
||||
ItemKind::Enum(_, def, _) => {
|
||||
ItemKind::Enum(_, _, def) => {
|
||||
self.check_variants(cx, item, &def);
|
||||
},
|
||||
ItemKind::Struct(_, VariantData::Struct { fields, .. }, _) => {
|
||||
ItemKind::Struct(_, _, VariantData::Struct { fields, .. }) => {
|
||||
self.check_fields(cx, item, fields);
|
||||
},
|
||||
_ => (),
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ impl_lint_pass!(LargeConstArrays => [LARGE_CONST_ARRAYS]);
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
if let ItemKind::Const(ident, _, generics, _) = &item.kind
|
||||
if let ItemKind::Const(ident, generics, _, _) = &item.kind
|
||||
// Since static items may not have generics, skip generic const items.
|
||||
// FIXME(generic_const_items): I don't think checking `generics.hwcp` suffices as it
|
||||
// doesn't account for empty where-clauses that only consist of keyword `where` IINM.
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]);
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) {
|
||||
if let ItemKind::Enum(ident, ref def, _) = item.kind
|
||||
if let ItemKind::Enum(ident, _, ref def) = item.kind
|
||||
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
|
||||
&& let ty::Adt(adt, subst) = ty.kind()
|
||||
&& adt.variants().len() > 1
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants {
|
|||
// so lint on the `use` statement directly.
|
||||
if let ItemKind::Use(path, kind @ (UseKind::Single(_) | UseKind::Glob)) = item.kind
|
||||
&& !item.span.in_external_macro(cx.sess().source_map())
|
||||
&& let Some(def_id) = path.res[0].opt_def_id()
|
||||
// use `present_items` because it could be in either type_ns or value_ns
|
||||
&& let Some(res) = path.res.present_items().next()
|
||||
&& let Some(def_id) = res.opt_def_id()
|
||||
&& self.msrv.meets(cx, msrvs::NUMERIC_ASSOCIATED_CONSTANTS)
|
||||
{
|
||||
let module = if is_integer_module(cx, def_id) {
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
|
|||
&& !local.span.in_external_macro(cx.tcx.sess.source_map())
|
||||
{
|
||||
let init_ty = cx.typeck_results().expr_ty(init);
|
||||
let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() {
|
||||
let contains_sync_guard = init_ty.walk().any(|inner| match inner.kind() {
|
||||
GenericArgKind::Type(inner_ty) => inner_ty
|
||||
.ty_adt_def()
|
||||
.is_some_and(|adt| paths::PARKING_LOT_GUARDS.iter().any(|path| path.matches(cx, adt.did()))),
|
||||
|
|
|
|||
|
|
@ -393,6 +393,7 @@ mod unwrap;
|
|||
mod unwrap_in_result;
|
||||
mod upper_case_acronyms;
|
||||
mod use_self;
|
||||
mod useless_concat;
|
||||
mod useless_conversion;
|
||||
mod vec;
|
||||
mod vec_init_then_push;
|
||||
|
|
@ -866,7 +867,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
store.register_late_pass(move |_| Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(conf)));
|
||||
store.register_late_pass(move |_| Box::new(lines_filter_map_ok::LinesFilterMapOk::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule));
|
||||
store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation));
|
||||
store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation::new(conf)));
|
||||
store.register_early_pass(move || Box::new(excessive_nesting::ExcessiveNesting::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(items_after_test_module::ItemsAfterTestModule));
|
||||
store.register_early_pass(|| Box::new(ref_patterns::RefPatterns));
|
||||
|
|
@ -937,6 +938,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
|
||||
store.register_early_pass(|| Box::new(empty_line_after::EmptyLineAfter::new()));
|
||||
store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(useless_concat::UselessConcat));
|
||||
store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern));
|
||||
store.register_late_pass(|_| Box::<unnecessary_semicolon::UnnecessarySemicolon>::default());
|
||||
store.register_late_pass(move |_| Box::new(non_std_lazy_statics::NonStdLazyStatic::new(conf)));
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
|
|||
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
|
||||
if let ImplItemKind::Fn(ref sig, id) = item.kind {
|
||||
let report_extra_lifetimes = trait_ref_of_method(cx, item.owner_id.def_id).is_none();
|
||||
let report_extra_lifetimes = trait_ref_of_method(cx, item.owner_id).is_none();
|
||||
check_fn_inner(
|
||||
cx,
|
||||
sig,
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ use clippy_config::Conf;
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{is_diag_item_method, is_trait_method, path_to_local_id};
|
||||
use clippy_utils::{is_diag_item_method, is_trait_method, path_to_local_id, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Body, Closure, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::Symbol;
|
||||
|
||||
pub struct LinesFilterMapOk {
|
||||
msrv: Msrv,
|
||||
|
|
@ -74,17 +74,17 @@ impl LateLintPass<'_> for LinesFilterMapOk {
|
|||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if let ExprKind::MethodCall(fm_method, fm_receiver, fm_args, fm_span) = expr.kind
|
||||
&& is_trait_method(cx, expr, sym::Iterator)
|
||||
&& let fm_method_str = fm_method.ident.as_str()
|
||||
&& matches!(fm_method_str, "filter_map" | "flat_map" | "flatten")
|
||||
&& let fm_method_name = fm_method.ident.name
|
||||
&& matches!(fm_method_name, sym::filter_map | sym::flat_map | sym::flatten)
|
||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty_adjusted(fm_receiver), sym::IoLines)
|
||||
&& should_lint(cx, fm_args, fm_method_str)
|
||||
&& should_lint(cx, fm_args, fm_method_name)
|
||||
&& self.msrv.meets(cx, msrvs::MAP_WHILE)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LINES_FILTER_MAP_OK,
|
||||
fm_span,
|
||||
format!("`{fm_method_str}()` will run forever if the iterator repeatedly produces an `Err`",),
|
||||
format!("`{fm_method_name}()` will run forever if the iterator repeatedly produces an `Err`",),
|
||||
|diag| {
|
||||
diag.span_note(
|
||||
fm_receiver.span,
|
||||
|
|
@ -101,9 +101,9 @@ impl LateLintPass<'_> for LinesFilterMapOk {
|
|||
}
|
||||
}
|
||||
|
||||
fn should_lint(cx: &LateContext<'_>, args: &[Expr<'_>], method_str: &str) -> bool {
|
||||
fn should_lint(cx: &LateContext<'_>, args: &[Expr<'_>], method_name: Symbol) -> bool {
|
||||
match args {
|
||||
[] => method_str == "flatten",
|
||||
[] => method_name == sym::flatten,
|
||||
[fm_arg] => {
|
||||
match &fm_arg.kind {
|
||||
// Detect `Result::ok`
|
||||
|
|
@ -120,7 +120,7 @@ fn should_lint(cx: &LateContext<'_>, args: &[Expr<'_>], method_str: &str) -> boo
|
|||
&& path_to_local_id(receiver, param.pat.hir_id)
|
||||
&& let Some(method_did) = cx.typeck_results().type_dependent_def_id(value.hir_id)
|
||||
{
|
||||
is_diag_item_method(cx, method_did, sym::Result) && method.ident.as_str() == "ok"
|
||||
is_diag_item_method(cx, method_did, sym::Result) && method.ident.name == sym::ok
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ use std::ops::ControlFlow;
|
|||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::ty::is_type_lang_item;
|
||||
use clippy_utils::visitors::for_each_expr;
|
||||
use clippy_utils::{eq_expr_value, higher, path_to_local_id};
|
||||
use clippy_utils::{eq_expr_value, higher, path_to_local_id, sym};
|
||||
use rustc_errors::{Applicability, MultiSpan};
|
||||
use rustc_hir::{Expr, ExprKind, LangItem, Node, Pat, PatKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::{Span, sym};
|
||||
use rustc_span::{Span, Symbol};
|
||||
|
||||
use super::CHAR_INDICES_AS_BYTE_INDICES;
|
||||
|
||||
|
|
@ -16,22 +16,22 @@ use super::CHAR_INDICES_AS_BYTE_INDICES;
|
|||
// Note: `String` also has methods that work with byte indices,
|
||||
// but they all take `&mut self` and aren't worth considering since the user couldn't have called
|
||||
// them while the chars iterator is live anyway.
|
||||
const BYTE_INDEX_METHODS: &[&str] = &[
|
||||
"is_char_boundary",
|
||||
"floor_char_boundary",
|
||||
"ceil_char_boundary",
|
||||
"get",
|
||||
"index",
|
||||
"index_mut",
|
||||
"get_mut",
|
||||
"get_unchecked",
|
||||
"get_unchecked_mut",
|
||||
"slice_unchecked",
|
||||
"slice_mut_unchecked",
|
||||
"split_at",
|
||||
"split_at_mut",
|
||||
"split_at_checked",
|
||||
"split_at_mut_checked",
|
||||
const BYTE_INDEX_METHODS: &[Symbol] = &[
|
||||
sym::ceil_char_boundary,
|
||||
sym::floor_char_boundary,
|
||||
sym::get,
|
||||
sym::get_mut,
|
||||
sym::get_unchecked,
|
||||
sym::get_unchecked_mut,
|
||||
sym::index,
|
||||
sym::index_mut,
|
||||
sym::is_char_boundary,
|
||||
sym::slice_mut_unchecked,
|
||||
sym::slice_unchecked,
|
||||
sym::split_at,
|
||||
sym::split_at_checked,
|
||||
sym::split_at_mut,
|
||||
sym::split_at_mut_checked,
|
||||
];
|
||||
|
||||
const CONTINUE: ControlFlow<!, ()> = ControlFlow::Continue(());
|
||||
|
|
@ -88,7 +88,7 @@ fn check_index_usage<'tcx>(
|
|||
// (contrary to the `ExprKind::Index` case which needs to handle both with `is_string_like` because `String` implements
|
||||
// `Index` directly and no deref to `str` would happen in that case).
|
||||
if cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_str()
|
||||
&& BYTE_INDEX_METHODS.contains(&segment.ident.name.as_str())
|
||||
&& BYTE_INDEX_METHODS.contains(&segment.ident.name)
|
||||
&& eq_expr_value(cx, chars_recv, recv) =>
|
||||
{
|
||||
"passing a character position to a method that expects a byte index"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use super::EXPLICIT_INTO_ITER_LOOP;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_trait_method;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -76,7 +76,7 @@ pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<
|
|||
};
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let object = snippet_with_applicability(cx, self_arg.span, "_", &mut applicability);
|
||||
let object = snippet_with_context(cx, self_arg.span, call_expr.span.ctxt(), "_", &mut applicability).0;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPLICIT_INTO_ITER_LOOP,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use super::EXPLICIT_ITER_LOOP;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::sym;
|
||||
use clippy_utils::ty::{
|
||||
implements_trait, implements_trait_with_env, is_copy, is_type_lang_item, make_normalized_projection,
|
||||
|
|
@ -36,7 +36,7 @@ pub(super) fn check(
|
|||
}
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let object = snippet_with_applicability(cx, self_arg.span, "_", &mut applicability);
|
||||
let object = snippet_with_context(cx, self_arg.span, call_expr.span.ctxt(), "_", &mut applicability).0;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPLICIT_ITER_LOOP,
|
||||
|
|
|
|||
|
|
@ -83,6 +83,13 @@ pub(super) fn check<'tcx>(
|
|||
)[..],
|
||||
);
|
||||
}
|
||||
|
||||
// If the return type requires adjustments, we need to add a `.map` after the iterator
|
||||
let inner_ret_adjust = cx.typeck_results().expr_adjustments(inner_ret);
|
||||
if !inner_ret_adjust.is_empty() {
|
||||
snippet.push_str(".map(|v| v as _)");
|
||||
}
|
||||
|
||||
// Extends to `last_stmt` to include semicolon in case of `return None;`
|
||||
let lint_span = span.to(last_stmt.span).to(last_ret.span);
|
||||
span_lint_and_then(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use super::utils::make_iterator_snippet;
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::visitors::is_local_used;
|
||||
use clippy_utils::{higher, path_to_local_id, peel_blocks_with_stmt};
|
||||
use clippy_utils::{higher, is_refutable, path_to_local_id, peel_blocks_with_stmt};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Expr, Pat, PatKind};
|
||||
|
|
@ -28,7 +28,7 @@ pub(super) fn check<'tcx>(
|
|||
&& let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind
|
||||
&& path_to_local_id(let_expr, pat_hir_id)
|
||||
// Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result`
|
||||
&& let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind
|
||||
&& let PatKind::TupleStruct(ref qpath, [inner_pat], _) = let_pat.kind
|
||||
&& let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id)
|
||||
&& let Some(variant_id) = cx.tcx.opt_parent(ctor_id)
|
||||
&& let some_ctor = cx.tcx.lang_items().option_some_variant() == Some(variant_id)
|
||||
|
|
@ -37,6 +37,7 @@ pub(super) fn check<'tcx>(
|
|||
// Ensure expr in `if let` is not used afterwards
|
||||
&& !is_local_used(cx, if_then, pat_hir_id)
|
||||
&& msrv.meets(cx, msrvs::ITER_FLATTEN)
|
||||
&& !is_refutable(cx, inner_pat)
|
||||
{
|
||||
let if_let_type = if some_ctor { "Some" } else { "Ok" };
|
||||
// Prepare the error message
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ mod while_let_loop;
|
|||
mod while_let_on_iterator;
|
||||
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::higher;
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use clippy_utils::{higher, sym};
|
||||
use rustc_ast::Label;
|
||||
use rustc_hir::{Expr, ExprKind, LoopSource, Pat};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
@ -909,15 +909,17 @@ impl Loops {
|
|||
}
|
||||
|
||||
fn check_for_loop_arg(&self, cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {
|
||||
if let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind {
|
||||
match method.ident.as_str() {
|
||||
"iter" | "iter_mut" => {
|
||||
if !arg.span.from_expansion()
|
||||
&& let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind
|
||||
{
|
||||
match method.ident.name {
|
||||
sym::iter | sym::iter_mut => {
|
||||
explicit_iter_loop::check(cx, self_arg, arg, self.msrv, self.enforce_iter_loop_reborrow);
|
||||
},
|
||||
"into_iter" => {
|
||||
sym::into_iter => {
|
||||
explicit_into_iter_loop::check(cx, self_arg, arg);
|
||||
},
|
||||
"next" => {
|
||||
sym::next => {
|
||||
iter_next_loop::check(cx, arg);
|
||||
},
|
||||
_ => {},
|
||||
|
|
|
|||
|
|
@ -1,68 +1,65 @@
|
|||
use super::WHILE_LET_LOOP;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::higher;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::source::{snippet, snippet_indent, snippet_opt};
|
||||
use clippy_utils::ty::needs_ordered_drop;
|
||||
use clippy_utils::visitors::any_temporaries_need_ordered_drop;
|
||||
use clippy_utils::{higher, peel_blocks};
|
||||
use rustc_ast::BindingMode;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Block, Expr, ExprKind, LetStmt, MatchSource, Pat, StmtKind};
|
||||
use rustc_hir::{Block, Expr, ExprKind, LetStmt, MatchSource, Pat, PatKind, Path, QPath, StmtKind, Ty};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) {
|
||||
let (init, has_trailing_exprs) = match (loop_block.stmts, loop_block.expr) {
|
||||
([stmt, stmts @ ..], expr) => {
|
||||
if let StmtKind::Let(&LetStmt {
|
||||
let (init, let_info) = match (loop_block.stmts, loop_block.expr) {
|
||||
([stmt, ..], _) => match stmt.kind {
|
||||
StmtKind::Let(LetStmt {
|
||||
init: Some(e),
|
||||
els: None,
|
||||
pat,
|
||||
ty,
|
||||
..
|
||||
})
|
||||
| StmtKind::Semi(e)
|
||||
| StmtKind::Expr(e) = stmt.kind
|
||||
{
|
||||
(e, !stmts.is_empty() || expr.is_some())
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}) => (*e, Some((*pat, *ty))),
|
||||
StmtKind::Semi(e) | StmtKind::Expr(e) => (e, None),
|
||||
_ => return,
|
||||
},
|
||||
([], Some(e)) => (e, false),
|
||||
([], Some(e)) => (e, None),
|
||||
_ => return,
|
||||
};
|
||||
let has_trailing_exprs = loop_block.stmts.len() + usize::from(loop_block.expr.is_some()) > 1;
|
||||
|
||||
if let Some(if_let) = higher::IfLet::hir(cx, init)
|
||||
&& let Some(else_expr) = if_let.if_else
|
||||
&& is_simple_break_expr(else_expr)
|
||||
{
|
||||
could_be_while_let(cx, expr, if_let.let_pat, if_let.let_expr, has_trailing_exprs);
|
||||
could_be_while_let(
|
||||
cx,
|
||||
expr,
|
||||
if_let.let_pat,
|
||||
if_let.let_expr,
|
||||
has_trailing_exprs,
|
||||
let_info,
|
||||
if_let.if_then,
|
||||
);
|
||||
} else if let ExprKind::Match(scrutinee, [arm1, arm2], MatchSource::Normal) = init.kind
|
||||
&& arm1.guard.is_none()
|
||||
&& arm2.guard.is_none()
|
||||
&& is_simple_break_expr(arm2.body)
|
||||
{
|
||||
could_be_while_let(cx, expr, arm1.pat, scrutinee, has_trailing_exprs);
|
||||
could_be_while_let(cx, expr, arm1.pat, scrutinee, has_trailing_exprs, let_info, arm1.body);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if expr contains a single break expression without a label or eub-expression.
|
||||
/// Returns `true` if expr contains a single break expression without a label or sub-expression,
|
||||
/// possibly embedded in blocks.
|
||||
fn is_simple_break_expr(e: &Expr<'_>) -> bool {
|
||||
matches!(peel_blocks(e).kind, ExprKind::Break(dest, None) if dest.label.is_none())
|
||||
}
|
||||
|
||||
/// Removes any blocks containing only a single expression.
|
||||
fn peel_blocks<'tcx>(e: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
|
||||
if let ExprKind::Block(b, _) = e.kind {
|
||||
match (b.stmts, b.expr) {
|
||||
([s], None) => {
|
||||
if let StmtKind::Expr(e) | StmtKind::Semi(e) = s.kind {
|
||||
peel_blocks(e)
|
||||
} else {
|
||||
e
|
||||
}
|
||||
},
|
||||
([], Some(e)) => peel_blocks(e),
|
||||
_ => e,
|
||||
([s], None) => matches!(s.kind, StmtKind::Expr(e) | StmtKind::Semi(e) if is_simple_break_expr(e)),
|
||||
([], Some(e)) => is_simple_break_expr(e),
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
e
|
||||
matches!(e.kind, ExprKind::Break(dest, None) if dest.label.is_none())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +69,8 @@ fn could_be_while_let<'tcx>(
|
|||
let_pat: &'tcx Pat<'_>,
|
||||
let_expr: &'tcx Expr<'_>,
|
||||
has_trailing_exprs: bool,
|
||||
let_info: Option<(&Pat<'_>, Option<&Ty<'_>>)>,
|
||||
inner_expr: &Expr<'_>,
|
||||
) {
|
||||
if has_trailing_exprs
|
||||
&& (needs_ordered_drop(cx, cx.typeck_results().expr_ty(let_expr))
|
||||
|
|
@ -86,7 +85,24 @@ fn could_be_while_let<'tcx>(
|
|||
// 1) it was ugly with big bodies;
|
||||
// 2) it was not indented properly;
|
||||
// 3) it wasn’t very smart (see #675).
|
||||
let mut applicability = Applicability::HasPlaceholders;
|
||||
let inner_content = if let Some((pat, ty)) = let_info
|
||||
// Prevent trivial reassignments such as `let x = x;` or `let _ = …;`, but
|
||||
// keep them if the type has been explicitly specified.
|
||||
&& (!is_trivial_assignment(pat, peel_blocks(inner_expr)) || ty.is_some())
|
||||
&& let Some(pat_str) = snippet_opt(cx, pat.span)
|
||||
&& let Some(init_str) = snippet_opt(cx, peel_blocks(inner_expr).span)
|
||||
{
|
||||
let ty_str = ty
|
||||
.map(|ty| format!(": {}", snippet(cx, ty.span, "_")))
|
||||
.unwrap_or_default();
|
||||
format!(
|
||||
"\n{indent} let {pat_str}{ty_str} = {init_str};\n{indent} ..\n{indent}",
|
||||
indent = snippet_indent(cx, expr.span).unwrap_or_default(),
|
||||
)
|
||||
} else {
|
||||
" .. ".into()
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
WHILE_LET_LOOP,
|
||||
|
|
@ -94,10 +110,21 @@ fn could_be_while_let<'tcx>(
|
|||
"this loop could be written as a `while let` loop",
|
||||
"try",
|
||||
format!(
|
||||
"while let {} = {} {{ .. }}",
|
||||
snippet_with_applicability(cx, let_pat.span, "..", &mut applicability),
|
||||
snippet_with_applicability(cx, let_expr.span, "..", &mut applicability),
|
||||
"while let {} = {} {{{inner_content}}}",
|
||||
snippet(cx, let_pat.span, ".."),
|
||||
snippet(cx, let_expr.span, ".."),
|
||||
),
|
||||
applicability,
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
}
|
||||
|
||||
fn is_trivial_assignment(pat: &Pat<'_>, init: &Expr<'_>) -> bool {
|
||||
match (pat.kind, init.kind) {
|
||||
(PatKind::Wild, _) => true,
|
||||
(
|
||||
PatKind::Binding(BindingMode::NONE, _, pat_ident, None),
|
||||
ExprKind::Path(QPath::Resolved(None, Path { segments: [init], .. })),
|
||||
) => pat_ident.name == init.ident.name,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,10 +100,7 @@ impl LateLintPass<'_> for MacroUseImports {
|
|||
&& let hir_id = item.hir_id()
|
||||
&& let attrs = cx.tcx.hir_attrs(hir_id)
|
||||
&& let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use))
|
||||
&& let Some(id) = path.res.iter().find_map(|res| match res {
|
||||
Res::Def(DefKind::Mod, id) => Some(id),
|
||||
_ => None,
|
||||
})
|
||||
&& let Some(Res::Def(DefKind::Mod, id)) = path.res.type_ns
|
||||
&& !id.is_local()
|
||||
{
|
||||
for kid in cx.tcx.module_children(id) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
|||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::ty_from_hir_ty;
|
||||
use clippy_utils::{SpanlessEq, is_in_const_context, is_integer_literal};
|
||||
use clippy_utils::{SpanlessEq, is_in_const_context, is_integer_literal, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
@ -103,7 +103,7 @@ fn count_ones_receiver<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Optio
|
|||
} else {
|
||||
return None;
|
||||
};
|
||||
(method.ident.as_str() == "count_ones" && matches!(ty.kind(), ty::Uint(_))).then_some(receiver)
|
||||
(method.ident.name == sym::count_ones && matches!(ty.kind(), ty::Uint(_))).then_some(receiver)
|
||||
}
|
||||
|
||||
/// Return `greater` if `smaller == greater - 1`
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustive {
|
|||
}
|
||||
|
||||
match item.kind {
|
||||
ItemKind::Enum(_, def, _) if def.variants.len() > 1 => {
|
||||
ItemKind::Enum(_, _, def) if def.variants.len() > 1 => {
|
||||
let iter = def.variants.iter().filter_map(|v| {
|
||||
(matches!(v.data, VariantData::Unit(_, _)) && is_doc_hidden(cx.tcx.hir_attrs(v.hir_id)))
|
||||
.then_some((v.def_id, v.span))
|
||||
|
|
@ -98,7 +98,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustive {
|
|||
self.potential_enums.push((item.owner_id.def_id, id, item.span, span));
|
||||
}
|
||||
},
|
||||
ItemKind::Struct(_, variant_data, _) => {
|
||||
ItemKind::Struct(_, _, variant_data) => {
|
||||
let fields = variant_data.fields();
|
||||
let private_fields = fields
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::{expr_or_init, is_in_const_context, std_or_core};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
|
@ -36,20 +38,33 @@ declare_clippy_lint! {
|
|||
complexity,
|
||||
"manual slice size calculation"
|
||||
}
|
||||
declare_lint_pass!(ManualSliceSizeCalculation => [MANUAL_SLICE_SIZE_CALCULATION]);
|
||||
impl_lint_pass!(ManualSliceSizeCalculation => [MANUAL_SLICE_SIZE_CALCULATION]);
|
||||
|
||||
pub struct ManualSliceSizeCalculation {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
||||
impl ManualSliceSizeCalculation {
|
||||
pub fn new(conf: &Conf) -> Self {
|
||||
Self { msrv: conf.msrv }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ManualSliceSizeCalculation {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if let ExprKind::Binary(ref op, left, right) = expr.kind
|
||||
&& BinOpKind::Mul == op.node
|
||||
&& !expr.span.from_expansion()
|
||||
// Does not apply inside const because size_of_val is not cost in stable.
|
||||
&& !is_in_const_context(cx)
|
||||
&& let Some((receiver, refs_count)) = simplify(cx, left, right)
|
||||
&& (!is_in_const_context(cx) || self.msrv.meets(cx, msrvs::CONST_SIZE_OF_VAL))
|
||||
{
|
||||
let ctxt = expr.span.ctxt();
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let deref = "*".repeat(refs_count - 1);
|
||||
let deref = if refs_count > 0 {
|
||||
"*".repeat(refs_count - 1)
|
||||
} else {
|
||||
"&".into()
|
||||
};
|
||||
let val_name = snippet_with_context(cx, receiver.span, ctxt, "slice", &mut app).0;
|
||||
let Some(sugg) = std_or_core(cx) else { return };
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use rustc_hir::{Expr, ExprKind, PathSegment, QPath, TyKind};
|
|||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::{Span, sym, symbol};
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -67,7 +67,7 @@ impl LateLintPass<'_> for ManualStringNew {
|
|||
fn is_expr_kind_empty_str(expr_kind: &ExprKind<'_>) -> bool {
|
||||
if let ExprKind::Lit(lit) = expr_kind
|
||||
&& let LitKind::Str(value, _) = lit.node
|
||||
&& value == symbol::kw::Empty
|
||||
&& value == sym::empty
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ impl LateLintPass<'_> for MapUnit {
|
|||
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) {
|
||||
if let hir::StmtKind::Semi(expr) = stmt.kind
|
||||
&& !stmt.span.from_expansion()
|
||||
&& let Some(arglists) = method_chain_args(expr, &["map"])
|
||||
&& let Some(arglists) = method_chain_args(expr, &[sym::map])
|
||||
{
|
||||
lint_map_unit_fn(cx, stmt, expr, arglists[0]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ fn handle(
|
|||
&& implements_trait(cx, expr_type, default_trait_id, &[])
|
||||
// We check if the initial condition implements Default.
|
||||
&& let Some(condition_ty) = cx.typeck_results().expr_ty(condition).walk().nth(1)
|
||||
&& let GenericArgKind::Type(condition_ty) = condition_ty.unpack()
|
||||
&& let GenericArgKind::Type(condition_ty) = condition_ty.kind()
|
||||
&& implements_trait(cx, condition_ty, default_trait_id, &[])
|
||||
&& is_default_equivalent(cx, peel_blocks(body_none))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{SpanlessEq, SpanlessHash, is_lint_allowed, path_to_local, search_same};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::{SpanlessEq, SpanlessHash, fulfill_or_allowed, is_lint_allowed, path_to_local, search_same};
|
||||
use core::cmp::Ordering;
|
||||
use core::{iter, slice};
|
||||
use itertools::Itertools;
|
||||
use rustc_arena::DroplessArena;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
|
|
@ -110,57 +111,68 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
|
|||
&& check_same_body()
|
||||
};
|
||||
|
||||
let mut appl = Applicability::MaybeIncorrect;
|
||||
let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
|
||||
for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) {
|
||||
if matches!(arm2.pat.kind, PatKind::Wild) {
|
||||
if !cx.tcx.features().non_exhaustive_omitted_patterns_lint()
|
||||
|| is_lint_allowed(cx, NON_EXHAUSTIVE_OMITTED_PATTERNS, arm2.hir_id)
|
||||
{
|
||||
let arm_span = adjusted_arm_span(cx, arm1.span);
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
MATCH_SAME_ARMS,
|
||||
arm1.hir_id,
|
||||
arm_span,
|
||||
"this match arm has an identical body to the `_` wildcard arm",
|
||||
|diag| {
|
||||
diag.span_suggestion(arm_span, "try removing the arm", "", appl)
|
||||
.help("or try changing either arm body")
|
||||
.span_note(arm2.span, "`_` wildcard arm here");
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let back_block = backwards_blocking_idxs[j];
|
||||
let (keep_arm, move_arm) = if back_block < i || (back_block == 0 && forwards_blocking_idxs[i] <= j) {
|
||||
(arm1, arm2)
|
||||
} else {
|
||||
(arm2, arm1)
|
||||
};
|
||||
for mut group in search_same(&indexed_arms, hash, eq) {
|
||||
// Filter out (and fulfill) `#[allow]`ed and `#[expect]`ed arms
|
||||
group.retain(|(_, arm)| !fulfill_or_allowed(cx, MATCH_SAME_ARMS, [arm.hir_id]));
|
||||
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
MATCH_SAME_ARMS,
|
||||
keep_arm.hir_id,
|
||||
keep_arm.span,
|
||||
"this match arm has an identical body to another arm",
|
||||
|diag| {
|
||||
let move_pat_snip = snippet_with_applicability(cx, move_arm.pat.span, "<pat2>", &mut appl);
|
||||
let keep_pat_snip = snippet_with_applicability(cx, keep_arm.pat.span, "<pat1>", &mut appl);
|
||||
|
||||
diag.multipart_suggestion(
|
||||
"or try merging the arm patterns and removing the obsolete arm",
|
||||
vec![
|
||||
(keep_arm.pat.span, format!("{keep_pat_snip} | {move_pat_snip}")),
|
||||
(adjusted_arm_span(cx, move_arm.span), String::new()),
|
||||
],
|
||||
appl,
|
||||
)
|
||||
.help("try changing either arm body");
|
||||
},
|
||||
);
|
||||
if group.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MATCH_SAME_ARMS,
|
||||
group.iter().map(|(_, arm)| arm.span).collect_vec(),
|
||||
"these match arms have identical bodies",
|
||||
|diag| {
|
||||
diag.help("if this is unintentional make the arms return different values");
|
||||
|
||||
if let [prev @ .., (_, last)] = group.as_slice()
|
||||
&& is_wildcard_arm(last.pat)
|
||||
&& is_lint_allowed(cx, NON_EXHAUSTIVE_OMITTED_PATTERNS, last.hir_id)
|
||||
{
|
||||
diag.span_label(last.span, "the wildcard arm");
|
||||
|
||||
let s = if prev.len() > 1 { "s" } else { "" };
|
||||
diag.multipart_suggestion_verbose(
|
||||
format!("otherwise remove the non-wildcard arm{s}"),
|
||||
prev.iter()
|
||||
.map(|(_, arm)| (adjusted_arm_span(cx, arm.span), String::new()))
|
||||
.collect(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else if let &[&(first_idx, _), .., &(last_idx, _)] = group.as_slice() {
|
||||
let back_block = backwards_blocking_idxs[last_idx];
|
||||
let split = if back_block < first_idx
|
||||
|| (back_block == 0 && forwards_blocking_idxs[first_idx] <= last_idx)
|
||||
{
|
||||
group.split_first()
|
||||
} else {
|
||||
group.split_last()
|
||||
};
|
||||
|
||||
if let Some(((_, dest), src)) = split
|
||||
&& let Some(pat_snippets) = group
|
||||
.iter()
|
||||
.map(|(_, arm)| arm.pat.span.get_source_text(cx))
|
||||
.collect::<Option<Vec<_>>>()
|
||||
{
|
||||
let mut suggs = src
|
||||
.iter()
|
||||
.map(|(_, arm)| (adjusted_arm_span(cx, arm.span), String::new()))
|
||||
.collect_vec();
|
||||
|
||||
suggs.push((dest.pat.span, pat_snippets.iter().join(" | ")));
|
||||
diag.multipart_suggestion_verbose(
|
||||
"otherwise merge the patterns into a single arm",
|
||||
suggs,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -450,3 +462,11 @@ fn bindings_eq(pat: &Pat<'_>, mut ids: HirIdSet) -> bool {
|
|||
pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.swap_remove(&id));
|
||||
result && ids.is_empty()
|
||||
}
|
||||
|
||||
fn is_wildcard_arm(pat: &Pat<'_>) -> bool {
|
||||
match pat.kind {
|
||||
PatKind::Wild => true,
|
||||
PatKind::Or([.., last]) => matches!(last.kind, PatKind::Wild),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ use clippy_config::Conf;
|
|||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::walk_span_to_context;
|
||||
use clippy_utils::{
|
||||
higher, is_direct_expn_of, is_in_const_context, is_span_match, span_contains_cfg, span_extract_comments,
|
||||
higher, is_direct_expn_of, is_in_const_context, is_span_match, span_contains_cfg, span_extract_comments, sym,
|
||||
};
|
||||
use rustc_hir::{Arm, Expr, ExprKind, LetStmt, MatchSource, Pat, PatKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
|
|
@ -1053,13 +1053,13 @@ impl_lint_pass!(Matches => [
|
|||
impl<'tcx> LateLintPass<'tcx> for Matches {
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if is_direct_expn_of(expr.span, "matches").is_none() && expr.span.in_external_macro(cx.sess().source_map()) {
|
||||
if is_direct_expn_of(expr.span, sym::matches).is_none() && expr.span.in_external_macro(cx.sess().source_map()) {
|
||||
return;
|
||||
}
|
||||
let from_expansion = expr.span.from_expansion();
|
||||
|
||||
if let ExprKind::Match(ex, arms, source) = expr.kind {
|
||||
if is_direct_expn_of(expr.span, "matches").is_some()
|
||||
if is_direct_expn_of(expr.span, sym::matches).is_some()
|
||||
&& let [arm, _] = arms
|
||||
{
|
||||
redundant_pattern_match::check_match(cx, expr, ex, arms);
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>])
|
|||
}
|
||||
|
||||
if let PatKind::Wild = arm.pat.kind {
|
||||
if !eq_expr_value(cx, match_expr, strip_return(arm_expr)) {
|
||||
if !eq_expr_value(cx, match_expr, arm_expr) {
|
||||
return false;
|
||||
}
|
||||
} else if !pat_same_as_expr(arm.pat, arm_expr) {
|
||||
|
|
@ -103,27 +103,18 @@ fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool
|
|||
if matches!(else_expr.kind, ExprKind::Block(..)) {
|
||||
return false;
|
||||
}
|
||||
let ret = strip_return(else_expr);
|
||||
let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
|
||||
if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) {
|
||||
return is_res_lang_ctor(cx, path_res(cx, ret), OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
|
||||
return is_res_lang_ctor(cx, path_res(cx, else_expr), OptionNone)
|
||||
|| eq_expr_value(cx, if_let.let_expr, else_expr);
|
||||
}
|
||||
return eq_expr_value(cx, if_let.let_expr, ret);
|
||||
return eq_expr_value(cx, if_let.let_expr, else_expr);
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Strip `return` keyword if the expression type is `ExprKind::Ret`.
|
||||
fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
|
||||
if let ExprKind::Ret(Some(ret)) = expr.kind {
|
||||
ret
|
||||
} else {
|
||||
expr
|
||||
}
|
||||
}
|
||||
|
||||
/// Manually check for coercion casting by checking if the type of the match operand or let expr
|
||||
/// differs with the assigned local variable or the function return type.
|
||||
fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>) -> bool {
|
||||
|
|
@ -161,7 +152,6 @@ fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>
|
|||
}
|
||||
|
||||
fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
|
||||
let expr = strip_return(expr);
|
||||
match (&pat.kind, &expr.kind) {
|
||||
// Example: `Some(val) => Some(val)`
|
||||
(PatKind::TupleStruct(QPath::Resolved(_, path), tuple_params, _), ExprKind::Call(call_expr, call_params)) => {
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ use clippy_utils::macros::matching_root_macro_call;
|
|||
use clippy_utils::msrvs::Msrv;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::visitors::{for_each_expr_without_closures, is_local_used};
|
||||
use clippy_utils::{is_in_const_context, path_to_local};
|
||||
use clippy_utils::{is_in_const_context, path_to_local, sym};
|
||||
use rustc_ast::{BorrowKind, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, MatchSource, Node, PatKind, UnOp};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{Span, sym};
|
||||
use rustc_span::{Span, Symbol};
|
||||
use std::borrow::Cow;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv:
|
|||
} else if let ExprKind::MethodCall(path, recv, args, ..) = guard.kind
|
||||
&& let Some(binding) = get_pat_binding(cx, recv, outer_arm)
|
||||
{
|
||||
check_method_calls(cx, outer_arm, path.ident.name.as_str(), recv, args, guard, &binding);
|
||||
check_method_calls(cx, outer_arm, path.ident.name, recv, args, guard, &binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -103,7 +103,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv:
|
|||
fn check_method_calls<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
arm: &Arm<'tcx>,
|
||||
method: &str,
|
||||
method: Symbol,
|
||||
recv: &Expr<'_>,
|
||||
args: &[Expr<'_>],
|
||||
if_expr: &Expr<'_>,
|
||||
|
|
@ -112,7 +112,7 @@ fn check_method_calls<'tcx>(
|
|||
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
|
||||
let slice_like = ty.is_slice() || ty.is_array();
|
||||
|
||||
let sugg = if method == "is_empty" {
|
||||
let sugg = if method == sym::is_empty {
|
||||
// `s if s.is_empty()` becomes ""
|
||||
// `arr if arr.is_empty()` becomes []
|
||||
|
||||
|
|
@ -137,9 +137,9 @@ fn check_method_calls<'tcx>(
|
|||
|
||||
if needles.is_empty() {
|
||||
sugg.insert_str(1, "..");
|
||||
} else if method == "starts_with" {
|
||||
} else if method == sym::starts_with {
|
||||
sugg.insert_str(sugg.len() - 1, ", ..");
|
||||
} else if method == "ends_with" {
|
||||
} else if method == sym::ends_with {
|
||||
sugg.insert_str(1, ".., ");
|
||||
} else {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ fn find_match_true<'tcx>(
|
|||
fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
|
||||
if let ty::Adt(_, subs) = ty.kind()
|
||||
&& let Some(sub) = subs.get(index)
|
||||
&& let GenericArgKind::Type(sub_ty) = sub.unpack()
|
||||
&& let GenericArgKind::Type(sub_ty) = sub.kind()
|
||||
{
|
||||
Some(sub_ty)
|
||||
} else {
|
||||
|
|
@ -273,7 +273,7 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op
|
|||
let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
|
||||
|
||||
if let Some((good_method, maybe_guard)) = found_good_method(cx, arms, node_pair) {
|
||||
let span = is_expn_of(expr.span, "matches").unwrap_or(expr.span.to(op.span));
|
||||
let span = is_expn_of(expr.span, sym::matches).unwrap_or(expr.span.to(op.span));
|
||||
let result_expr = match &op.kind {
|
||||
ExprKind::AddrOf(_, _, borrowed) => borrowed,
|
||||
_ => op,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::FxHashSet;
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{first_line_of_span, indent_of, snippet};
|
||||
use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy};
|
||||
use clippy_utils::{get_attr, is_lint_allowed};
|
||||
use clippy_utils::{get_attr, is_lint_allowed, sym};
|
||||
use itertools::Itertools;
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_data_structures::fx::FxIndexSet;
|
||||
|
|
@ -186,7 +186,7 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
|
|||
&& get_attr(
|
||||
self.cx.sess(),
|
||||
self.cx.tcx.get_attrs_unchecked(adt.did()),
|
||||
"has_significant_drop",
|
||||
sym::has_significant_drop,
|
||||
)
|
||||
.count()
|
||||
> 0
|
||||
|
|
@ -208,12 +208,12 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
|
|||
// (to avoid false positive on `Ref<'a, MutexGuard<Foo>>`)
|
||||
|| (args
|
||||
.iter()
|
||||
.all(|arg| !matches!(arg.unpack(), GenericArgKind::Lifetime(_)))
|
||||
.all(|arg| !matches!(arg.kind(), GenericArgKind::Lifetime(_)))
|
||||
// some generic parameter has significant drop
|
||||
// (to avoid false negative on `Box<MutexGuard<Foo>>`)
|
||||
&& args
|
||||
.iter()
|
||||
.filter_map(|arg| match arg.unpack() {
|
||||
.filter_map(|arg| match arg.kind() {
|
||||
GenericArgKind::Type(ty) => Some(ty),
|
||||
_ => None,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sym;
|
||||
use clippy_utils::ty::is_type_lang_item;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, LangItem};
|
||||
|
|
@ -25,7 +26,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx E
|
|||
|
||||
if let Some(parent) = clippy_utils::get_parent_expr(cx, expr)
|
||||
&& let Some((name, _, _, _, _)) = method_call(parent)
|
||||
&& name == "unwrap"
|
||||
&& name == sym::unwrap
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@ use rustc_errors::Applicability;
|
|||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, Lint};
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::Symbol;
|
||||
|
||||
/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
info: &crate::methods::BinaryExprInfo<'_>,
|
||||
chain_methods: &[&str],
|
||||
chain_methods: &[Symbol],
|
||||
lint: &'static Lint,
|
||||
suggest: &str,
|
||||
) -> bool {
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@ use rustc_ast::ast;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, Lint};
|
||||
use rustc_span::Symbol;
|
||||
|
||||
/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints with `unwrap()`.
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
info: &crate::methods::BinaryExprInfo<'_>,
|
||||
chain_methods: &[&str],
|
||||
chain_methods: &[Symbol],
|
||||
lint: &'static Lint,
|
||||
suggest: &str,
|
||||
) -> bool {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
use crate::methods::chars_cmp;
|
||||
use clippy_utils::sym;
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::CHARS_LAST_CMP;
|
||||
|
||||
/// Checks for the `CHARS_LAST_CMP` lint.
|
||||
pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
|
||||
if chars_cmp::check(cx, info, &["chars", "last"], CHARS_LAST_CMP, "ends_with") {
|
||||
if chars_cmp::check(cx, info, &[sym::chars, sym::last], CHARS_LAST_CMP, "ends_with") {
|
||||
true
|
||||
} else {
|
||||
chars_cmp::check(cx, info, &["chars", "next_back"], CHARS_LAST_CMP, "ends_with")
|
||||
chars_cmp::check(cx, info, &[sym::chars, sym::next_back], CHARS_LAST_CMP, "ends_with")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,26 @@
|
|||
use crate::methods::chars_cmp_with_unwrap;
|
||||
use clippy_utils::sym;
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::CHARS_LAST_CMP;
|
||||
|
||||
/// Checks for the `CHARS_LAST_CMP` lint with `unwrap()`.
|
||||
pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
|
||||
if chars_cmp_with_unwrap::check(cx, info, &["chars", "last", "unwrap"], CHARS_LAST_CMP, "ends_with") {
|
||||
if chars_cmp_with_unwrap::check(
|
||||
cx,
|
||||
info,
|
||||
&[sym::chars, sym::last, sym::unwrap],
|
||||
CHARS_LAST_CMP,
|
||||
"ends_with",
|
||||
) {
|
||||
true
|
||||
} else {
|
||||
chars_cmp_with_unwrap::check(cx, info, &["chars", "next_back", "unwrap"], CHARS_LAST_CMP, "ends_with")
|
||||
chars_cmp_with_unwrap::check(
|
||||
cx,
|
||||
info,
|
||||
&[sym::chars, sym::next_back, sym::unwrap],
|
||||
CHARS_LAST_CMP,
|
||||
"ends_with",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use clippy_utils::sym;
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::CHARS_NEXT_CMP;
|
||||
|
||||
/// Checks for the `CHARS_NEXT_CMP` lint.
|
||||
pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
|
||||
crate::methods::chars_cmp::check(cx, info, &["chars", "next"], CHARS_NEXT_CMP, "starts_with")
|
||||
crate::methods::chars_cmp::check(cx, info, &[sym::chars, sym::next], CHARS_NEXT_CMP, "starts_with")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
use clippy_utils::sym;
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::CHARS_NEXT_CMP;
|
||||
|
||||
/// Checks for the `CHARS_NEXT_CMP` lint with `unwrap()`.
|
||||
pub(super) fn check(cx: &LateContext<'_>, info: &crate::methods::BinaryExprInfo<'_>) -> bool {
|
||||
crate::methods::chars_cmp_with_unwrap::check(cx, info, &["chars", "next", "unwrap"], CHARS_NEXT_CMP, "starts_with")
|
||||
crate::methods::chars_cmp_with_unwrap::check(
|
||||
cx,
|
||||
info,
|
||||
&[sym::chars, sym::next, sym::unwrap],
|
||||
CHARS_NEXT_CMP,
|
||||
"starts_with",
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||
use clippy_utils::{eq_expr_value, get_parent_expr};
|
||||
use clippy_utils::{eq_expr_value, get_parent_expr, sym};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
|
|
@ -22,7 +22,7 @@ pub(super) fn check<'tcx>(
|
|||
// If the parent node's `to` argument is the same as the `to` argument
|
||||
// of the last replace call in the current chain, don't lint as it was already linted
|
||||
if let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let Some(("replace", _, [current_from, current_to], _, _)) = method_call(parent)
|
||||
&& let Some((sym::replace, _, [current_from, current_to], _, _)) = method_call(parent)
|
||||
&& eq_expr_value(cx, to, current_to)
|
||||
&& from_kind == cx.typeck_results().expr_ty(current_from).peel_refs().kind()
|
||||
{
|
||||
|
|
@ -47,7 +47,7 @@ fn collect_replace_calls<'tcx>(
|
|||
let mut from_args = VecDeque::new();
|
||||
|
||||
let _: Option<()> = for_each_expr_without_closures(expr, |e| {
|
||||
if let Some(("replace", _, [from, to], _, _)) = method_call(e) {
|
||||
if let Some((sym::replace, _, [from, to], _, _)) = method_call(e) {
|
||||
if eq_expr_value(cx, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() {
|
||||
methods.push_front(e);
|
||||
from_args.push_front(from);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ use rustc_errors::Applicability;
|
|||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::{Span, Symbol};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::EXPECT_FUN_CALL;
|
||||
|
|
@ -19,7 +19,7 @@ pub(super) fn check<'tcx>(
|
|||
format_args_storage: &FormatArgsStorage,
|
||||
expr: &hir::Expr<'_>,
|
||||
method_span: Span,
|
||||
name: &str,
|
||||
name: Symbol,
|
||||
receiver: &'tcx hir::Expr<'tcx>,
|
||||
args: &'tcx [hir::Expr<'tcx>],
|
||||
) {
|
||||
|
|
@ -114,7 +114,7 @@ pub(super) fn check<'tcx>(
|
|||
}
|
||||
}
|
||||
|
||||
if args.len() != 1 || name != "expect" || !is_call(&args[0].kind) {
|
||||
if args.len() != 1 || name != sym::expect || !is_call(&args[0].kind) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sym;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, LangItem};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
use super::EXTEND_WITH_DRAIN;
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg:
|
|||
if is_type_diagnostic_item(cx, ty, sym::Vec)
|
||||
//check source object
|
||||
&& let ExprKind::MethodCall(src_method, drain_vec, [drain_arg], _) = &arg.kind
|
||||
&& src_method.ident.as_str() == "drain"
|
||||
&& src_method.ident.name == sym::drain
|
||||
&& let src_ty = cx.typeck_results().expr_ty(drain_vec)
|
||||
//check if actual src type is mutable for code suggestion
|
||||
&& let immutable = src_ty.is_mutable_ptr()
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{is_diag_item_method, is_diag_trait_item, peel_middle_ty_refs};
|
||||
use clippy_utils::{is_diag_item_method, is_diag_trait_item, peel_middle_ty_refs, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::Symbol;
|
||||
|
||||
use super::IMPLICIT_CLONE;
|
||||
|
||||
pub fn check(cx: &LateContext<'_>, method_name: &str, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) {
|
||||
pub fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) {
|
||||
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& is_clone_like(cx, method_name, method_def_id)
|
||||
&& let return_type = cx.typeck_results().expr_ty(expr)
|
||||
|
|
@ -43,12 +43,12 @@ pub fn check(cx: &LateContext<'_>, method_name: &str, expr: &hir::Expr<'_>, recv
|
|||
/// Note that `to_string` is not flagged by `implicit_clone`. So other lints that call
|
||||
/// `is_clone_like` and that do flag `to_string` must handle it separately. See, e.g.,
|
||||
/// `is_to_owned_like` in `unnecessary_to_owned.rs`.
|
||||
pub fn is_clone_like(cx: &LateContext<'_>, method_name: &str, method_def_id: hir::def_id::DefId) -> bool {
|
||||
pub fn is_clone_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: hir::def_id::DefId) -> bool {
|
||||
match method_name {
|
||||
"to_os_string" => is_diag_item_method(cx, method_def_id, sym::OsStr),
|
||||
"to_owned" => is_diag_trait_item(cx, method_def_id, sym::ToOwned),
|
||||
"to_path_buf" => is_diag_item_method(cx, method_def_id, sym::Path),
|
||||
"to_vec" => cx
|
||||
sym::to_os_string => is_diag_item_method(cx, method_def_id, sym::OsStr),
|
||||
sym::to_owned => is_diag_trait_item(cx, method_def_id, sym::ToOwned),
|
||||
sym::to_path_buf => is_diag_item_method(cx, method_def_id, sym::Path),
|
||||
sym::to_vec => cx
|
||||
.tcx
|
||||
.impl_of_method(method_def_id)
|
||||
.filter(|&impl_did| {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue