Auto merge of #141343 - flip1995:clippy-subtree-update, r=Manishearth

Clippy subtree update

Out of cycle sync to fix an ICE that was reported twice already: https://github.com/rust-lang/rust-clippy/issues/14828

r? `@Manishearth`
This commit is contained in:
bors 2025-05-21 19:47:01 +00:00
commit bf64d66bd5
279 changed files with 6312 additions and 4766 deletions

View file

@ -582,13 +582,11 @@ dependencies = [
name = "clippy_dev"
version = "0.0.1"
dependencies = [
"aho-corasick",
"chrono",
"clap",
"indoc",
"itertools",
"opener",
"shell-escape",
"walkdir",
]
@ -4888,12 +4886,6 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "shell-escape"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
[[package]]
name = "shlex"
version = "1.3.0"

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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.

View file

@ -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.

View file

@ -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)

View file

@ -1,8 +1,6 @@
[package]
name = "clippy_config"
# begin autogenerated version
version = "0.1.89"
# end autogenerated version
edition = "2024"
publish = false

View file

@ -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,

View file

@ -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]

View file

@ -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");

View file

@ -1,19 +1,18 @@
use crate::utils::{
ClippyInfo, ErrAction, FileUpdater, UpdateMode, UpdateStatus, panic_action, run_with_args_split, run_with_output,
};
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,153 @@ 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(clippy: &ClippyInfo, 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 mut cargo_path = String::from_utf8(run_with_output(
"rustup which cargo",
Command::new("rustup").args(["which", "cargo"]),
))
.expect("invalid cargo path");
cargo_path.truncate(cargo_path.trim_end().len());
// Start all format jobs first before waiting on the results.
let mut children = Vec::with_capacity(16);
for &path in &[
".",
"clippy_config",
"clippy_dev",
"clippy_lints",
"clippy_lints_internal",
"clippy_utils",
"rustc_tools_util",
"lintcheck",
] {
let mut cmd = Command::new(&cargo_path);
cmd.current_dir(clippy.path.join(path))
.args(["fmt"])
.env("RUSTFMT", &rustfmt_path)
.stdout(Stdio::null())
.stdin(Stdio::null())
.stderr(Stdio::piped());
if update_mode.is_check() {
cmd.arg("--check");
}
match cmd.spawn() {
Ok(x) => children.push(("cargo fmt", x)),
Err(ref e) => panic_action(&e, ErrAction::Run, "cargo fmt".as_ref()),
}
}
run_with_args_split(
|| {
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(["--config", "show_parse_errors=false"]);
cmd
},
|cmd| match cmd.spawn() {
Ok(x) => children.push(("rustfmt", x)),
Err(ref e) => panic_action(&e, ErrAction::Run, "rustfmt".as_ref()),
},
WalkDir::new("tests")
.into_iter()
.filter_entry(|p| p.path().file_name().is_none_or(|x| x != "skip_rustfmt"))
.filter_map(|e| {
let e = e.expect("error reading `tests`");
e.path()
.as_os_str()
.as_encoded_bytes()
.ends_with(b".rs")
.then(|| e.into_path().into_os_string())
}),
);
for (name, child) in &mut children {
match child.wait() {
Ok(status) => 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, Err(e)) => {
let mut s = String::new();
if let Some(mut stderr) = child.stderr.take()
&& stderr.read_to_string(&mut s).is_ok()
{
eprintln!("{s}");
}
panic_action(&e, ErrAction::Run, name.as_ref());
},
},
Err(ref e) => panic_action(e, ErrAction::Run, name.as_ref()),
}
}
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);
pub fn run(clippy: &ClippyInfo, update_mode: UpdateMode) {
if clippy.has_intellij_hook {
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`."
);
return;
}
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)) {
run_rustfmt(clippy, 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)
}

View file

@ -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,

View file

@ -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(&clippy, 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

View file

@ -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))
}
});
}
}

View file

@ -1,9 +1,11 @@
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::{
FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists,
try_rename_dir, try_rename_file,
};
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;
@ -22,7 +24,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 +35,369 @@ 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 file in WalkDir::new(".").into_iter().filter_entry(|e| {
// Skip traversing some of the larger directories.
e.path()
.as_os_str()
.as_encoded_bytes()
.get(2..)
.is_none_or(|x| x != "target".as_bytes() && x != ".git".as_bytes())
}) {
let file = file.expect("error reading clippy directory");
if file.path().as_os_str().as_encoded_bytes().ends_with(b".rs") {
updater.update_file(file.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)
}
}

View file

@ -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);
}

View file

@ -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, panic_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,
@ -172,7 +203,7 @@ fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator<Item = (DirE
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),
Err(ref e) => panic_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")
@ -202,17 +233,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 +255,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 +299,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 +386,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 +396,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(),
},
];

View file

@ -1,13 +1,14 @@
use aho_corasick::{AhoCorasick, AhoCorasickBuilder};
use core::fmt::{self, Display};
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};
#[cfg(not(windows))]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
@ -15,14 +16,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,13 +33,15 @@ 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)
}
@ -52,7 +57,7 @@ impl<'a> File<'a> {
let path = path.as_ref();
match options.open(path) {
Ok(inner) => Self { inner, path },
Err(e) => panic_file(&e, FileAction::Open, path),
Err(e) => panic_action(&e, ErrAction::Open, path),
}
}
@ -63,7 +68,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),
}
}
@ -81,7 +86,7 @@ impl<'a> File<'a> {
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),
Err(e) => panic_action(&e, ErrAction::Read, self.path),
}
dst
}
@ -103,7 +108,7 @@ impl<'a> File<'a> {
Err(e) => Err(e),
};
if let Err(e) = res {
panic_file(&e, FileAction::Write, self.path);
panic_action(&e, ErrAction::Write, self.path);
}
}
}
@ -165,9 +170,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 +256,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 +323,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 +436,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 +459,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 +487,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 +515,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 +537,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 +579,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 +591,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 +605,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 +616,107 @@ 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));
fs::write(path, contents).unwrap_or_else(|e| panic_action(&e, 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> {
match cmd
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.output()
{
Ok(x) => match x.status.exit_ok() {
Ok(()) => x.stdout,
Err(ref e) => panic_action(e, ErrAction::Run, path),
},
Err(ref e) => panic_action(e, ErrAction::Run, path),
}
}
f(path.as_ref(), cmd)
}
pub fn run_with_args_split(
mut make_cmd: impl FnMut() -> Command,
mut run_cmd: impl FnMut(&mut Command),
args: impl Iterator<Item: AsRef<OsStr>>,
) {
let mut cmd = make_cmd();
let mut len = 0;
for arg in args {
len += arg.as_ref().len();
cmd.arg(arg);
// Very conservative limit
if len > 10000 {
run_cmd(&mut cmd);
cmd = make_cmd();
len = 0;
}
}
if len != 0 {
run_cmd(&mut cmd);
}
}
#[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),
}
}

View file

@ -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"

View file

@ -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,6 +262,15 @@ 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 => {
let mut cur_v: Option<&Variant<'_>> = None;

View file

@ -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,

View file

@ -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))
{

View file

@ -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;
}

View file

@ -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))
}

View file

@ -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)

View file

@ -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);
}
}

View file

@ -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(

View file

@ -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",
);
}
}

View file

@ -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,

View file

@ -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"),
]}

View file

@ -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) => {
@ -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<_>`
@ -944,7 +942,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 +966,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,

View file

@ -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),

View file

@ -93,6 +93,7 @@ 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
&& !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)

View file

@ -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

View file

@ -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())

View file

@ -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)

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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());

View file

@ -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 {

View file

@ -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;
@ -72,7 +72,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()

View file

@ -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;

View file

@ -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);

View file

@ -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);
}

View file

@ -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)));

View file

@ -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,

View file

@ -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
}

View file

@ -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"

View file

@ -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,

View file

@ -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,

View file

@ -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);
},
_ => {},

View file

@ -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`

View file

@ -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 };

View file

@ -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]);
}

View file

@ -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,
}
}

View file

@ -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);

View file

@ -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)) => {

View file

@ -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;

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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 {

View file

@ -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 {

View file

@ -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")
}
}

View file

@ -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",
)
}
}

View file

@ -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")
}

View file

@ -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",
)
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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()

View file

@ -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| {

View file

@ -5,11 +5,16 @@ use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::sym;
use rustc_span::{Symbol, sym};
use super::ITER_CLONED_COLLECT;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, method_name: &str, expr: &hir::Expr<'_>, recv: &'tcx hir::Expr<'_>) {
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
method_name: Symbol,
expr: &hir::Expr<'_>,
recv: &'tcx hir::Expr<'_>,
) {
let expr_ty = cx.typeck_results().expr_ty(expr);
if is_type_diagnostic_item(cx, expr_ty, sym::Vec)
&& let Some(slice) = derefs_to_slice(cx, recv, cx.typeck_results().expr_ty(recv))

View file

@ -5,11 +5,11 @@ use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_span::sym;
use rustc_span::{Symbol, sym};
use super::ITER_COUNT;
pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, iter_method: &str) {
pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, iter_method: Symbol) {
let ty = cx.typeck_results().expr_ty(recv);
let caller_type = if derefs_to_slice(cx, recv, ty).is_some() {
"slice"

View file

@ -1,12 +1,12 @@
use super::ITER_KV_MAP;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::pat_is_wild;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{pat_is_wild, sym};
use rustc_hir::{Body, Expr, ExprKind, PatKind};
use rustc_lint::LateContext;
use rustc_span::sym;
use rustc_span::Symbol;
/// lint use of:
///
@ -16,13 +16,13 @@ use rustc_span::sym;
/// on `HashMaps` and `BTreeMaps` in std
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
map_type: &'tcx str, // iter / into_iter
map_type: Symbol, // iter / into_iter
expr: &'tcx Expr<'tcx>, // .iter().map(|(_, v_| v))
recv: &'tcx Expr<'tcx>, // hashmap
m_arg: &'tcx Expr<'tcx>, // |(_, v)| v
msrv: Msrv,
) {
if map_type == "into_iter" && !msrv.meets(cx, msrvs::INTO_KEYS) {
if map_type == sym::into_iter && !msrv.meets(cx, msrvs::INTO_KEYS) {
return;
}
if !expr.span.from_expansion()
@ -42,7 +42,7 @@ pub(super) fn check<'tcx>(
{
let mut applicability = rustc_errors::Applicability::MachineApplicable;
let recv_snippet = snippet_with_applicability(cx, recv.span, "map", &mut applicability);
let into_prefix = if map_type == "into_iter" { "into_" } else { "" };
let into_prefix = if map_type == sym::into_iter { "into_" } else { "" };
if let ExprKind::Path(rustc_hir::QPath::Resolved(_, path)) = body_expr.kind
&& let [local_ident] = path.segments

View file

@ -1,10 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sym;
use clippy_utils::ty::get_type_diagnostic_name;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::Span;
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol};
use super::ITER_NTH;
@ -12,7 +12,7 @@ pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &hir::Expr<'_>,
iter_recv: &'tcx hir::Expr<'tcx>,
iter_method: &str,
iter_method: Symbol,
iter_span: Span,
nth_span: Span,
) -> bool {
@ -30,7 +30,7 @@ pub(super) fn check<'tcx>(
expr.span,
format!("called `.{iter_method}().nth()` on a {caller_type}"),
|diag| {
let get_method = if iter_method == "iter_mut" { "get_mut" } else { "get" };
let get_method = if iter_method == sym::iter_mut { "get_mut" } else { "get" };
diag.span_suggestion_verbose(
iter_span.to(nth_span),
format!("`{get_method}` is equivalent but more concise"),

View file

@ -2,7 +2,7 @@ use std::iter::once;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::{get_expr_use_or_unification_node, is_res_lang_ctor, path_res, std_or_core};
use clippy_utils::{get_expr_use_or_unification_node, is_res_lang_ctor, path_res, std_or_core, sym};
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
@ -10,6 +10,7 @@ use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::HirId;
use rustc_hir::{Expr, ExprKind, Node};
use rustc_lint::LateContext;
use rustc_span::Symbol;
use super::{ITER_ON_EMPTY_COLLECTIONS, ITER_ON_SINGLE_ITEMS};
@ -51,7 +52,7 @@ fn is_arg_ty_unified_in_fn<'tcx>(
.any(|(i, ty)| i != arg_id_in_args && ty.skip_binder().walk().any(|arg| arg.as_type() == Some(arg_ty_in_args)))
}
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: &str, recv: &'tcx Expr<'tcx>) {
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, recv: &'tcx Expr<'tcx>) {
let item = match recv.kind {
ExprKind::Array([]) => None,
ExprKind::Array([e]) => Some(e),
@ -60,9 +61,9 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method
_ => return,
};
let iter_type = match method_name {
"iter" => IterType::Iter,
"iter_mut" => IterType::IterMut,
"into_iter" => IterType::IntoIter,
sym::iter => IterType::Iter,
sym::iter_mut => IterType::IterMut,
sym::into_iter => IterType::IntoIter,
_ => return,
};

View file

@ -8,7 +8,7 @@ use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, Pl
use rustc_lint::LateContext;
use rustc_middle::mir::{FakeReadCause, Mutability};
use rustc_middle::ty::{self, BorrowKind};
use rustc_span::sym;
use rustc_span::{Symbol, sym};
use super::ITER_OVEREAGER_CLONED;
use crate::redundant_clone::REDUNDANT_CLONE;
@ -26,7 +26,7 @@ pub(super) enum Op<'a> {
// later `.cloned()`
// and add `&` to the parameter of closure parameter
// e.g. `find` `filter`
FixClosure(&'a str, &'a Expr<'a>),
FixClosure(Symbol, &'a Expr<'a>),
// later `.cloned()`
// e.g. `skip` `take`

View file

@ -168,7 +168,7 @@ fn rewrite_as_cstr(cx: &LateContext<'_>, span: Span) -> Option<String> {
fn get_cast_target<'tcx>(e: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
match &e.kind {
ExprKind::MethodCall(method, receiver, [], _) if method.ident.as_str() == "cast" => Some(receiver),
ExprKind::MethodCall(method, receiver, [], _) if method.ident.name == sym::cast => Some(receiver),
ExprKind::Cast(expr, _) => Some(expr),
_ => None,
}

View file

@ -3,18 +3,18 @@ use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{IntoSpan, SpanRangeExt};
use clippy_utils::ty::get_field_by_name;
use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures};
use clippy_utils::{ExprUseNode, expr_use_ctxt, is_diag_item_method, is_diag_trait_item, path_to_local_id};
use clippy_utils::{ExprUseNode, expr_use_ctxt, is_diag_item_method, is_diag_trait_item, path_to_local_id, sym};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::{BindingMode, BorrowKind, ByRef, ClosureKind, Expr, ExprKind, Mutability, Node, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
use rustc_span::{DUMMY_SP, Span, Symbol, sym};
use rustc_span::{DUMMY_SP, Span, Symbol};
use super::MANUAL_INSPECT;
#[expect(clippy::too_many_lines)]
pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: &str, name_span: Span, msrv: Msrv) {
pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: Symbol, name_span: Span, msrv: Msrv) {
if let ExprKind::Closure(c) = arg.kind
&& matches!(c.kind, ClosureKind::Closure)
&& let typeck = cx.typeck_results()
@ -101,7 +101,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
UseKind::Return(s) => edits.push((s.with_leading_whitespace(cx).with_ctxt(s.ctxt()), String::new())),
UseKind::Borrowed(s) => {
#[expect(clippy::range_plus_one)]
let range = s.map_range(cx, |src, range| {
let range = s.map_range(cx, |_, src, range| {
let src = src.get(range.clone())?;
let trimmed = src.trim_start_matches([' ', '\t', '\n', '\r', '(']);
trimmed.starts_with('&').then(|| {
@ -168,8 +168,8 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
edits.extend(addr_of_edits);
}
let edit = match name {
"map" => "inspect",
"map_err" => "inspect_err",
sym::map => "inspect",
sym::map_err => "inspect_err",
_ => return,
};
edits.push((name_span, edit.to_string()));

View file

@ -5,7 +5,7 @@ use rustc_ast::BindingMode;
use rustc_errors::Applicability;
use rustc_hir::{self as hir, Node, PatKind};
use rustc_lint::LateContext;
use rustc_span::{Span, sym};
use rustc_span::{Span, Symbol, sym};
use super::MAP_IDENTITY;
@ -14,7 +14,7 @@ pub(super) fn check(
expr: &hir::Expr<'_>,
caller: &hir::Expr<'_>,
map_arg: &hir::Expr<'_>,
name: &str,
name: Symbol,
_map_span: Span,
) {
let caller_ty = cx.typeck_results().expr_ty(caller);

View file

@ -150,7 +150,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::macros::FormatArgsStorage;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item};
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty};
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty, sym};
pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES;
use rustc_abi::ExternAbi;
use rustc_data_structures::fx::FxHashSet;
@ -159,7 +159,7 @@ use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::{self, TraitRef, Ty};
use rustc_session::impl_lint_pass;
use rustc_span::{Span, sym};
use rustc_span::{Span, Symbol, kw};
declare_clippy_lint! {
/// ### What it does
@ -4711,17 +4711,15 @@ impl_lint_pass!(Methods => [
/// Extracts a method call name, args, and `Span` of the method name.
/// This ensures that neither the receiver nor any of the arguments
/// come from expansion.
pub fn method_call<'tcx>(
recv: &'tcx Expr<'tcx>,
) -> Option<(&'tcx str, &'tcx Expr<'tcx>, &'tcx [Expr<'tcx>], Span, Span)> {
pub fn method_call<'tcx>(recv: &'tcx Expr<'tcx>) -> Option<(Symbol, &'tcx Expr<'tcx>, &'tcx [Expr<'tcx>], Span, Span)> {
if let ExprKind::MethodCall(path, receiver, args, call_span) = recv.kind
&& !args.iter().any(|e| e.span.from_expansion())
&& !receiver.span.from_expansion()
{
let name = path.ident.name.as_str();
return Some((name, receiver, args, path.ident.span, call_span));
Some((path.ident.name, receiver, args, path.ident.span, call_span))
} else {
None
}
None
}
impl<'tcx> LateLintPass<'tcx> for Methods {
@ -4743,13 +4741,13 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
},
ExprKind::MethodCall(method_call, receiver, args, _) => {
let method_span = method_call.ident.span;
or_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), receiver, args);
or_fun_call::check(cx, expr, method_span, method_call.ident.name, receiver, args);
expect_fun_call::check(
cx,
&self.format_args,
expr,
method_span,
method_call.ident.as_str(),
method_call.ident.name,
receiver,
args,
);
@ -4778,7 +4776,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
if impl_item.span.in_external_macro(cx.sess().source_map()) {
return;
}
let name = impl_item.ident.name.as_str();
let name = impl_item.ident.name;
let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id;
let item = cx.tcx.hir_expect_item(parent);
let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
@ -4851,7 +4849,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
return;
}
if name == "new" && ret_ty != self_ty {
if name == sym::new && ret_ty != self_ty {
span_lint(
cx,
NEW_RET_NO_SELF,
@ -4881,7 +4879,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty();
wrong_self_convention::check(
cx,
item.ident.name.as_str(),
item.ident.name,
self_ty,
first_arg_ty,
first_arg_hir_ty.span,
@ -4912,14 +4910,17 @@ impl Methods {
// Handle method calls whose receiver and arguments may not come from expansion
if let Some((name, recv, args, span, call_span)) = method_call(expr) {
match (name, args) {
("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => {
(
sym::add | sym::offset | sym::sub | sym::wrapping_offset | sym::wrapping_add | sym::wrapping_sub,
[_arg],
) => {
zst_offset::check(cx, expr, recv);
},
("all", [arg]) => {
(sym::all, [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
needless_character_iteration::check(cx, expr, recv, arg, true);
match method_call(recv) {
Some(("cloned", recv2, [], _, _)) => {
Some((sym::cloned, recv2, [], _, _)) => {
iter_overeager_cloned::check(
cx,
expr,
@ -4929,13 +4930,13 @@ impl Methods {
false,
);
},
Some(("map", _, [map_arg], _, map_call_span)) => {
Some((sym::map, _, [map_arg], _, map_call_span)) => {
map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "all");
},
_ => {},
}
},
("and_then", [arg]) => {
(sym::and_then, [arg]) => {
let biom_option_linted = bind_instead_of_map::check_and_then_some(cx, expr, recv, arg);
let biom_result_linted = bind_instead_of_map::check_and_then_ok(cx, expr, recv, arg);
if !biom_option_linted && !biom_result_linted {
@ -4945,11 +4946,11 @@ impl Methods {
}
}
},
("any", [arg]) => {
(sym::any, [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
needless_character_iteration::check(cx, expr, recv, arg, false);
match method_call(recv) {
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
expr,
recv,
@ -4957,80 +4958,79 @@ impl Methods {
iter_overeager_cloned::Op::NeedlessMove(arg),
false,
),
Some(("chars", recv, _, _, _))
Some((sym::chars, recv, _, _, _))
if let ExprKind::Closure(arg) = arg.kind
&& let body = cx.tcx.hir_body(arg.body)
&& let [param] = body.params =>
{
string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), self.msrv);
},
Some(("map", _, [map_arg], _, map_call_span)) => {
Some((sym::map, _, [map_arg], _, map_call_span)) => {
map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "any");
},
Some(("iter", iter_recv, ..)) => {
Some((sym::iter, iter_recv, ..)) => {
manual_contains::check(cx, expr, iter_recv, arg);
},
_ => {},
}
},
("arg", [arg]) => {
(sym::arg, [arg]) => {
suspicious_command_arg_space::check(cx, recv, arg, span);
},
("as_deref" | "as_deref_mut", []) => {
(sym::as_deref | sym::as_deref_mut, []) => {
needless_option_as_deref::check(cx, expr, recv, name);
},
("as_bytes", []) => {
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
(sym::as_bytes, []) => {
if let Some((sym::as_str, recv, [], as_str_span, _)) = method_call(recv) {
redundant_as_str::check(cx, expr, recv, as_str_span, span);
}
sliced_string_as_bytes::check(cx, expr, recv);
},
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
("as_ptr", []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, self.msrv),
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
("bytes", []) => unbuffered_bytes::check(cx, expr, recv),
("cloned", []) => {
(sym::as_mut | sym::as_ref, []) => useless_asref::check(cx, expr, name, recv),
(sym::as_ptr, []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, self.msrv),
(sym::assume_init, []) => uninit_assumed_init::check(cx, expr, recv),
(sym::bytes, []) => unbuffered_bytes::check(cx, expr, recv),
(sym::cloned, []) => {
cloned_instead_of_copied::check(cx, expr, recv, span, self.msrv);
option_as_ref_cloned::check(cx, recv, span);
},
("collect", []) if is_trait_method(cx, expr, sym::Iterator) => {
(sym::collect, []) if is_trait_method(cx, expr, sym::Iterator) => {
needless_collect::check(cx, span, expr, recv, call_span);
match method_call(recv) {
Some((name @ ("cloned" | "copied"), recv2, [], _, _)) => {
Some((name @ (sym::cloned | sym::copied), recv2, [], _, _)) => {
iter_cloned_collect::check(cx, name, expr, recv2);
},
Some(("map", m_recv, [m_arg], m_ident_span, _)) => {
Some((sym::map, m_recv, [m_arg], m_ident_span, _)) => {
map_collect_result_unit::check(cx, expr, m_recv, m_arg);
format_collect::check(cx, expr, m_arg, m_ident_span);
},
Some(("take", take_self_arg, [take_arg], _, _)) => {
Some((sym::take, take_self_arg, [take_arg], _, _)) => {
if self.msrv.meets(cx, msrvs::STR_REPEAT) {
manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
}
},
Some(("drain", recv, args, ..)) => {
Some((sym::drain, recv, args, ..)) => {
drain_collect::check(cx, args, expr, recv);
},
_ => {},
}
},
("count", []) if is_trait_method(cx, expr, sym::Iterator) => match method_call(recv) {
Some(("cloned", recv2, [], _, _)) => {
(sym::count, []) if is_trait_method(cx, expr, sym::Iterator) => match method_call(recv) {
Some((sym::cloned, recv2, [], _, _)) => {
iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::RmCloned, false);
},
Some((name2 @ ("into_iter" | "iter" | "iter_mut"), recv2, [], _, _)) => {
Some((name2 @ (sym::into_iter | sym::iter | sym::iter_mut), recv2, [], _, _)) => {
iter_count::check(cx, expr, recv2, name2);
},
Some(("map", _, [arg], _, _)) => suspicious_map::check(cx, expr, recv, arg),
Some(("filter", recv2, [arg], _, _)) => bytecount::check(cx, expr, recv2, arg),
Some(("bytes", recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2),
Some((sym::map, _, [arg], _, _)) => suspicious_map::check(cx, expr, recv, arg),
Some((sym::filter, recv2, [arg], _, _)) => bytecount::check(cx, expr, recv2, arg),
Some((sym::bytes, recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2),
_ => {},
},
("min" | "max", [arg]) => {
(sym::min | sym::max, [arg]) => {
unnecessary_min_or_max::check(cx, expr, name, recv, arg);
},
("drain", ..) => {
(sym::drain, ..) => {
if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.parent_hir_node(expr.hir_id)
&& matches!(kind, StmtKind::Semi(_))
&& args.len() <= 1
@ -5040,31 +5040,31 @@ impl Methods {
iter_with_drain::check(cx, expr, recv, span, arg);
}
},
("ends_with", [arg]) => {
(sym::ends_with, [arg]) => {
if let ExprKind::MethodCall(.., span) = expr.kind {
case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg, self.msrv);
}
path_ends_with_ext::check(cx, recv, arg, expr, self.msrv, &self.allowed_dotfiles);
},
("expect", [_]) => {
(sym::expect, [_]) => {
match method_call(recv) {
Some(("ok", recv, [], _, _)) => ok_expect::check(cx, expr, recv),
Some(("err", recv, [], err_span, _)) => {
Some((sym::ok, recv, [], _, _)) => ok_expect::check(cx, expr, recv),
Some((sym::err, recv, [], err_span, _)) => {
err_expect::check(cx, expr, recv, span, err_span, self.msrv);
},
_ => {},
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("expect_err", [_]) | ("unwrap_err" | "unwrap_unchecked" | "unwrap_err_unchecked", []) => {
(sym::expect_err, [_]) | (sym::unwrap_err | sym::unwrap_unchecked | sym::unwrap_err_unchecked, []) => {
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("extend", [arg]) => {
(sym::extend, [arg]) => {
string_extend_chars::check(cx, expr, recv, arg);
extend_with_drain::check(cx, expr, recv, arg);
},
("filter", [arg]) => {
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
(sym::filter, [arg]) => {
if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) {
// if `arg` has side-effect, the semantic will change
iter_overeager_cloned::check(
cx,
@ -5080,8 +5080,8 @@ impl Methods {
iter_filter::check(cx, expr, arg, span);
}
},
("find", [arg]) => {
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
(sym::find, [arg]) => {
if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) {
// if `arg` has side-effect, the semantic will change
iter_overeager_cloned::check(
cx,
@ -5093,26 +5093,26 @@ impl Methods {
);
}
},
("filter_map", [arg]) => {
(sym::filter_map, [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
unnecessary_filter_map::check(cx, expr, arg, name);
filter_map_bool_then::check(cx, expr, arg, call_span);
filter_map_identity::check(cx, expr, arg, span);
},
("find_map", [arg]) => {
(sym::find_map, [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
unnecessary_filter_map::check(cx, expr, arg, name);
},
("flat_map", [arg]) => {
(sym::flat_map, [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
flat_map_identity::check(cx, expr, arg, span);
flat_map_option::check(cx, expr, arg, span);
},
("flatten", []) => match method_call(recv) {
Some(("map", recv, [map_arg], map_span, _)) => {
(sym::flatten, []) => match method_call(recv) {
Some((sym::map, recv, [map_arg], map_span, _)) => {
map_flatten::check(cx, expr, recv, map_arg, map_span);
},
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
expr,
recv,
@ -5122,15 +5122,15 @@ impl Methods {
),
_ => {},
},
("fold", [init, acc]) => {
(sym::fold, [init, acc]) => {
manual_try_fold::check(cx, expr, init, acc, call_span, self.msrv);
unnecessary_fold::check(cx, expr, init, acc, span);
},
("for_each", [arg]) => {
(sym::for_each, [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
match method_call(recv) {
Some(("inspect", _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2),
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
Some((sym::inspect, _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2),
Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
expr,
recv,
@ -5141,44 +5141,44 @@ impl Methods {
_ => {},
}
},
("get", [arg]) => {
(sym::get, [arg]) => {
get_first::check(cx, expr, recv, arg);
get_last_with_len::check(cx, expr, recv, arg);
},
("get_or_insert_with", [arg]) => {
(sym::get_or_insert_with, [arg]) => {
unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert");
},
("hash", [arg]) => {
(sym::hash, [arg]) => {
unit_hash::check(cx, expr, recv, arg);
},
("is_empty", []) => {
(sym::is_empty, []) => {
match method_call(recv) {
Some((prev_method @ ("as_bytes" | "bytes"), prev_recv, [], _, _)) => {
needless_as_bytes::check(cx, prev_method, "is_empty", prev_recv, expr.span);
Some((prev_method @ (sym::as_bytes | sym::bytes), prev_recv, [], _, _)) => {
needless_as_bytes::check(cx, prev_method, name, prev_recv, expr.span);
},
Some(("as_str", recv, [], as_str_span, _)) => {
Some((sym::as_str, recv, [], as_str_span, _)) => {
redundant_as_str::check(cx, expr, recv, as_str_span, span);
},
_ => {},
}
is_empty::check(cx, expr, recv);
},
("is_file", []) => filetype_is_file::check(cx, expr, recv),
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv),
("is_none", []) => check_is_some_is_none(cx, expr, recv, call_span, false),
("is_some", []) => check_is_some_is_none(cx, expr, recv, call_span, true),
("iter" | "iter_mut" | "into_iter", []) => {
(sym::is_file, []) => filetype_is_file::check(cx, expr, recv),
(sym::is_digit, [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv),
(sym::is_none, []) => check_is_some_is_none(cx, expr, recv, call_span, false),
(sym::is_some, []) => check_is_some_is_none(cx, expr, recv, call_span, true),
(sym::iter | sym::iter_mut | sym::into_iter, []) => {
iter_on_single_or_empty_collections::check(cx, expr, name, recv);
},
("join", [join_arg]) => {
if let Some(("collect", _, _, span, _)) = method_call(recv) {
(sym::join, [join_arg]) => {
if let Some((sym::collect, _, _, span, _)) = method_call(recv) {
unnecessary_join::check(cx, expr, recv, join_arg, span);
} else {
join_absolute_paths::check(cx, recv, join_arg, expr.span);
}
},
("last", []) => {
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
(sym::last, []) => {
if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) {
iter_overeager_cloned::check(
cx,
expr,
@ -5190,24 +5190,24 @@ impl Methods {
}
double_ended_iterator_last::check(cx, expr, recv, call_span);
},
("len", []) => {
if let Some((prev_method @ ("as_bytes" | "bytes"), prev_recv, [], _, _)) = method_call(recv) {
needless_as_bytes::check(cx, prev_method, "len", prev_recv, expr.span);
(sym::len, []) => {
if let Some((prev_method @ (sym::as_bytes | sym::bytes), prev_recv, [], _, _)) = method_call(recv) {
needless_as_bytes::check(cx, prev_method, sym::len, prev_recv, expr.span);
}
},
("lock", []) => {
(sym::lock, []) => {
mut_mutex_lock::check(cx, expr, recv, span);
},
(name @ ("map" | "map_err"), [m_arg]) => {
if name == "map" {
(name @ (sym::map | sym::map_err), [m_arg]) => {
if name == sym::map {
unused_enumerate_index::check(cx, expr, recv, m_arg);
map_clone::check(cx, expr, recv, m_arg, self.msrv);
map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, self.msrv, span);
match method_call(recv) {
Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => {
Some((map_name @ (sym::iter | sym::into_iter), recv2, _, _, _)) => {
iter_kv_map::check(cx, map_name, expr, recv2, m_arg, self.msrv);
},
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
expr,
recv,
@ -5222,12 +5222,12 @@ impl Methods {
}
if let Some((name, recv2, args, span2, _)) = method_call(recv) {
match (name, args) {
("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, self.msrv),
("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, self.msrv),
("filter", [f_arg]) => {
(sym::as_mut, []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, self.msrv),
(sym::as_ref, []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, self.msrv),
(sym::filter, [f_arg]) => {
filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false);
},
("find", [f_arg]) => {
(sym::find, [f_arg]) => {
filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, true);
},
_ => {},
@ -5237,22 +5237,22 @@ impl Methods {
manual_inspect::check(cx, expr, m_arg, name, span, self.msrv);
crate::useless_conversion::check_function_application(cx, expr, recv, m_arg);
},
("map_break" | "map_continue", [m_arg]) => {
(sym::map_break | sym::map_continue, [m_arg]) => {
crate::useless_conversion::check_function_application(cx, expr, recv, m_arg);
},
("map_or", [def, map]) => {
(sym::map_or, [def, map]) => {
option_map_or_none::check(cx, expr, recv, def, map);
manual_ok_or::check(cx, expr, recv, def, map);
unnecessary_map_or::check(cx, expr, recv, def, map, span, self.msrv);
},
("map_or_else", [def, map]) => {
(sym::map_or_else, [def, map]) => {
result_map_or_else_none::check(cx, expr, recv, def, map);
unnecessary_result_map_or_else::check(cx, expr, recv, def, map);
},
("next", []) => {
(sym::next, []) => {
if let Some((name2, recv2, args2, _, _)) = method_call(recv) {
match (name2, args2) {
("cloned", []) => iter_overeager_cloned::check(
(sym::cloned, []) => iter_overeager_cloned::check(
cx,
expr,
recv,
@ -5260,19 +5260,19 @@ impl Methods {
iter_overeager_cloned::Op::LaterCloned,
false,
),
("filter", [arg]) => filter_next::check(cx, expr, recv2, arg),
("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv),
("iter", []) => iter_next_slice::check(cx, expr, recv2),
("skip", [arg]) => iter_skip_next::check(cx, expr, recv2, arg),
("skip_while", [_]) => skip_while_next::check(cx, expr),
("rev", []) => manual_next_back::check(cx, expr, recv, recv2),
(sym::filter, [arg]) => filter_next::check(cx, expr, recv2, arg),
(sym::filter_map, [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv),
(sym::iter, []) => iter_next_slice::check(cx, expr, recv2),
(sym::skip, [arg]) => iter_skip_next::check(cx, expr, recv2, arg),
(sym::skip_while, [_]) => skip_while_next::check(cx, expr),
(sym::rev, []) => manual_next_back::check(cx, expr, recv, recv2),
_ => {},
}
}
},
("nth", [n_arg]) => match method_call(recv) {
Some(("bytes", recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg),
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
(sym::nth, [n_arg]) => match method_call(recv) {
Some((sym::bytes, recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg),
Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
expr,
recv,
@ -5280,54 +5280,54 @@ impl Methods {
iter_overeager_cloned::Op::LaterCloned,
false,
),
Some((iter_method @ ("iter" | "iter_mut"), iter_recv, [], iter_span, _)) => {
Some((iter_method @ (sym::iter | sym::iter_mut), iter_recv, [], iter_span, _)) => {
if !iter_nth::check(cx, expr, iter_recv, iter_method, iter_span, span) {
iter_nth_zero::check(cx, expr, recv, n_arg);
}
},
_ => iter_nth_zero::check(cx, expr, recv, n_arg),
},
("ok_or_else", [arg]) => {
(sym::ok_or_else, [arg]) => {
unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or");
},
("open", [_]) => {
(sym::open, [_]) => {
open_options::check(cx, expr, recv);
},
("or_else", [arg]) => {
(sym::or_else, [arg]) => {
if !bind_instead_of_map::check_or_else_err(cx, expr, recv, arg) {
unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
}
},
("push", [arg]) => {
(sym::push, [arg]) => {
path_buf_push_overwrite::check(cx, expr, arg);
},
("read_to_end", [_]) => {
(sym::read_to_end, [_]) => {
verbose_file_reads::check(cx, expr, recv, verbose_file_reads::READ_TO_END_MSG);
},
("read_to_string", [_]) => {
(sym::read_to_string, [_]) => {
verbose_file_reads::check(cx, expr, recv, verbose_file_reads::READ_TO_STRING_MSG);
},
("read_line", [arg]) => {
(sym::read_line, [arg]) => {
read_line_without_trim::check(cx, expr, recv, arg);
},
("repeat", [arg]) => {
(sym::repeat, [arg]) => {
repeat_once::check(cx, expr, recv, arg);
},
(name @ ("replace" | "replacen"), [arg1, arg2] | [arg1, arg2, _]) => {
(name @ (sym::replace | sym::replacen), [arg1, arg2] | [arg1, arg2, _]) => {
no_effect_replace::check(cx, expr, arg1, arg2);
// Check for repeated `str::replace` calls to perform `collapsible_str_replace` lint
if self.msrv.meets(cx, msrvs::PATTERN_TRAIT_CHAR_ARRAY)
&& name == "replace"
&& let Some(("replace", ..)) = method_call(recv)
&& name == sym::replace
&& let Some((sym::replace, ..)) = method_call(recv)
{
collapsible_str_replace::check(cx, expr, arg1, arg2);
}
},
("resize", [count_arg, default_arg]) => {
(sym::resize, [count_arg, default_arg]) => {
vec_resize_to_zero::check(cx, expr, count_arg, default_arg, span);
},
("seek", [arg]) => {
(sym::seek, [arg]) => {
if self.msrv.meets(cx, msrvs::SEEK_FROM_CURRENT) {
seek_from_current::check(cx, expr, recv, arg);
}
@ -5335,11 +5335,11 @@ impl Methods {
seek_to_start_instead_of_rewind::check(cx, expr, recv, arg, span);
}
},
("skip", [arg]) => {
(sym::skip, [arg]) => {
iter_skip_zero::check(cx, expr, arg);
iter_out_of_bounds::check_skip(cx, expr, recv, arg);
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) {
iter_overeager_cloned::check(
cx,
expr,
@ -5350,34 +5350,34 @@ impl Methods {
);
}
},
("sort", []) => {
(sym::sort, []) => {
stable_sort_primitive::check(cx, expr, recv);
},
("sort_by", [arg]) => {
(sym::sort_by, [arg]) => {
unnecessary_sort_by::check(cx, expr, recv, arg, false);
},
("sort_unstable_by", [arg]) => {
(sym::sort_unstable_by, [arg]) => {
unnecessary_sort_by::check(cx, expr, recv, arg, true);
},
("split", [arg]) => {
(sym::split, [arg]) => {
str_split::check(cx, expr, recv, arg);
},
("splitn" | "rsplitn", [count_arg, pat_arg]) => {
(sym::splitn | sym::rsplitn, [count_arg, pat_arg]) => {
if let Some(Constant::Int(count)) = ConstEvalCtxt::new(cx).eval(count_arg) {
suspicious_splitn::check(cx, name, expr, recv, count);
str_splitn::check(cx, name, expr, recv, pat_arg, count, self.msrv);
}
},
("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
(sym::splitn_mut | sym::rsplitn_mut, [count_arg, _]) => {
if let Some(Constant::Int(count)) = ConstEvalCtxt::new(cx).eval(count_arg) {
suspicious_splitn::check(cx, name, expr, recv, count);
}
},
("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
("take", [arg]) => {
(sym::step_by, [arg]) => iterator_step_by_zero::check(cx, expr, arg),
(sym::take, [arg]) => {
iter_out_of_bounds::check_take(cx, expr, recv, arg);
manual_repeat_n::check(cx, expr, recv, arg, self.msrv);
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
if let Some((sym::cloned, recv2, [], _span2, _)) = method_call(recv) {
iter_overeager_cloned::check(
cx,
expr,
@ -5388,74 +5388,89 @@ impl Methods {
);
}
},
("take", []) => needless_option_take::check(cx, expr, recv),
("then", [arg]) => {
(sym::take, []) => needless_option_take::check(cx, expr, recv),
(sym::then, [arg]) => {
if !self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) {
return;
}
unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some");
},
("try_into", []) if is_trait_method(cx, expr, sym::TryInto) => {
(sym::try_into, []) if is_trait_method(cx, expr, sym::TryInto) => {
unnecessary_fallible_conversions::check_method(cx, expr);
},
("to_owned", []) => {
(sym::to_owned, []) => {
if !suspicious_to_owned::check(cx, expr, recv) {
implicit_clone::check(cx, name, expr, recv);
}
},
("to_os_string" | "to_path_buf" | "to_vec", []) => {
(sym::to_os_string | sym::to_path_buf | sym::to_vec, []) => {
implicit_clone::check(cx, name, expr, recv);
},
("type_id", []) => {
(sym::type_id, []) => {
type_id_on_box::check(cx, recv, expr.span);
},
("unwrap", []) => {
(sym::unwrap, []) => {
match method_call(recv) {
Some(("get", recv, [get_arg], _, _)) => {
Some((sym::get, recv, [get_arg], _, _)) => {
get_unwrap::check(cx, expr, recv, get_arg, false);
},
Some(("get_mut", recv, [get_arg], _, _)) => {
Some((sym::get_mut, recv, [get_arg], _, _)) => {
get_unwrap::check(cx, expr, recv, get_arg, true);
},
Some(("or", recv, [or_arg], or_span, _)) => {
Some((sym::or, recv, [or_arg], or_span, _)) => {
or_then_unwrap::check(cx, expr, recv, or_arg, or_span);
},
_ => {},
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("unwrap_or", [u_arg]) => {
(sym::unwrap_or, [u_arg]) => {
match method_call(recv) {
Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), lhs, [rhs], _, _)) => {
manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
Some((arith @ (sym::checked_add | sym::checked_sub | sym::checked_mul), lhs, [rhs], _, _)) => {
manual_saturating_arithmetic::check(
cx,
expr,
lhs,
rhs,
u_arg,
&arith.as_str()[const { "checked_".len() }..],
);
},
Some(("map", m_recv, [m_arg], span, _)) => {
Some((sym::map, m_recv, [m_arg], span, _)) => {
option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv);
},
Some((then_method @ ("then" | "then_some"), t_recv, [t_arg], _, _)) => {
obfuscated_if_else::check(cx, expr, t_recv, t_arg, Some(u_arg), then_method, "unwrap_or");
Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => {
obfuscated_if_else::check(cx, expr, t_recv, t_arg, Some(u_arg), then_method, name);
},
_ => {},
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("unwrap_or_default", []) => {
(sym::unwrap_or_default, []) => {
match method_call(recv) {
Some(("map", m_recv, [arg], span, _)) => {
Some((sym::map, m_recv, [arg], span, _)) => {
manual_is_variant_and::check(cx, expr, m_recv, arg, span, self.msrv);
},
Some((then_method @ ("then" | "then_some"), t_recv, [t_arg], _, _)) => {
obfuscated_if_else::check(cx, expr, t_recv, t_arg, None, then_method, "unwrap_or_default");
Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => {
obfuscated_if_else::check(
cx,
expr,
t_recv,
t_arg,
None,
then_method,
sym::unwrap_or_default,
);
},
_ => {},
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("unwrap_or_else", [u_arg]) => {
(sym::unwrap_or_else, [u_arg]) => {
match method_call(recv) {
Some(("map", recv, [map_arg], _, _))
Some((sym::map, recv, [map_arg], _, _))
if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {},
Some((then_method @ ("then" | "then_some"), t_recv, [t_arg], _, _)) => {
Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => {
obfuscated_if_else::check(
cx,
expr,
@ -5463,7 +5478,7 @@ impl Methods {
t_arg,
Some(u_arg),
then_method,
"unwrap_or_else",
sym::unwrap_or_else,
);
},
_ => {
@ -5472,13 +5487,13 @@ impl Methods {
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("wake", []) => {
(sym::wake, []) => {
waker_clone_wake::check(cx, expr, recv);
},
("write", []) => {
(sym::write, []) => {
readonly_write_lock::check(cx, expr, recv);
},
("zip", [arg]) => {
(sym::zip, [arg]) => {
if let ExprKind::MethodCall(name, iter_recv, [], _) = recv.kind
&& name.ident.name == sym::iter
{
@ -5490,8 +5505,8 @@ impl Methods {
}
// Handle method calls whose receiver and arguments may come from expansion
if let ExprKind::MethodCall(path, recv, args, _call_span) = expr.kind {
match (path.ident.name.as_str(), args) {
("expect", [_]) if !matches!(method_call(recv), Some(("ok" | "err", _, [], _, _))) => {
match (path.ident.name, args) {
(sym::expect, [_]) if !matches!(method_call(recv), Some((sym::ok | sym::err, _, [], _, _))) => {
unwrap_expect_used::check(
cx,
expr,
@ -5502,7 +5517,7 @@ impl Methods {
unwrap_expect_used::Variant::Expect,
);
},
("expect_err", [_]) => {
(sym::expect_err, [_]) => {
unwrap_expect_used::check(
cx,
expr,
@ -5513,7 +5528,7 @@ impl Methods {
unwrap_expect_used::Variant::Expect,
);
},
("unwrap", []) => {
(sym::unwrap, []) => {
unwrap_expect_used::check(
cx,
expr,
@ -5524,7 +5539,7 @@ impl Methods {
unwrap_expect_used::Variant::Unwrap,
);
},
("unwrap_err", []) => {
(sym::unwrap_err, []) => {
unwrap_expect_used::check(
cx,
expr,
@ -5543,13 +5558,13 @@ impl Methods {
fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, call_span: Span, is_some: bool) {
match method_call(recv) {
Some((name @ ("find" | "position" | "rposition"), f_recv, [arg], span, _)) => {
Some((name @ (sym::find | sym::position | sym::rposition), f_recv, [arg], span, _)) => {
search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span);
},
Some(("get", f_recv, [arg], _, _)) => {
Some((sym::get, f_recv, [arg], _, _)) => {
unnecessary_get_then_check::check(cx, call_span, recv, f_recv, arg, is_some);
},
Some(("first", f_recv, [], _, _)) => {
Some((sym::first, f_recv, [], _, _)) => {
unnecessary_first_then_check::check(cx, call_span, recv, f_recv, is_some);
},
_ => {},
@ -5593,7 +5608,7 @@ const FN_HEADER: hir::FnHeader = hir::FnHeader {
struct ShouldImplTraitCase {
trait_name: &'static str,
method_name: &'static str,
method_name: Symbol,
param_count: usize,
fn_header: hir::FnHeader,
// implicit self kind expected (none, self, &self, ...)
@ -5606,7 +5621,7 @@ struct ShouldImplTraitCase {
impl ShouldImplTraitCase {
const fn new(
trait_name: &'static str,
method_name: &'static str,
method_name: Symbol,
param_count: usize,
fn_header: hir::FnHeader,
self_kind: SelfKind,
@ -5639,36 +5654,36 @@ impl ShouldImplTraitCase {
#[rustfmt::skip]
const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
ShouldImplTraitCase::new("std::convert::AsRef", "as_ref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
ShouldImplTraitCase::new("std::ops::BitAnd", "bitand", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::BitOr", "bitor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::BitXor", "bitxor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::borrow::Borrow", "borrow", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
ShouldImplTraitCase::new("std::borrow::BorrowMut", "borrow_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
ShouldImplTraitCase::new("std::clone::Clone", "clone", 1, FN_HEADER, SelfKind::Ref, OutType::Any, true),
ShouldImplTraitCase::new("std::cmp::Ord", "cmp", 2, FN_HEADER, SelfKind::Ref, OutType::Any, true),
ShouldImplTraitCase::new("std::default::Default", "default", 0, FN_HEADER, SelfKind::No, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Deref", "deref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
ShouldImplTraitCase::new("std::ops::DerefMut", "deref_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
ShouldImplTraitCase::new("std::ops::Div", "div", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Drop", "drop", 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true),
ShouldImplTraitCase::new("std::cmp::PartialEq", "eq", 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true),
ShouldImplTraitCase::new("std::iter::FromIterator", "from_iter", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
ShouldImplTraitCase::new("std::str::FromStr", "from_str", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
ShouldImplTraitCase::new("std::hash::Hash", "hash", 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true),
ShouldImplTraitCase::new("std::ops::Index", "index", 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
ShouldImplTraitCase::new("std::ops::IndexMut", "index_mut", 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
ShouldImplTraitCase::new("std::iter::IntoIterator", "into_iter", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Mul", "mul", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Neg", "neg", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::iter::Iterator", "next", 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false),
ShouldImplTraitCase::new("std::ops::Not", "not", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Rem", "rem", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Shl", "shl", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Shr", "shr", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, FN_HEADER, SelfKind::Ref, OutType::Any, true),
ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, FN_HEADER, SelfKind::Ref, OutType::Any, true),
ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, FN_HEADER, SelfKind::No, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true),
ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true),
ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, FN_HEADER, SelfKind::No, OutType::Any, true),
ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, FN_HEADER, SelfKind::No, OutType::Any, true),
ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true),
ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false),
ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
];
#[derive(Clone, Copy, PartialEq, Eq, Debug)]

View file

@ -4,11 +4,11 @@ use clippy_utils::ty::is_type_lang_item;
use rustc_errors::Applicability;
use rustc_hir::{Expr, LangItem};
use rustc_lint::LateContext;
use rustc_span::Span;
use rustc_span::{Span, Symbol};
use super::NEEDLESS_AS_BYTES;
pub fn check(cx: &LateContext<'_>, prev_method: &str, method: &str, prev_recv: &Expr<'_>, span: Span) {
pub fn check(cx: &LateContext<'_>, prev_method: Symbol, method: Symbol, prev_recv: &Expr<'_>, span: Span) {
let ty1 = cx.typeck_results().expr_ty_adjusted(prev_recv).peel_refs();
if is_type_lang_item(cx, ty1, LangItem::String) || ty1.is_str() {
let mut app = Applicability::MachineApplicable;

View file

@ -1,22 +1,22 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::path_res;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::usage::local_used_after_expr;
use clippy_utils::{path_res, sym};
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_hir::def::Res;
use rustc_lint::LateContext;
use rustc_span::sym;
use rustc_span::Symbol;
use super::NEEDLESS_OPTION_AS_DEREF;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, name: &str) {
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, name: Symbol) {
let typeck = cx.typeck_results();
let outer_ty = typeck.expr_ty(expr);
if is_type_diagnostic_item(cx, outer_ty, sym::Option) && outer_ty == typeck.expr_ty(recv) {
if name == "as_deref_mut" && recv.is_syntactic_place_expr() {
if name == sym::as_deref_mut && recv.is_syntactic_place_expr() {
let Res::Local(binding_id) = path_res(cx, recv) else {
return;
};

View file

@ -3,7 +3,7 @@ use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::LateContext;
use rustc_span::sym;
use rustc_span::{Symbol, sym};
use super::NEEDLESS_OPTION_TAKE;
@ -42,20 +42,20 @@ fn is_expr_option(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
/// When this function is called, we are reasonably certain that the `ExprKind` is either
/// `Call` or `MethodCall` because we already checked that the expression is not
/// `is_syntactic_place_expr()`.
fn source_of_temporary_value<'a>(expr: &'a Expr<'_>) -> Option<&'a str> {
fn source_of_temporary_value(expr: &Expr<'_>) -> Option<Symbol> {
match expr.peel_borrows().kind {
ExprKind::Call(function, _) => {
if let ExprKind::Path(QPath::Resolved(_, func_path)) = function.kind
&& !func_path.segments.is_empty()
{
return Some(func_path.segments[0].ident.name.as_str());
return Some(func_path.segments[0].ident.name);
}
if let ExprKind::Path(QPath::TypeRelative(_, func_path_segment)) = function.kind {
return Some(func_path_segment.ident.name.as_str());
return Some(func_path_segment.ident.name);
}
None
},
ExprKind::MethodCall(path_segment, ..) => Some(path_segment.ident.name.as_str()),
ExprKind::MethodCall(path_segment, ..) => Some(path_segment.ident.name),
_ => None,
}
}

View file

@ -1,13 +1,14 @@
use super::OBFUSCATED_IF_ELSE;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::get_parent_expr;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::{get_parent_expr, sym};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::ExprKind;
use rustc_lint::LateContext;
use rustc_span::Symbol;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
@ -15,8 +16,8 @@ pub(super) fn check<'tcx>(
then_recv: &'tcx hir::Expr<'_>,
then_arg: &'tcx hir::Expr<'_>,
unwrap_arg: Option<&'tcx hir::Expr<'_>>,
then_method_name: &str,
unwrap_method_name: &str,
then_method_name: Symbol,
unwrap_method_name: Symbol,
) {
let recv_ty = cx.typeck_results().expr_ty(then_recv);
@ -31,25 +32,25 @@ pub(super) fn check<'tcx>(
};
let if_then = match then_method_name {
"then" if let ExprKind::Closure(closure) = then_arg.kind => {
sym::then if let ExprKind::Closure(closure) = then_arg.kind => {
let body = cx.tcx.hir_body(closure.body);
snippet_with_applicability(cx, body.value.span, "..", &mut applicability)
},
"then_some" => snippet_with_applicability(cx, then_arg.span, "..", &mut applicability),
sym::then_some => snippet_with_applicability(cx, then_arg.span, "..", &mut applicability),
_ => return,
};
// FIXME: Add `unwrap_or_else` and `unwrap_or_default` symbol
let els = match unwrap_method_name {
"unwrap_or" => snippet_with_applicability(cx, unwrap_arg.unwrap().span, "..", &mut applicability),
"unwrap_or_else" if let ExprKind::Closure(closure) = unwrap_arg.unwrap().kind => {
sym::unwrap_or => snippet_with_applicability(cx, unwrap_arg.unwrap().span, "..", &mut applicability),
sym::unwrap_or_else if let ExprKind::Closure(closure) = unwrap_arg.unwrap().kind => {
let body = cx.tcx.hir_body(closure.body);
snippet_with_applicability(cx, body.value.span, "..", &mut applicability)
},
"unwrap_or_else" if let ExprKind::Path(_) = unwrap_arg.unwrap().kind => {
sym::unwrap_or_else if let ExprKind::Path(_) = unwrap_arg.unwrap().kind => {
snippet_with_applicability(cx, unwrap_arg.unwrap().span, "_", &mut applicability) + "()"
},
"unwrap_or_default" => "Default::default()".into(),
sym::unwrap_or_default => "Default::default()".into(),
_ => return,
};

View file

@ -1,14 +1,16 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sym;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_span::{Span, sym};
use rustc_span::Span;
use super::{OPTION_AS_REF_CLONED, method_call};
pub(super) fn check(cx: &LateContext<'_>, cloned_recv: &Expr<'_>, cloned_ident_span: Span) {
if let Some((method @ ("as_ref" | "as_mut"), as_ref_recv, [], as_ref_ident_span, _)) = method_call(cloned_recv)
if let Some((method @ (sym::as_ref | sym::as_mut), as_ref_recv, [], as_ref_ident_span, _)) =
method_call(cloned_recv)
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(as_ref_recv).peel_refs(), sym::Option)
{
span_lint_and_sugg(

View file

@ -23,7 +23,7 @@ pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &hir::Expr<'_>,
method_span: Span,
name: &str,
name: Symbol,
receiver: &'tcx hir::Expr<'_>,
args: &'tcx [hir::Expr<'_>],
) {
@ -33,7 +33,7 @@ pub(super) fn check<'tcx>(
/// `or_insert_with(T::new)` or `or_insert_with(T::default)`.
fn check_unwrap_or_default(
cx: &LateContext<'_>,
name: &str,
name: Symbol,
receiver: &hir::Expr<'_>,
fun: &hir::Expr<'_>,
call_expr: Option<&hir::Expr<'_>>,
@ -66,8 +66,8 @@ pub(super) fn check<'tcx>(
};
let sugg = match (name, call_expr.is_some()) {
("unwrap_or", true) | ("unwrap_or_else", false) => sym::unwrap_or_default,
("or_insert", true) | ("or_insert_with", false) => sym::or_default,
(sym::unwrap_or, true) | (sym::unwrap_or_else, false) => sym::unwrap_or_default,
(sym::or_insert, true) | (sym::or_insert_with, false) => sym::or_default,
_ => return false,
};
@ -126,7 +126,7 @@ pub(super) fn check<'tcx>(
#[expect(clippy::too_many_arguments)]
fn check_or_fn_call<'tcx>(
cx: &LateContext<'tcx>,
name: &str,
name: Symbol,
method_span: Span,
self_expr: &hir::Expr<'_>,
arg: &'tcx hir::Expr<'_>,
@ -137,11 +137,16 @@ pub(super) fn check<'tcx>(
fun_span: Option<Span>,
) -> bool {
// (path, fn_has_argument, methods, suffix)
const KNOW_TYPES: [(Symbol, bool, &[&str], &str); 4] = [
(sym::BTreeEntry, false, &["or_insert"], "with"),
(sym::HashMapEntry, false, &["or_insert"], "with"),
(sym::Option, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"),
(sym::Result, true, &["or", "unwrap_or"], "else"),
const KNOW_TYPES: [(Symbol, bool, &[Symbol], &str); 4] = [
(sym::BTreeEntry, false, &[sym::or_insert], "with"),
(sym::HashMapEntry, false, &[sym::or_insert], "with"),
(
sym::Option,
false,
&[sym::map_or, sym::ok_or, sym::or, sym::unwrap_or],
"else",
),
(sym::Result, true, &[sym::or, sym::unwrap_or], "else"),
];
if KNOW_TYPES.iter().any(|k| k.2.contains(&name))

View file

@ -2,14 +2,13 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::sugg::deref_closure_args;
use clippy_utils::ty::is_type_lang_item;
use clippy_utils::{is_receiver_of_method_call, is_trait_method, strip_pat_refs};
use clippy_utils::{is_receiver_of_method_call, is_trait_method, strip_pat_refs, sym};
use hir::ExprKind;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::PatKind;
use rustc_lint::LateContext;
use rustc_span::Span;
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol};
use super::SEARCH_IS_SOME;
@ -19,7 +18,7 @@ use super::SEARCH_IS_SOME;
pub(super) fn check<'tcx>(
cx: &LateContext<'_>,
expr: &'tcx hir::Expr<'_>,
search_method: &str,
search_method: Symbol,
is_some: bool,
search_recv: &hir::Expr<'_>,
search_arg: &'tcx hir::Expr<'_>,
@ -35,7 +34,7 @@ pub(super) fn check<'tcx>(
// suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()`
// suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()`
let mut applicability = Applicability::MachineApplicable;
let any_search_snippet = if search_method == "find"
let any_search_snippet = if search_method == sym::find
&& let ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind
&& let closure_body = cx.tcx.hir_body(body)
&& let Some(closure_arg) = closure_body.params.first()
@ -107,7 +106,7 @@ pub(super) fn check<'tcx>(
}
}
// lint if `find()` is called by `String` or `&str`
else if search_method == "find" {
else if search_method == sym::find {
let is_string_or_str_slice = |e| {
let self_ty = cx.typeck_results().expr_ty(e).peel_refs();
if is_type_lang_item(cx, self_ty, hir::LangItem::String) {

View file

@ -15,7 +15,7 @@ pub(super) fn check<'a>(cx: &LateContext<'a>, expr: &'_ Expr<'_>, split_recv: &'
// or `"\r\n"`). There are a lot of ways to specify a pattern, and this lint only checks the most
// basic ones: a `'\n'`, `"\n"`, and `"\r\n"`.
if let ExprKind::MethodCall(trim_method_name, trim_recv, [], _) = split_recv.kind
&& trim_method_name.ident.as_str() == "trim"
&& trim_method_name.ident.name == sym::trim
&& cx.typeck_results().expr_ty_adjusted(trim_recv).peel_refs().is_str()
&& !is_const_evaluatable(cx, trim_recv)
&& let ExprKind::Lit(split_lit) = split_arg.kind

View file

@ -4,7 +4,7 @@ use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_context;
use clippy_utils::usage::local_used_after_expr;
use clippy_utils::visitors::{Descend, for_each_expr};
use clippy_utils::{is_diag_item_method, path_to_local_id, paths};
use clippy_utils::{is_diag_item_method, path_to_local_id, paths, sym};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::{
@ -12,13 +12,13 @@ use rustc_hir::{
};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::{Span, Symbol, SyntaxContext, sym};
use rustc_span::{Span, Symbol, SyntaxContext};
use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN};
pub(super) fn check(
cx: &LateContext<'_>,
method_name: &str,
method_name: Symbol,
expr: &Expr<'_>,
self_arg: &Expr<'_>,
pat_arg: &Expr<'_>,
@ -45,9 +45,9 @@ pub(super) fn check(
}
}
fn lint_needless(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) {
fn lint_needless(cx: &LateContext<'_>, method_name: Symbol, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) {
let mut app = Applicability::MachineApplicable;
let r = if method_name == "splitn" { "" } else { "r" };
let r = if method_name == sym::splitn { "" } else { "r" };
span_lint_and_sugg(
cx,
@ -66,14 +66,14 @@ fn lint_needless(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_
fn check_manual_split_once(
cx: &LateContext<'_>,
method_name: &str,
method_name: Symbol,
expr: &Expr<'_>,
self_arg: &Expr<'_>,
pat_arg: &Expr<'_>,
usage: &IterUsage,
) {
let ctxt = expr.span.ctxt();
let (msg, reverse) = if method_name == "splitn" {
let (msg, reverse) = if method_name == sym::splitn {
("manual implementation of `split_once`", false)
} else {
("manual implementation of `rsplit_once`", true)
@ -121,7 +121,7 @@ fn check_manual_split_once(
/// ```
fn check_manual_split_once_indirect(
cx: &LateContext<'_>,
method_name: &str,
method_name: Symbol,
expr: &Expr<'_>,
self_arg: &Expr<'_>,
pat_arg: &Expr<'_>,
@ -143,7 +143,7 @@ fn check_manual_split_once_indirect(
&& first.name != second.name
&& !local_used_after_expr(cx, iter_binding_id, second.init_expr)
{
let (r, lhs, rhs) = if method_name == "splitn" {
let (r, lhs, rhs) = if method_name == sym::splitn {
("", first.name, second.name)
} else {
("r", second.name, first.name)

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::method_chain_args;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_lang_item;
use clippy_utils::{method_chain_args, sym};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
@ -13,7 +13,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
if !is_type_lang_item(cx, obj_ty, hir::LangItem::String) {
return;
}
if let Some(arglists) = method_chain_args(arg, &["chars"]) {
if let Some(arglists) = method_chain_args(arg, &[sym::chars]) {
let target = &arglists[0].0;
let self_ty = cx.typeck_results().expr_ty(target).peel_refs();
let ref_str = if self_ty.is_str() {

View file

@ -2,11 +2,12 @@ use clippy_utils::diagnostics::span_lint_and_note;
use rustc_ast::LitKind;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::Symbol;
use rustc_span::source_map::Spanned;
use super::SUSPICIOUS_SPLITN;
pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, count: u128) {
pub(super) fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &Expr<'_>, self_arg: &Expr<'_>, count: u128) {
if count <= 1
&& let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& let Some(impl_id) = cx.tcx.impl_of_method(call_id)

View file

@ -9,10 +9,16 @@ use rustc_hir as hir;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::Symbol;
use super::{UNNECESSARY_FILTER_MAP, UNNECESSARY_FIND_MAP};
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>, name: &str) {
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
arg: &'tcx hir::Expr<'tcx>,
name: Symbol,
) {
if !is_trait_method(cx, expr, sym::Iterator) {
return;
}
@ -38,7 +44,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
let sugg = if !found_filtering {
// Check if the closure is .filter_map(|x| Some(x))
if name == "filter_map"
if name == sym::filter_map
&& let hir::ExprKind::Call(expr, args) = body.value.kind
&& is_res_lang_ctor(cx, path_res(cx, expr), OptionSome)
&& let hir::ExprKind::Path(_) = args[0].kind
@ -51,7 +57,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
);
return;
}
if name == "filter_map" {
if name == sym::filter_map {
"map(..)"
} else {
"map(..).next()"
@ -61,7 +67,11 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
ty::Adt(adt, subst)
if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && in_ty == subst.type_at(0) =>
{
if name == "filter_map" { "filter(..)" } else { "find(..)" }
if name == sym::filter_map {
"filter(..)"
} else {
"find(..)"
}
},
_ => return,
}
@ -70,7 +80,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
};
span_lint(
cx,
if name == "filter_map" {
if name == sym::filter_map {
UNNECESSARY_FILTER_MAP
} else {
UNNECESSARY_FIND_MAP

View file

@ -1,10 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{MaybePath, is_res_lang_ctor, last_path_segment, path_res};
use clippy_utils::{MaybePath, is_res_lang_ctor, last_path_segment, path_res, sym};
use rustc_errors::Applicability;
use rustc_hir::{self as hir, AmbigArg};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_middle::ty::print::with_forced_trimmed_paths;
use rustc_span::Symbol;
use super::UNNECESSARY_LITERAL_UNWRAP;
@ -25,7 +26,7 @@ pub(super) fn check(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
recv: &hir::Expr<'_>,
method: &str,
method: Symbol,
args: &[hir::Expr<'_>],
) {
let init = clippy_utils::expr_or_init(cx, recv);
@ -42,17 +43,17 @@ pub(super) fn check(
let res = cx.qpath_res(qpath, call.hir_id());
if is_res_lang_ctor(cx, res, hir::LangItem::OptionSome) {
("Some", call_args, get_ty_from_args(args, 0))
(sym::Some, call_args, get_ty_from_args(args, 0))
} else if is_res_lang_ctor(cx, res, hir::LangItem::ResultOk) {
("Ok", call_args, get_ty_from_args(args, 0))
(sym::Ok, call_args, get_ty_from_args(args, 0))
} else if is_res_lang_ctor(cx, res, hir::LangItem::ResultErr) {
("Err", call_args, get_ty_from_args(args, 1))
(sym::Err, call_args, get_ty_from_args(args, 1))
} else {
return;
}
} else if is_res_lang_ctor(cx, path_res(cx, init), hir::LangItem::OptionNone) {
let call_args: &[hir::Expr<'_>] = &[];
("None", call_args, None)
(sym::None, call_args, None)
} else {
return;
};
@ -62,12 +63,12 @@ pub(super) fn check(
span_lint_and_then(cx, UNNECESSARY_LITERAL_UNWRAP, expr.span, help_message, |diag| {
let suggestions = match (constructor, method, ty) {
("None", "unwrap", _) => Some(vec![(expr.span, "panic!()".to_string())]),
("None", "expect", _) => Some(vec![
(sym::None, sym::unwrap, _) => Some(vec![(expr.span, "panic!()".to_string())]),
(sym::None, sym::expect, _) => Some(vec![
(expr.span.with_hi(args[0].span.lo()), "panic!(".to_string()),
(expr.span.with_lo(args[0].span.hi()), ")".to_string()),
]),
("Some" | "Ok", "unwrap_unchecked", _) | ("Err", "unwrap_err_unchecked", _) => {
(sym::Some | sym::Ok, sym::unwrap_unchecked, _) | (sym::Err, sym::unwrap_err_unchecked, _) => {
let mut suggs = vec![
(recv.span.with_hi(call_args[0].span.lo()), String::new()),
(expr.span.with_lo(call_args[0].span.hi()), String::new()),
@ -83,7 +84,7 @@ pub(super) fn check(
}
Some(suggs)
},
("None", "unwrap_or_default", _) => {
(sym::None, sym::unwrap_or_default, _) => {
let ty = cx.typeck_results().expr_ty(expr);
let default_ty_string = if let ty::Adt(def, ..) = ty.kind() {
with_forced_trimmed_paths!(format!("{}", cx.tcx.def_path_str(def.did())))
@ -92,11 +93,11 @@ pub(super) fn check(
};
Some(vec![(expr.span, format!("{default_ty_string}::default()"))])
},
("None", "unwrap_or", _) => Some(vec![
(sym::None, sym::unwrap_or, _) => Some(vec![
(expr.span.with_hi(args[0].span.lo()), String::new()),
(expr.span.with_lo(args[0].span.hi()), String::new()),
]),
("None", "unwrap_or_else", _) => match args[0].kind {
(sym::None, sym::unwrap_or_else, _) => match args[0].kind {
hir::ExprKind::Closure(hir::Closure { body, .. }) => Some(vec![
(expr.span.with_hi(cx.tcx.hir_body(*body).value.span.lo()), String::new()),
(expr.span.with_lo(args[0].span.hi()), String::new()),
@ -105,14 +106,14 @@ pub(super) fn check(
},
_ if call_args.is_empty() => None,
(_, _, Some(_)) => None,
("Ok", "unwrap_err", None) | ("Err", "unwrap", None) => Some(vec![
(sym::Ok, sym::unwrap_err, None) | (sym::Err, sym::unwrap, None) => Some(vec![
(
recv.span.with_hi(call_args[0].span.lo()),
"panic!(\"{:?}\", ".to_string(),
),
(expr.span.with_lo(call_args[0].span.hi()), ")".to_string()),
]),
("Ok", "expect_err", None) | ("Err", "expect", None) => Some(vec![
(sym::Ok, sym::expect_err, None) | (sym::Err, sym::expect, None) => Some(vec![
(
recv.span.with_hi(call_args[0].span.lo()),
"panic!(\"{1}: {:?}\", ".to_string(),

View file

@ -5,16 +5,17 @@ use clippy_utils::consts::{ConstEvalCtxt, Constant, ConstantSource, FullInt};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::sym;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::{Span, sym};
use rustc_span::{Span, Symbol};
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
name: &str,
name: Symbol,
recv: &'tcx Expr<'_>,
arg: &'tcx Expr<'_>,
) {
@ -47,10 +48,10 @@ pub(super) fn check<'tcx>(
}
}
fn lint(cx: &LateContext<'_>, expr: &Expr<'_>, name: &str, lhs: Span, rhs: Span, order: Ordering) {
fn lint(cx: &LateContext<'_>, expr: &Expr<'_>, name: Symbol, lhs: Span, rhs: Span, order: Ordering) {
let cmp_str = if order.is_ge() { "smaller" } else { "greater" };
let suggested_value = if (name == "min" && order.is_ge()) || (name == "max" && order.is_le()) {
let suggested_value = if (name == sym::min && order.is_ge()) || (name == sym::max && order.is_le()) {
snippet(cx, rhs, "..")
} else {
snippet(cx, lhs, "..")

View file

@ -619,7 +619,7 @@ fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id:
/// Returns true if the named method can be used to convert the receiver to its "owned"
/// representation.
fn is_to_owned_like<'a>(cx: &LateContext<'a>, call_expr: &Expr<'a>, method_name: Symbol, method_def_id: DefId) -> bool {
is_clone_like(cx, method_name.as_str(), method_def_id)
is_clone_like(cx, method_name, method_def_id)
|| is_cow_into_owned(cx, method_name, method_def_id)
|| is_to_string_on_string_like(cx, call_expr, method_name, method_def_id)
}
@ -655,11 +655,18 @@ fn is_to_string_on_string_like<'a>(
}
}
fn is_a_std_map_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
is_type_diagnostic_item(cx, ty, sym::HashSet)
|| is_type_diagnostic_item(cx, ty, sym::HashMap)
|| is_type_diagnostic_item(cx, ty, sym::BTreeMap)
|| is_type_diagnostic_item(cx, ty, sym::BTreeSet)
fn std_map_key<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
match ty.kind() {
ty::Adt(adt, args)
if matches!(
cx.tcx.get_diagnostic_name(adt.did()),
Some(sym::BTreeMap | sym::BTreeSet | sym::HashMap | sym::HashSet)
) =>
{
Some(args.type_at(0))
},
_ => None,
}
}
fn is_str_and_string(cx: &LateContext<'_>, arg_ty: Ty<'_>, original_arg_ty: Ty<'_>) -> bool {
@ -679,11 +686,11 @@ fn check_if_applicable_to_argument<'tcx>(cx: &LateContext<'tcx>, arg: &Expr<'tcx
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = arg.kind
&& let ExprKind::MethodCall(method_path, caller, &[], _) = expr.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& let method_name = method_path.ident.name.as_str()
&& let method_name = method_path.ident.name
&& match method_name {
"to_owned" => cx.tcx.is_diagnostic_item(sym::to_owned_method, method_def_id),
"to_string" => cx.tcx.is_diagnostic_item(sym::to_string_method, method_def_id),
"to_vec" => cx
sym::to_owned => cx.tcx.is_diagnostic_item(sym::to_owned_method, method_def_id),
sym::to_string => cx.tcx.is_diagnostic_item(sym::to_string_method, method_def_id),
sym::to_vec => cx
.tcx
.impl_of_method(method_def_id)
.filter(|&impl_did| cx.tcx.type_of(impl_did).instantiate_identity().is_slice())
@ -721,6 +728,7 @@ fn check_if_applicable_to_argument<'tcx>(cx: &LateContext<'tcx>, arg: &Expr<'tcx
// 1. This is a method with only one argument that doesn't come from a trait.
// 2. That it has `Borrow` in its generic predicates.
// 3. `Self` is a std "map type" (ie `HashSet`, `HashMap`, `BTreeSet`, `BTreeMap`).
// 4. The key to the "map type" is not a reference.
fn check_borrow_predicate<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if let ExprKind::MethodCall(_, caller, &[arg], _) = expr.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
@ -738,7 +746,9 @@ fn check_borrow_predicate<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
})
&& let caller_ty = cx.typeck_results().expr_ty(caller)
// For now we limit it to "map types".
&& is_a_std_map_type(cx, caller_ty)
&& let Some(key_ty) = std_map_key(cx, caller_ty)
// We need to check that the key type is not a reference.
&& !key_ty.is_ref()
{
check_if_applicable_to_argument(cx, &arg);
}

View file

@ -7,7 +7,7 @@ use rustc_hir::{self as hir, LangItem};
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::{Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
use rustc_span::{Span, sym};
use rustc_span::{Span, Symbol, sym};
use core::ops::ControlFlow;
@ -39,7 +39,7 @@ fn get_enum_ty(enum_ty: Ty<'_>) -> Option<Ty<'_>> {
}
/// Checks for the `USELESS_ASREF` lint.
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, recvr: &hir::Expr<'_>) {
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: Symbol, recvr: &hir::Expr<'_>) {
// when we get here, we've already checked that the call name is "as_ref" or "as_mut"
// check if the call is to the actual `AsRef` or `AsMut` trait
let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else {
@ -79,9 +79,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str,
applicability,
);
}
} else if let Some(impl_id) = cx.tcx.opt_parent(def_id)
} else if let Some(impl_id) = cx.tcx.impl_of_method(def_id)
&& let Some(adt) = cx.tcx.type_of(impl_id).instantiate_identity().ty_adt_def()
&& (cx.tcx.lang_items().option_type() == Some(adt.did()) || cx.tcx.is_diagnostic_item(sym::Result, adt.did()))
&& matches!(cx.tcx.get_diagnostic_name(adt.did()), Some(sym::Option | sym::Result))
{
let rcv_ty = cx.typeck_results().expr_ty(recvr).peel_refs();
let res_ty = cx.typeck_results().expr_ty(expr).peel_refs();
@ -161,7 +161,7 @@ fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
}
}
fn lint_as_ref_clone(cx: &LateContext<'_>, span: Span, recvr: &hir::Expr<'_>, call_name: &str) {
fn lint_as_ref_clone(cx: &LateContext<'_>, span: Span, recvr: &hir::Expr<'_>, call_name: Symbol) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,

View file

@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::is_copy;
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_span::Span;
use rustc_span::{Span, Symbol};
use std::fmt;
use super::WRONG_SELF_CONVENTION;
@ -83,17 +83,18 @@ impl fmt::Display for Convention {
#[allow(clippy::too_many_arguments)]
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
item_name: &str,
item_name: Symbol,
self_ty: Ty<'tcx>,
first_arg_ty: Ty<'tcx>,
first_arg_span: Span,
implements_trait: bool,
is_trait_item: bool,
) {
let item_name_str = item_name.as_str();
if let Some((conventions, self_kinds)) = &CONVENTIONS.iter().find(|(convs, _)| {
convs
.iter()
.all(|conv| conv.check(cx, self_ty, item_name, implements_trait, is_trait_item))
.all(|conv| conv.check(cx, self_ty, item_name_str, implements_trait, is_trait_item))
}) {
// don't lint if it implements a trait but not willing to check `Copy` types conventions (see #7032)
if implements_trait

Some files were not shown because too many files have changed in this diff Show more