Merge commit '877967959a' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2025-08-22 14:57:22 +02:00
parent bea2e2be48
commit 71b79ab466
142 changed files with 1828 additions and 1055 deletions

View file

@ -16,7 +16,7 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
# Unsetting this would make so that any malicious package could get our Github Token
persist-credentials: false

View file

@ -36,7 +36,7 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
@ -96,7 +96,7 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
@ -114,7 +114,7 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
@ -170,7 +170,7 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false

View file

@ -24,7 +24,7 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
# Unsetting this would make so that any malicious package could get our Github Token
persist-credentials: false

View file

@ -25,13 +25,13 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
# Unsetting this would make so that any malicious package could get our Github Token
persist-credentials: false
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: ${{ env.TARGET_BRANCH }}
path: 'out'

View file

@ -16,7 +16,7 @@ jobs:
permissions:
pull-requests: write
# Do not in any case add code that runs anything coming from the the content
# Do not in any case add code that runs anything coming from the content
# of the pull request, as malicious code would be able to access the private
# GitHub token.
steps:

View file

@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 2
# Unsetting this would make so that any malicious package could get our Github Token
@ -80,7 +80,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
# Unsetting this would make so that any malicious package could get our Github Token
persist-credentials: false
@ -113,7 +113,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
# Unsetting this would make so that any malicious package could get our Github Token
persist-credentials: false

View file

@ -11,7 +11,7 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
# Unsetting this would make so that any malicious package could get our Github Token
persist-credentials: false

View file

@ -17,7 +17,7 @@ All contributors are expected to follow the [Rust Code of Conduct].
- [High level approach](#high-level-approach)
- [Finding something to fix/improve](#finding-something-to-fiximprove)
- [Getting code-completion for rustc internals to work](#getting-code-completion-for-rustc-internals-to-work)
- [IntelliJ Rust](#intellij-rust)
- [RustRover](#rustrover)
- [Rust Analyzer](#rust-analyzer)
- [How Clippy works](#how-clippy-works)
- [Issue and PR triage](#issue-and-pr-triage)
@ -92,22 +92,22 @@ an AST expression).
## Getting code-completion for rustc internals to work
### IntelliJ Rust
Unfortunately, [`IntelliJ Rust`][IntelliJ_rust_homepage] does not (yet?) understand how Clippy uses compiler-internals
### RustRover
Unfortunately, [`RustRover`][RustRover_homepage] does not (yet?) understand how Clippy uses compiler-internals
using `extern crate` and it also needs to be able to read the source files of the rustc-compiler which are not
available via a `rustup` component at the time of writing.
To work around this, you need to have a copy of the [rustc-repo][rustc_repo] available which can be obtained via
`git clone https://github.com/rust-lang/rust/`.
Then you can run a `cargo dev` command to automatically make Clippy use the rustc-repo via path-dependencies
which `IntelliJ Rust` will be able to understand.
which `RustRover` will be able to understand.
Run `cargo dev setup intellij --repo-path <repo-path>` where `<repo-path>` is a path to the rustc repo
you just cloned.
The command will add path-dependencies pointing towards rustc-crates inside the rustc repo to
Clippy's `Cargo.toml`s and should allow `IntelliJ Rust` to understand most of the types that Clippy uses.
Clippy's `Cargo.toml`s and should allow `RustRover` to understand most of the types that Clippy uses.
Just make sure to remove the dependencies again before finally making a pull request!
[rustc_repo]: https://github.com/rust-lang/rust/
[IntelliJ_rust_homepage]: https://intellij-rust.github.io/
[RustRover_homepage]: https://www.jetbrains.com/rust/
### Rust Analyzer
For [`rust-analyzer`][ra_homepage] to work correctly make sure that in the `rust-analyzer` configuration you set

View file

@ -64,3 +64,14 @@ harness = false
[[test]]
name = "dogfood"
harness = false
# quine-mc_cluskey makes up a significant part of the runtime in dogfood
# due to the number of conditions in the clippy_lints crate
# and enabling optimizations for that specific dependency helps a bit
# without increasing total build times.
[profile.dev.package.quine-mc_cluskey]
opt-level = 3
[lints.rust.unexpected_cfgs]
level = "warn"
check-cfg = ['cfg(bootstrap)']

View file

@ -15,7 +15,7 @@ jobs:
clippy_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Run Clippy
run: cargo clippy --all-targets --all-features
```

View file

@ -95,7 +95,7 @@ cargo dev new_lint
cargo dev deprecate
# automatically formatting all code before each commit
cargo dev setup git-hook
# (experimental) Setup Clippy to work with IntelliJ-Rust
# (experimental) Setup Clippy to work with RustRover
cargo dev setup intellij
# runs the `dogfood` tests
cargo dev dogfood
@ -103,7 +103,7 @@ cargo dev dogfood
More about [intellij] command usage and reasons.
[intellij]: https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md#intellij-rust
[intellij]: https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md#rustrover
## lintcheck

View file

@ -141,7 +141,7 @@ impl LateLintPass<'_> for MyStructLint {
// we are looking for the `DefId` of `Drop` trait in lang items
.drop_trait()
// then we use it with our type `ty` by calling `implements_trait` from Clippy's utils
.map_or(false, |id| implements_trait(cx, ty, id, &[])) {
.is_some_and(|id| implements_trait(cx, ty, id, &[])) {
// `expr` implements `Drop` trait
}
}

View file

@ -555,7 +555,7 @@ default configuration of Clippy. By default, any configuration will replace the
* `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
* `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "PowerPC", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
---
**Affected lints:**

View file

@ -34,7 +34,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
"GitHub", "GitLab",
"IPv4", "IPv6",
"ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript",
"WebAssembly",
"PowerPC", "WebAssembly",
"NaN", "NaNs",
"OAuth", "GraphQL",
"OCaml",

View file

@ -1,35 +1,28 @@
use crate::utils::exit_if_err;
use std::process::Command;
use crate::utils::{cargo_cmd, run_exit_on_err};
use itertools::Itertools;
/// # Panics
///
/// Panics if unable to run the dogfood test
#[allow(clippy::fn_params_excessive_bools)]
pub fn dogfood(fix: bool, allow_dirty: bool, allow_staged: bool, allow_no_vcs: bool) {
let mut cmd = Command::new("cargo");
cmd.args(["test", "--test", "dogfood"])
.args(["--features", "internal"])
.args(["--", "dogfood_clippy", "--nocapture"]);
let mut dogfood_args = Vec::new();
if fix {
dogfood_args.push("--fix");
}
if allow_dirty {
dogfood_args.push("--allow-dirty");
}
if allow_staged {
dogfood_args.push("--allow-staged");
}
if allow_no_vcs {
dogfood_args.push("--allow-no-vcs");
}
cmd.env("__CLIPPY_DOGFOOD_ARGS", dogfood_args.join(" "));
exit_if_err(cmd.status());
run_exit_on_err(
"cargo test",
cargo_cmd()
.args(["test", "--test", "dogfood"])
.args(["--features", "internal"])
.args(["--", "dogfood_clippy", "--nocapture"])
.env(
"__CLIPPY_DOGFOOD_ARGS",
[
if fix { "--fix" } else { "" },
if allow_dirty { "--allow-dirty" } else { "" },
if allow_staged { "--allow-staged" } else { "" },
if allow_no_vcs { "--allow-no-vcs" } else { "" },
]
.iter()
.filter(|x| !x.is_empty())
.join(" "),
),
);
}

View file

@ -15,8 +15,7 @@
)]
#![allow(clippy::missing_panics_doc)]
// The `rustc_driver` crate seems to be required in order to use the `rust_lexer` crate.
#[allow(unused_extern_crates)]
#[expect(unused_extern_crates, reason = "required to link to rustc crates")]
extern crate rustc_driver;
extern crate rustc_lexer;
extern crate rustc_literal_escaper;
@ -32,4 +31,6 @@ pub mod serve;
pub mod setup;
pub mod sync;
pub mod update_lints;
pub mod utils;
mod utils;
pub use utils::{ClippyInfo, UpdateMode};

View file

@ -1,19 +1,18 @@
use crate::utils::{cargo_clippy_path, exit_if_err};
use std::process::{self, Command};
use crate::utils::{ErrAction, cargo_cmd, expect_action, run_exit_on_err};
use std::process::Command;
use std::{env, fs};
pub fn run<'a>(path: &str, edition: &str, args: impl Iterator<Item = &'a String>) {
let is_file = match fs::metadata(path) {
Ok(metadata) => metadata.is_file(),
Err(e) => {
eprintln!("Failed to read {path}: {e:?}");
process::exit(1);
},
};
#[cfg(not(windows))]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
#[cfg(windows)]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
pub fn run<'a>(path: &str, edition: &str, args: impl Iterator<Item = &'a String>) {
let is_file = expect_action(fs::metadata(path), ErrAction::Read, path).is_file();
if is_file {
exit_if_err(
Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
run_exit_on_err(
"cargo run",
cargo_cmd()
.args(["run", "--bin", "clippy-driver", "--"])
.args(["-L", "./target/debug"])
.args(["-Z", "no-codegen"])
@ -21,24 +20,25 @@ pub fn run<'a>(path: &str, edition: &str, args: impl Iterator<Item = &'a String>
.arg(path)
.args(args)
// Prevent rustc from creating `rustc-ice-*` files the console output is enough.
.env("RUSTC_ICE", "0")
.status(),
.env("RUSTC_ICE", "0"),
);
} else {
exit_if_err(
Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
.arg("build")
.status(),
// Ideally this would just be `cargo run`, but the working directory needs to be
// set to clippy's directory when building, and the target project's directory
// when running clippy. `cargo` can only set a single working directory for both
// when using `run`.
run_exit_on_err("cargo build", cargo_cmd().arg("build"));
let mut exe = env::current_exe().expect("failed to get current executable name");
exe.set_file_name(CARGO_CLIPPY_EXE);
run_exit_on_err(
"cargo clippy",
Command::new(exe)
.arg("clippy")
.args(args)
// Prevent rustc from creating `rustc-ice-*` files the console output is enough.
.env("RUSTC_ICE", "0")
.current_dir(path),
);
let status = Command::new(cargo_clippy_path())
.arg("clippy")
.args(args)
// Prevent rustc from creating `rustc-ice-*` files the console output is enough.
.env("RUSTC_ICE", "0")
.current_dir(path)
.status();
exit_if_err(status);
}
}

View file

@ -4,14 +4,15 @@
use clap::{Args, Parser, Subcommand};
use clippy_dev::{
deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync, update_lints, utils,
ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync,
update_lints,
};
use std::convert::Infallible;
use std::env;
fn main() {
let dev = Dev::parse();
let clippy = utils::ClippyInfo::search_for_manifest();
let clippy = ClippyInfo::search_for_manifest();
if let Err(e) = env::set_current_dir(&clippy.path) {
panic!("error setting current directory to `{}`: {e}", clippy.path.display());
}
@ -26,8 +27,8 @@ fn main() {
allow_staged,
allow_no_vcs,
} => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs),
DevCommand::Fmt { check } => fmt::run(utils::UpdateMode::from_check(check)),
DevCommand::UpdateLints { check } => update_lints::update(utils::UpdateMode::from_check(check)),
DevCommand::Fmt { check } => fmt::run(UpdateMode::from_check(check)),
DevCommand::UpdateLints { check } => update_lints::update(UpdateMode::from_check(check)),
DevCommand::NewLint {
pass,
name,
@ -35,7 +36,7 @@ fn main() {
r#type,
msrv,
} => match new_lint::create(clippy.version, pass, &name, &category, r#type.as_deref(), msrv) {
Ok(()) => update_lints::update(utils::UpdateMode::Change),
Ok(()) => update_lints::update(UpdateMode::Change),
Err(e) => eprintln!("Unable to create lint: {e}"),
},
DevCommand::Setup(SetupCommand { subcommand }) => match subcommand {

View file

@ -1,7 +1,11 @@
use crate::utils::{ErrAction, cargo_cmd, expect_action};
use core::fmt::Display;
use core::mem;
use std::path::Path;
use std::process::Command;
use std::time::{Duration, SystemTime};
use std::{env, thread};
use std::{fs, thread};
use walkdir::WalkDir;
#[cfg(windows)]
const PYTHON: &str = "python";
@ -18,56 +22,83 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
Some(lint) => format!("http://localhost:{port}/#{lint}"),
});
let mut last_update = mtime("util/gh-pages/index.html");
loop {
let index_time = mtime("util/gh-pages/index.html");
let times = [
"clippy_lints/src",
"util/gh-pages/index_template.html",
"tests/compile-test.rs",
]
.map(mtime);
if times.iter().any(|&time| index_time < time) {
Command::new(env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
.arg("collect-metadata")
.spawn()
.unwrap()
.wait()
.unwrap();
if is_metadata_outdated(mem::replace(&mut last_update, SystemTime::now())) {
// Ignore the command result; we'll fall back to displaying the old metadata.
let _ = expect_action(
cargo_cmd().arg("collect-metadata").status(),
ErrAction::Run,
"cargo collect-metadata",
);
last_update = SystemTime::now();
}
// Only start the web server the first time around.
if let Some(url) = url.take() {
thread::spawn(move || {
let mut child = Command::new(PYTHON)
.arg("-m")
.arg("http.server")
.arg(port.to_string())
.current_dir("util/gh-pages")
.spawn()
.unwrap();
let mut child = expect_action(
Command::new(PYTHON)
.args(["-m", "http.server", port.to_string().as_str()])
.current_dir("util/gh-pages")
.spawn(),
ErrAction::Run,
"python -m http.server",
);
// Give some time for python to start
thread::sleep(Duration::from_millis(500));
// Launch browser after first export.py has completed and http.server is up
let _result = opener::open(url);
child.wait().unwrap();
expect_action(child.wait(), ErrAction::Run, "python -m http.server");
});
}
// Delay to avoid updating the metadata too aggressively.
thread::sleep(Duration::from_millis(1000));
}
}
fn mtime(path: impl AsRef<Path>) -> SystemTime {
let path = path.as_ref();
if path.is_dir() {
path.read_dir()
.into_iter()
.flatten()
.flatten()
.map(|entry| mtime(entry.path()))
.max()
.unwrap_or(SystemTime::UNIX_EPOCH)
} else {
path.metadata()
.and_then(|metadata| metadata.modified())
.unwrap_or(SystemTime::UNIX_EPOCH)
fn log_err_and_continue<T>(res: Result<T, impl Display>, path: &Path) -> Option<T> {
match res {
Ok(x) => Some(x),
Err(ref e) => {
eprintln!("error reading `{}`: {e}", path.display());
None
},
}
}
fn mtime(path: &str) -> SystemTime {
log_err_and_continue(fs::metadata(path), path.as_ref())
.and_then(|metadata| log_err_and_continue(metadata.modified(), path.as_ref()))
.unwrap_or(SystemTime::UNIX_EPOCH)
}
fn is_metadata_outdated(time: SystemTime) -> bool {
// Ignore all IO errors here. We don't want to stop them from hosting the server.
if time < mtime("util/gh-pages/index_template.html") || time < mtime("tests/compile-test.rs") {
return true;
}
let Some(dir) = log_err_and_continue(fs::read_dir("."), ".".as_ref()) else {
return false;
};
dir.map_while(|e| log_err_and_continue(e, ".".as_ref())).any(|e| {
let name = e.file_name();
let name_bytes = name.as_encoded_bytes();
if (name_bytes.starts_with(b"clippy_lints") && name_bytes != b"clippy_lints_internal")
|| name_bytes == b"clippy_config"
{
WalkDir::new(&name)
.into_iter()
.map_while(|e| log_err_and_continue(e, name.as_ref()))
.filter(|e| e.file_type().is_file())
.filter_map(|e| {
log_err_and_continue(e.metadata(), e.path())
.and_then(|m| log_err_and_continue(m.modified(), e.path()))
})
.any(|ftime| time < ftime)
} else {
false
}
})
}

View file

@ -1,8 +1,6 @@
use std::fs;
use std::path::Path;
use super::verify_inside_clippy_dir;
/// Rusts setup uses `git rev-parse --git-common-dir` to get the root directory of the repo.
/// I've decided against this for the sake of simplicity and to make sure that it doesn't install
/// the hook if `clippy_dev` would be used in the rust tree. The hook also references this tool
@ -35,10 +33,6 @@ pub fn install_hook(force_override: bool) {
}
fn check_precondition(force_override: bool) -> bool {
if !verify_inside_clippy_dir() {
return false;
}
// Make sure that we can find the git repository
let git_path = Path::new(REPO_GIT_DIR);
if !git_path.exists() || !git_path.is_dir() {

View file

@ -2,23 +2,3 @@ pub mod git_hook;
pub mod intellij;
pub mod toolchain;
pub mod vscode;
use std::path::Path;
const CLIPPY_DEV_DIR: &str = "clippy_dev";
/// This function verifies that the tool is being executed in the clippy directory.
/// This is useful to ensure that setups only modify Clippy's resources. The verification
/// is done by checking that `clippy_dev` is a sub directory of the current directory.
///
/// It will print an error message and return `false` if the directory could not be
/// verified.
fn verify_inside_clippy_dir() -> bool {
let path = Path::new(CLIPPY_DEV_DIR);
if path.exists() && path.is_dir() {
true
} else {
eprintln!("error: unable to verify that the working directory is clippy's directory");
false
}
}

View file

@ -1,20 +1,12 @@
use crate::utils::{cargo_cmd, run_exit_on_err};
use std::env::consts::EXE_SUFFIX;
use std::env::current_dir;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use walkdir::WalkDir;
use crate::utils::exit_if_err;
use super::verify_inside_clippy_dir;
pub fn create(standalone: bool, force: bool, release: bool, name: &str) {
if !verify_inside_clippy_dir() {
return;
}
let rustup_home = std::env::var("RUSTUP_HOME").unwrap();
let toolchain = std::env::var("RUSTUP_TOOLCHAIN").unwrap();
@ -51,11 +43,10 @@ pub fn create(standalone: bool, force: bool, release: bool, name: &str) {
}
}
let status = Command::new("cargo")
.arg("build")
.args(release.then_some("--release"))
.status();
exit_if_err(status);
run_exit_on_err(
"cargo build",
cargo_cmd().arg("build").args(release.then_some("--release")),
);
install_bin("cargo-clippy", &dest, standalone, release);
install_bin("clippy-driver", &dest, standalone, release);

View file

@ -1,8 +1,6 @@
use std::fs;
use std::path::Path;
use super::verify_inside_clippy_dir;
const VSCODE_DIR: &str = ".vscode";
const TASK_SOURCE_FILE: &str = "util/etc/vscode-tasks.json";
const TASK_TARGET_FILE: &str = ".vscode/tasks.json";
@ -22,10 +20,6 @@ pub fn install_tasks(force_override: bool) {
}
fn check_install_precondition(force_override: bool) -> bool {
if !verify_inside_clippy_dir() {
return false;
}
let vs_dir_path = Path::new(VSCODE_DIR);
if vs_dir_path.exists() {
// verify the target will be valid

View file

@ -8,15 +8,10 @@ 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, Command, ExitStatus, Stdio};
use std::process::{self, Command, Stdio};
use std::{env, thread};
use walkdir::WalkDir;
#[cfg(not(windows))]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
#[cfg(windows)]
static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe";
#[derive(Clone, Copy)]
pub enum ErrAction {
Open,
@ -118,16 +113,14 @@ impl<'a> File<'a> {
}
}
/// Returns the path to the `cargo-clippy` binary
///
/// # Panics
///
/// Panics if the path of current executable could not be retrieved.
/// Creates a `Command` for running cargo.
#[must_use]
pub fn cargo_clippy_path() -> PathBuf {
let mut path = env::current_exe().expect("failed to get current executable name");
path.set_file_name(CARGO_CLIPPY_EXE);
path
pub fn cargo_cmd() -> Command {
if let Some(path) = env::var_os("CARGO") {
Command::new(path)
} else {
Command::new("cargo")
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@ -288,19 +281,6 @@ impl ClippyInfo {
}
}
/// # Panics
/// Panics if given command result was failed.
pub fn exit_if_err(status: io::Result<ExitStatus>) {
match status.expect("failed to run command").code() {
Some(0) => {},
Some(n) => process::exit(n),
None => {
eprintln!("Killed by signal");
process::exit(1);
},
}
}
#[derive(Clone, Copy)]
pub enum UpdateStatus {
Unchanged,
@ -341,6 +321,7 @@ pub struct FileUpdater {
dst_buf: String,
}
impl FileUpdater {
#[track_caller]
fn update_file_checked_inner(
&mut self,
tool: &str,
@ -364,6 +345,7 @@ impl FileUpdater {
}
}
#[track_caller]
fn update_file_inner(&mut self, path: &Path, update: &mut dyn FnMut(&Path, &str, &mut String) -> UpdateStatus) {
let mut file = File::open(path, OpenOptions::new().read(true).write(true));
file.read_to_cleared_string(&mut self.src_buf);
@ -373,6 +355,7 @@ impl FileUpdater {
}
}
#[track_caller]
pub fn update_file_checked(
&mut self,
tool: &str,
@ -383,6 +366,7 @@ impl FileUpdater {
self.update_file_checked_inner(tool, mode, path.as_ref(), update);
}
#[track_caller]
pub fn update_file(
&mut self,
path: impl AsRef<Path>,
@ -450,7 +434,6 @@ pub enum Token<'a> {
OpenParen,
Pound,
Semi,
Slash,
}
pub struct RustSearcher<'txt> {
@ -528,7 +511,6 @@ impl<'txt> RustSearcher<'txt> {
| (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 {
@ -601,7 +583,7 @@ impl<'txt> RustSearcher<'txt> {
}
}
#[expect(clippy::must_use_candidate)]
#[track_caller]
pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
match OpenOptions::new().create_new(true).write(true).open(new_name) {
Ok(file) => drop(file),
@ -623,7 +605,7 @@ pub fn try_rename_file(old_name: &Path, new_name: &Path) -> bool {
}
}
#[expect(clippy::must_use_candidate)]
#[track_caller]
pub fn try_rename_dir(old_name: &Path, new_name: &Path) -> bool {
match fs::create_dir(new_name) {
Ok(()) => {},
@ -649,10 +631,19 @@ pub fn try_rename_dir(old_name: &Path, new_name: &Path) -> bool {
}
}
pub fn write_file(path: &Path, contents: &str) {
expect_action(fs::write(path, contents), ErrAction::Write, path);
#[track_caller]
pub fn run_exit_on_err(path: &(impl AsRef<Path> + ?Sized), cmd: &mut Command) {
match expect_action(cmd.status(), ErrAction::Run, path.as_ref()).code() {
Some(0) => {},
Some(n) => process::exit(n),
None => {
eprintln!("{} killed by signal", path.as_ref().display());
process::exit(1);
},
}
}
#[track_caller]
#[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> {
@ -738,7 +729,7 @@ pub fn split_args_for_threads(
}
}
#[expect(clippy::must_use_candidate)]
#[track_caller]
pub fn delete_file_if_exists(path: &Path) -> bool {
match fs::remove_file(path) {
Ok(()) => true,
@ -747,6 +738,7 @@ pub fn delete_file_if_exists(path: &Path) -> bool {
}
}
#[track_caller]
pub fn delete_dir_if_exists(path: &Path) {
match fs::remove_dir_all(path) {
Ok(()) => {},

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::Msrv;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::{get_parent_expr, is_expr_temporary_value, is_lint_allowed, msrvs, std_or_core};
use clippy_utils::{get_parent_expr, is_expr_temporary_value, is_from_proc_macro, is_lint_allowed, msrvs, std_or_core};
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind};
use rustc_lint::LateContext;
@ -22,13 +22,12 @@ pub(super) fn check<'tcx>(
&& !matches!(target.ty.kind, TyKind::TraitObject(..))
&& let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = cast_expr.kind
&& !is_lint_allowed(cx, BORROW_AS_PTR, expr.hir_id)
// Fix #9884
&& !is_expr_temporary_value(cx, e)
&& !is_from_proc_macro(cx, expr)
{
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, e.span, cast_expr.span.ctxt(), "..", &mut app).0;
// Fix #9884
if is_expr_temporary_value(cx, e) {
return false;
}
let (suggestion, span) = if msrv.meets(cx, msrvs::RAW_REF_OP) {
// Make sure that the span to be replaced doesn't include parentheses, that could break the

View file

@ -4,18 +4,17 @@ use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, UintTy};
use rustc_middle::ty::{self, Ty, UintTy};
use super::CHAR_LIT_AS_U8;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Cast(e, _) = &expr.kind
&& let ExprKind::Lit(l) = &e.kind
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from_expr: &Expr<'_>, cast_to: Ty<'_>) {
if let ExprKind::Lit(l) = &cast_from_expr.kind
&& let LitKind::Char(c) = l.node
&& ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(expr).kind()
&& ty::Uint(UintTy::U8) == *cast_to.kind()
{
let mut applicability = Applicability::MachineApplicable;
let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability);
let snippet = snippet_with_applicability(cx, cast_from_expr.span, "'x'", &mut applicability);
span_lint_and_then(
cx,

View file

@ -871,6 +871,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
if !expr.span.from_expansion() && unnecessary_cast::check(cx, expr, cast_from_expr, cast_from, cast_to) {
return;
}
char_lit_as_u8::check(cx, expr, cast_from_expr, cast_to);
cast_slice_from_raw_parts::check(cx, expr, cast_from_expr, cast_to, self.msrv);
ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to);
@ -911,7 +912,6 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
borrow_as_ptr::check_implicit_cast(cx, expr);
}
cast_ptr_alignment::check(cx, expr);
char_lit_as_u8::check(cx, expr);
ptr_as_ptr::check(cx, expr, self.msrv);
cast_slice_different_sizes::check(cx, expr, self.msrv);
ptr_cast_constness::check_null_ptr_cast_method(cx, expr);

View file

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_from_proc_macro;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
@ -25,7 +26,7 @@ impl OmitFollowedCastReason<'_> {
}
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) {
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) {
if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind
&& let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr))
&& let ty::RawPtr(_, from_mutbl) = cast_from.kind()
@ -36,6 +37,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) {
// as explained here: https://github.com/rust-lang/rust/issues/60602.
&& to_pointee_ty.is_sized(cx.tcx, cx.typing_env())
&& msrv.meets(cx, msrvs::POINTER_CAST)
&& !is_from_proc_macro(cx, expr)
{
let mut app = Applicability::MachineApplicable;
let turbofish = match &cast_to_hir_ty.kind {

View file

@ -56,7 +56,7 @@ impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]);
impl CognitiveComplexity {
fn check<'tcx>(
&mut self,
&self,
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx FnDecl<'_>,

View file

@ -5,7 +5,7 @@ use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block_w
use clippy_utils::{span_contains_non_whitespace, tokenize_with_text};
use rustc_ast::BinOpKind;
use rustc_errors::Applicability;
use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind};
use rustc_hir::{Block, Expr, ExprKind, StmtKind};
use rustc_lexer::TokenKind;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
@ -141,11 +141,7 @@ impl CollapsibleIf {
// Prevent "elseif"
// Check that the "else" is followed by whitespace
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
!c.is_whitespace()
} else {
false
};
let requires_space = snippet(cx, up_to_else, "..").ends_with(|c: char| !c.is_whitespace());
let mut applicability = Applicability::MachineApplicable;
diag.span_suggestion(
else_block.span,
@ -173,8 +169,7 @@ impl CollapsibleIf {
&& cx.tcx.hir_attrs(inner.hir_id).is_empty()
&& let ExprKind::If(check_inner, _, None) = &inner.kind
&& self.eligible_condition(cx, check_inner)
&& let ctxt = expr.span.ctxt()
&& inner.span.ctxt() == ctxt
&& expr.span.eq_ctxt(inner.span)
&& !block_starts_with_significant_tokens(cx, then, inner, self.lint_commented_code)
{
span_lint_and_then(
@ -262,14 +257,9 @@ fn block_starts_with_significant_tokens(
/// If `block` is a block with either one expression or a statement containing an expression,
/// return the expression. We don't peel blocks recursively, as extra blocks might be intentional.
fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
match block.stmts {
[] => block.expr,
[
Stmt {
kind: StmtKind::Semi(expr),
..
},
] if block.expr.is_none() => Some(expr),
match (block.stmts, block.expr) {
([], expr) => expr,
([stmt], None) if let StmtKind::Semi(expr) = stmt.kind => Some(expr),
_ => None,
}
}

View file

@ -187,6 +187,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::from_raw_with_void_ptr::FROM_RAW_WITH_VOID_PTR_INFO,
crate::from_str_radix_10::FROM_STR_RADIX_10_INFO,
crate::functions::DOUBLE_MUST_USE_INFO,
crate::functions::DUPLICATE_UNDERSCORE_ARGUMENT_INFO,
crate::functions::IMPL_TRAIT_IN_PARAMS_INFO,
crate::functions::MISNAMED_GETTERS_INFO,
crate::functions::MUST_USE_CANDIDATE_INFO,
@ -505,7 +506,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::misc::USED_UNDERSCORE_BINDING_INFO,
crate::misc::USED_UNDERSCORE_ITEMS_INFO,
crate::misc_early::BUILTIN_TYPE_SHADOW_INFO,
crate::misc_early::DUPLICATE_UNDERSCORE_ARGUMENT_INFO,
crate::misc_early::MIXED_CASE_HEX_LITERALS_INFO,
crate::misc_early::REDUNDANT_AT_REST_PATTERN_INFO,
crate::misc_early::REDUNDANT_PATTERN_INFO,

View file

@ -1,12 +1,11 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::{implements_trait, is_manually_drop};
use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop};
use clippy_utils::{
DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local,
peel_middle_ty_refs,
};
use core::mem;
use rustc_ast::util::parser::ExprPrecedence;
use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::Applicability;
@ -707,14 +706,6 @@ fn try_parse_ref_op<'tcx>(
))
}
// Checks if the adjustments contains a deref of `ManuallyDrop<_>`
fn adjust_derefs_manually_drop<'tcx>(adjustments: &'tcx [Adjustment<'tcx>], mut ty: Ty<'tcx>) -> bool {
adjustments.iter().any(|a| {
let ty = mem::replace(&mut ty, a.target);
matches!(a.kind, Adjust::Deref(Some(ref op)) if op.mutbl == Mutability::Mut) && is_manually_drop(ty)
})
}
// Checks whether the type for a deref call actually changed the type, not just the mutability of
// the reference.
fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {

View file

@ -1139,12 +1139,12 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
None,
"a backtick may be missing a pair",
);
text_to_check.clear();
} else {
for (text, range, assoc_code_level) in text_to_check {
for (text, range, assoc_code_level) in text_to_check.drain(..) {
markdown::check(cx, valid_idents, &text, &fragments, range, assoc_code_level, blockquote_level);
}
}
text_to_check = Vec::new();
},
Start(FootnoteDefinition(..)) => in_footnote_definition = true,
End(TagEnd::FootnoteDefinition) => in_footnote_definition = false,

View file

@ -231,9 +231,13 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx
_ => (),
}
}
let replace_with = match callee_ty_adjusted.kind() {
ty::FnDef(def, _) => cx.tcx.def_descr(*def),
_ => "function",
};
diag.span_suggestion(
expr.span,
"replace the closure with the function itself",
format!("replace the closure with the {replace_with} itself"),
snippet,
Applicability::MachineApplicable,
);

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::ty::is_type_lang_item;
use clippy_utils::{is_in_const_context, is_integer_literal, sym};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, PrimTy, QPath, TyKind, def};
@ -89,5 +89,5 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
/// Checks if a Ty is `String` or `&str`
fn is_ty_stringish(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
is_type_lang_item(cx, ty, LangItem::String) || is_type_diagnostic_item(cx, ty, sym::str)
is_type_lang_item(cx, ty, LangItem::String) || ty.peel_refs().is_str()
}

View file

@ -0,0 +1,34 @@
use clippy_utils::diagnostics::span_lint;
use rustc_ast::PatKind;
use rustc_ast::visit::FnKind;
use rustc_data_structures::fx::FxHashMap;
use rustc_lint::EarlyContext;
use rustc_span::Span;
use super::DUPLICATE_UNDERSCORE_ARGUMENT;
pub(super) fn check(cx: &EarlyContext<'_>, fn_kind: FnKind<'_>) {
let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();
for arg in &fn_kind.decl().inputs {
if let PatKind::Ident(_, ident, None) = arg.pat.kind {
let arg_name = ident.to_string();
if let Some(arg_name) = arg_name.strip_prefix('_') {
if let Some(correspondence) = registered_names.get(arg_name) {
span_lint(
cx,
DUPLICATE_UNDERSCORE_ARGUMENT,
*correspondence,
format!(
"`{arg_name}` already exists, having another argument having almost the same \
name makes code comprehension and documentation more difficult"
),
);
}
} else {
registered_names.insert(arg_name, arg.pat.span);
}
}
}
}

View file

@ -1,3 +1,4 @@
mod duplicate_underscore_argument;
mod impl_trait_in_params;
mod misnamed_getters;
mod must_use;
@ -11,14 +12,38 @@ mod too_many_lines;
use clippy_config::Conf;
use clippy_utils::msrvs::Msrv;
use clippy_utils::paths::{PathNS, lookup_path_str};
use rustc_ast::{self as ast, visit};
use rustc_hir as hir;
use rustc_hir::intravisit;
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass;
use rustc_session::{declare_lint_pass, impl_lint_pass};
use rustc_span::Span;
use rustc_span::def_id::{DefIdSet, LocalDefId};
declare_clippy_lint! {
/// ### What it does
/// Checks for function arguments having the similar names
/// differing by an underscore.
///
/// ### Why is this bad?
/// It affects code readability.
///
/// ### Example
/// ```no_run
/// fn foo(a: i32, _a: i32) {}
/// ```
///
/// Use instead:
/// ```no_run
/// fn bar(a: i32, _b: i32) {}
/// ```
#[clippy::version = "pre 1.29.0"]
pub DUPLICATE_UNDERSCORE_ARGUMENT,
style,
"function arguments having names which only differ by an underscore"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for functions with too many parameters.
@ -448,6 +473,14 @@ declare_clippy_lint! {
"function signature uses `&Option<T>` instead of `Option<&T>`"
}
declare_lint_pass!(EarlyFunctions => [DUPLICATE_UNDERSCORE_ARGUMENT]);
impl EarlyLintPass for EarlyFunctions {
fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: visit::FnKind<'_>, _: Span, _: ast::NodeId) {
duplicate_underscore_argument::check(cx, fn_kind);
}
}
pub struct Functions {
too_many_arguments_threshold: u64,
too_many_lines_threshold: u64,
@ -503,7 +536,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
) {
let hir_id = cx.tcx.local_def_id_to_hir_id(def_id);
too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold);
too_many_lines::check_fn(cx, kind, span, body, self.too_many_lines_threshold);
too_many_lines::check_fn(cx, kind, body, span, def_id, self.too_many_lines_threshold);
not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, def_id);
misnamed_getters::check_fn(cx, kind, decl, body, span);
impl_trait_in_params::check_fn(cx, &kind, body, hir_id);

View file

@ -6,6 +6,7 @@ use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind, Node, TraitRef};
use rustc_lint::LateContext;
use rustc_span::Span;
use rustc_span::symbol::{Ident, kw};
use std::iter;
use super::RENAMED_FUNCTION_PARAMS;
@ -58,16 +59,11 @@ impl RenamedFnArgs {
let mut renamed: Vec<(Span, String)> = vec![];
debug_assert!(default_idents.size_hint() == current_idents.size_hint());
while let (Some(default_ident), Some(current_ident)) = (default_idents.next(), current_idents.next()) {
for (default_ident, current_ident) in iter::zip(default_idents, current_idents) {
let has_name_to_check = |ident: Option<Ident>| {
if let Some(ident) = ident
&& ident.name != kw::Underscore
&& !ident.name.as_str().starts_with('_')
{
Some(ident)
} else {
None
}
ident
.filter(|ident| ident.name != kw::Underscore)
.filter(|ident| !ident.name.as_str().starts_with('_'))
};
if let Some(default_ident) = has_name_to_check(default_ident)
@ -97,8 +93,7 @@ fn trait_item_def_id_of_impl(cx: &LateContext<'_>, target: OwnerId) -> Option<De
}
fn is_from_ignored_trait(of_trait: &TraitRef<'_>, ignored_traits: &DefIdSet) -> bool {
let Some(trait_did) = of_trait.trait_def_id() else {
return false;
};
ignored_traits.contains(&trait_did)
of_trait
.trait_def_id()
.is_some_and(|trait_did| ignored_traits.contains(&trait_did))
}

View file

@ -97,11 +97,7 @@ fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: S
fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty_span: Span, large_err_threshold: u64) {
if let ty::Adt(adt, subst) = err_ty.kind()
&& let Some(local_def_id) = err_ty
.ty_adt_def()
.expect("already checked this is adt")
.did()
.as_local()
&& let Some(local_def_id) = adt.did().as_local()
&& let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(local_def_id)
&& let hir::ItemKind::Enum(_, _, ref def) = item.kind
{

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::source::SpanRangeExt;
use rustc_hir as hir;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::FnKind;
use rustc_lint::{LateContext, LintContext};
use rustc_span::Span;
@ -10,8 +11,9 @@ use super::TOO_MANY_LINES;
pub(super) fn check_fn(
cx: &LateContext<'_>,
kind: FnKind<'_>,
span: Span,
body: &hir::Body<'_>,
span: Span,
def_id: LocalDefId,
too_many_lines_threshold: u64,
) {
// Closures must be contained in a parent body, which will be checked for `too_many_lines`.
@ -74,7 +76,7 @@ pub(super) fn check_fn(
span_lint(
cx,
TOO_MANY_LINES,
span,
cx.tcx.def_span(def_id),
format!("this function has too many lines ({line_count}/{too_many_lines_threshold})"),
);
}

View file

@ -176,12 +176,11 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
if let ExprKind::Let(lt) = expr.kind
&& match lt.pat.kind {
PatKind::Slice([], None, []) => true,
PatKind::Expr(lit) => match lit.kind {
PatExprKind::Lit { lit, .. } => match lit.node {
LitKind::Str(lit, _) => lit.as_str().is_empty(),
_ => false,
},
_ => false,
PatKind::Expr(lit)
if let PatExprKind::Lit { lit, .. } = lit.kind
&& let LitKind::Str(lit, _) = lit.node =>
{
lit.as_str().is_empty()
},
_ => false,
}
@ -336,33 +335,23 @@ fn extract_future_output<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&
}
fn is_first_generic_integral<'tcx>(segment: &'tcx PathSegment<'tcx>) -> bool {
if let Some(generic_args) = segment.args {
if generic_args.args.is_empty() {
return false;
}
let arg = &generic_args.args[0];
if let GenericArg::Type(rustc_hir::Ty {
kind: TyKind::Path(QPath::Resolved(_, path)),
..
}) = arg
{
let segments = &path.segments;
let segment = &segments[0];
let res = &segment.res;
if matches!(res, Res::PrimTy(PrimTy::Uint(_))) || matches!(res, Res::PrimTy(PrimTy::Int(_))) {
return true;
}
}
if let Some(generic_args) = segment.args
&& let [GenericArg::Type(ty), ..] = &generic_args.args
&& let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
&& let [segment, ..] = &path.segments
&& matches!(segment.res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_)))
{
true
} else {
false
}
false
}
fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<LenOutput> {
if let Some(segment) = extract_future_output(cx, sig.output()) {
let res = segment.res;
if matches!(res, Res::PrimTy(PrimTy::Uint(_))) || matches!(res, Res::PrimTy(PrimTy::Int(_))) {
if matches!(res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_))) {
return Some(LenOutput::Integral);
}

View file

@ -556,6 +556,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
store.register_late_pass(|_| Box::new(panicking_overflow_checks::PanickingOverflowChecks));
store.register_late_pass(|_| Box::<new_without_default::NewWithoutDefault>::default());
store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(conf)));
store.register_early_pass(|| Box::new(functions::EarlyFunctions));
store.register_late_pass(move |tcx| Box::new(functions::Functions::new(tcx, conf)));
store.register_late_pass(move |_| Box::new(doc::Documentation::new(conf)));
store.register_early_pass(move || Box::new(doc::Documentation::new(conf)));
@ -600,7 +601,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(conf)));
store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain));
store.register_late_pass(move |tcx| Box::new(mut_key::MutableKeyType::new(tcx, conf)));
store.register_early_pass(|| Box::new(reference::DerefAddrOf));
store.register_late_pass(|_| Box::new(reference::DerefAddrOf));
store.register_early_pass(|| Box::new(double_parens::DoubleParens));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| Box::new(format_impl::FormatImpl::new(format_args.clone())));

View file

@ -1,10 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed};
use hir::intravisit::{Visitor, walk_expr};
use hir::{Expr, ExprKind, FnRetTy, FnSig, Node, TyKind};
use rustc_ast::Label;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{
self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Expr, ExprKind, FnRetTy, FnSig, Node, TyKind,
};
use rustc_lint::{LateContext, LintContext};
use rustc_span::sym;
@ -29,6 +30,10 @@ pub(super) fn check<'tcx>(
return;
}
if is_inside_unawaited_async_block(cx, expr) {
return;
}
if expr.span.in_external_macro(cx.sess().source_map()) || is_from_proc_macro(cx, expr) {
return;
}
@ -60,6 +65,39 @@ pub(super) fn check<'tcx>(
}
}
/// Check if the given expression is inside an async block that is not being awaited.
/// This helps avoid false positives when async blocks are spawned or assigned to variables.
fn is_inside_unawaited_async_block(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let current_hir_id = expr.hir_id;
for (_, parent_node) in cx.tcx.hir_parent_iter(current_hir_id) {
if let Node::Expr(Expr {
kind:
ExprKind::Closure(Closure {
kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),
..
}),
..
}) = parent_node
{
return !is_async_block_awaited(cx, expr);
}
}
false
}
fn is_async_block_awaited(cx: &LateContext<'_>, async_expr: &Expr<'_>) -> bool {
for (_, parent_node) in cx.tcx.hir_parent_iter(async_expr.hir_id) {
if let Node::Expr(Expr {
kind: ExprKind::Match(_, _, hir::MatchSource::AwaitDesugar),
..
}) = parent_node
{
return true;
}
}
false
}
fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<FnRetTy<'tcx>> {
for (_, parent_node) in cx.tcx.hir_parent_iter(expr.hir_id) {
match parent_node {
@ -67,8 +105,8 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
// This is because we still need to backtrack one parent node to get the `OpaqueDef` ty.
Node::Expr(Expr {
kind:
ExprKind::Closure(hir::Closure {
kind: hir::ClosureKind::Coroutine(_),
ExprKind::Closure(Closure {
kind: ClosureKind::Coroutine(_),
..
}),
..
@ -90,7 +128,7 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
..
})
| Node::Expr(Expr {
kind: ExprKind::Closure(hir::Closure { fn_decl: decl, .. }),
kind: ExprKind::Closure(Closure { fn_decl: decl, .. }),
..
}) => return Some(decl.output),
_ => (),

View file

@ -49,7 +49,7 @@ declare_clippy_lint! {
}
impl<'tcx> QuestionMark {
pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) {
pub(crate) fn check_manual_let_else(&self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) {
if let StmtKind::Let(local) = stmt.kind
&& let Some(init) = local.init
&& local.els.is_none()

View file

@ -12,7 +12,11 @@ use super::MATCH_BOOL;
pub(crate) fn check(cx: &LateContext<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
// Type of expression is `bool`.
if *cx.typeck_results().expr_ty(scrutinee).kind() == ty::Bool {
if *cx.typeck_results().expr_ty(scrutinee).kind() == ty::Bool
&& arms
.iter()
.all(|arm| arm.pat.walk_short(|p| !matches!(p.kind, PatKind::Binding(..))))
{
span_lint_and_then(
cx,
MATCH_BOOL,

View file

@ -17,6 +17,11 @@ where
return;
}
// `!` cannot be deref-ed
if cx.typeck_results().expr_ty(scrutinee).is_never() {
return;
}
let (first_sugg, msg, title);
let ctxt = expr.span.ctxt();
let mut app = Applicability::Unspecified;

View file

@ -54,7 +54,7 @@ impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> {
}
impl MatchExprVisitor<'_, '_> {
fn case_altered(&mut self, segment_ident: Symbol, receiver: &Expr<'_>) -> ControlFlow<CaseMethod> {
fn case_altered(&self, segment_ident: Symbol, receiver: &Expr<'_>) -> ControlFlow<CaseMethod> {
if let Some(case_method) = get_case_method(segment_ident) {
let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs();

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::{has_non_owning_mutable_access, implements_trait};
use clippy_utils::{is_mutable, is_trait_method, path_to_local, sym};
use clippy_utils::{is_mutable, is_trait_method, path_to_local_with_projections, sym};
use rustc_errors::Applicability;
use rustc_hir::{Expr, Node, PatKind};
use rustc_lint::LateContext;
@ -37,7 +37,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Exp
// TODO: Change this to lint only when the referred iterator is not used later. If it is used later,
// changing to `next_back()` may change its behavior.
if !(is_mutable(cx, self_expr) || self_type.is_ref()) {
if let Some(hir_id) = path_to_local(self_expr)
if let Some(hir_id) = path_to_local_with_projections(self_expr)
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
&& let PatKind::Binding(_, _, ident, _) = pat.kind
{

View file

@ -15,7 +15,6 @@ use std::ops::ControlFlow;
use super::EXPECT_FUN_CALL;
/// Checks for the `EXPECT_FUN_CALL` lint.
#[allow(clippy::too_many_lines)]
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
format_args_storage: &FormatArgsStorage,
@ -25,43 +24,6 @@ pub(super) fn check<'tcx>(
receiver: &'tcx hir::Expr<'tcx>,
args: &'tcx [hir::Expr<'tcx>],
) {
// Strip `{}`, `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
// `&str`
fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
let mut arg_root = peel_blocks(arg);
loop {
arg_root = match &arg_root.kind {
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
hir::ExprKind::MethodCall(method_name, receiver, [], ..) => {
if (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref) && {
let arg_type = cx.typeck_results().expr_ty(receiver);
let base_type = arg_type.peel_refs();
base_type.is_str() || is_type_lang_item(cx, base_type, hir::LangItem::String)
} {
receiver
} else {
break;
}
},
_ => break,
};
}
arg_root
}
fn contains_call<'a>(cx: &LateContext<'a>, arg: &'a hir::Expr<'a>) -> bool {
for_each_expr(cx, arg, |expr| {
if matches!(expr.kind, hir::ExprKind::MethodCall { .. } | hir::ExprKind::Call { .. })
&& !is_inside_always_const_context(cx.tcx, expr.hir_id)
{
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
})
.is_some()
}
if name == sym::expect
&& let [arg] = args
&& let arg_root = get_arg_root(cx, arg)
@ -114,3 +76,40 @@ pub(super) fn check<'tcx>(
);
}
}
/// Strip `{}`, `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
/// `&str`
fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
let mut arg_root = peel_blocks(arg);
loop {
arg_root = match &arg_root.kind {
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
hir::ExprKind::MethodCall(method_name, receiver, [], ..) => {
if (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref) && {
let arg_type = cx.typeck_results().expr_ty(receiver);
let base_type = arg_type.peel_refs();
base_type.is_str() || is_type_lang_item(cx, base_type, hir::LangItem::String)
} {
receiver
} else {
break;
}
},
_ => break,
};
}
arg_root
}
fn contains_call<'a>(cx: &LateContext<'a>, arg: &'a hir::Expr<'a>) -> bool {
for_each_expr(cx, arg, |expr| {
if matches!(expr.kind, hir::ExprKind::MethodCall { .. } | hir::ExprKind::Call { .. })
&& !is_inside_always_const_context(cx.tcx, expr.hir_id)
{
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
})
.is_some()
}

View file

@ -106,7 +106,7 @@ enum CheckResult<'tcx> {
impl<'tcx> OffendingFilterExpr<'tcx> {
pub fn check_map_call(
&mut self,
&self,
cx: &LateContext<'tcx>,
map_body: &'tcx Body<'tcx>,
map_param_id: HirId,
@ -413,7 +413,7 @@ fn is_find_or_filter<'a>(
}
&& let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind
&& let Some(mut offending_expr) = OffendingFilterExpr::hir(cx, filter_body.value, filter_param_id)
&& let Some(offending_expr) = OffendingFilterExpr::hir(cx, filter_body.value, filter_param_id)
&& let ExprKind::Closure(&Closure { body: map_body_id, .. }) = map_arg.kind
&& let map_body = cx.tcx.hir_body(map_body_id)

View file

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::path_to_local_with_projections;
use clippy_utils::source::snippet;
use clippy_utils::ty::implements_trait;
use rustc_ast::{BindingMode, Mutability};
@ -9,21 +10,6 @@ use rustc_span::sym;
use super::FILTER_NEXT;
fn path_to_local(expr: &hir::Expr<'_>) -> Option<hir::HirId> {
match expr.kind {
hir::ExprKind::Field(f, _) => path_to_local(f),
hir::ExprKind::Index(recv, _, _) => path_to_local(recv),
hir::ExprKind::Path(hir::QPath::Resolved(
_,
hir::Path {
res: rustc_hir::def::Res::Local(local),
..
},
)) => Some(*local),
_ => None,
}
}
/// lint use of `filter().next()` for `Iterators`
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
@ -44,7 +30,7 @@ pub(super) fn check<'tcx>(
let iter_snippet = snippet(cx, recv.span, "..");
// add note if not multi-line
span_lint_and_then(cx, FILTER_NEXT, expr.span, msg, |diag| {
let (applicability, pat) = if let Some(id) = path_to_local(recv)
let (applicability, pat) = if let Some(id) = path_to_local_with_projections(recv)
&& let hir::Node::Pat(pat) = cx.tcx.hir_node(id)
&& let hir::PatKind::Binding(BindingMode(_, Mutability::Not), _, ident, _) = pat.kind
{

View file

@ -7,12 +7,9 @@ mod unneeded_field_pattern;
mod unneeded_wildcard_pattern;
mod zero_prefixed_literal;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::source::snippet_opt;
use rustc_ast::ast::{Expr, ExprKind, Generics, LitFloatType, LitIntType, LitKind, NodeId, Pat, PatKind};
use rustc_ast::ast::{Expr, ExprKind, Generics, LitFloatType, LitIntType, LitKind, Pat};
use rustc_ast::token;
use rustc_ast::visit::FnKind;
use rustc_data_structures::fx::FxHashMap;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
@ -60,29 +57,6 @@ declare_clippy_lint! {
"struct fields bound to a wildcard instead of using `..`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for function arguments having the similar names
/// differing by an underscore.
///
/// ### Why is this bad?
/// It affects code readability.
///
/// ### Example
/// ```no_run
/// fn foo(a: i32, _a: i32) {}
/// ```
///
/// Use instead:
/// ```no_run
/// fn bar(a: i32, _b: i32) {}
/// ```
#[clippy::version = "pre 1.29.0"]
pub DUPLICATE_UNDERSCORE_ARGUMENT,
style,
"function arguments having names which only differ by an underscore"
}
declare_clippy_lint! {
/// ### What it does
/// Warns on hexadecimal literals with mixed-case letter
@ -330,7 +304,6 @@ declare_clippy_lint! {
declare_lint_pass!(MiscEarlyLints => [
UNNEEDED_FIELD_PATTERN,
DUPLICATE_UNDERSCORE_ARGUMENT,
MIXED_CASE_HEX_LITERALS,
UNSEPARATED_LITERAL_SUFFIX,
SEPARATED_LITERAL_SUFFIX,
@ -359,32 +332,6 @@ impl EarlyLintPass for MiscEarlyLints {
unneeded_wildcard_pattern::check(cx, pat);
}
fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();
for arg in &fn_kind.decl().inputs {
if let PatKind::Ident(_, ident, None) = arg.pat.kind {
let arg_name = ident.to_string();
if let Some(arg_name) = arg_name.strip_prefix('_') {
if let Some(correspondence) = registered_names.get(arg_name) {
span_lint(
cx,
DUPLICATE_UNDERSCORE_ARGUMENT,
*correspondence,
format!(
"`{arg_name}` already exists, having another argument having almost the same \
name makes code comprehension and documentation more difficult"
),
);
}
} else {
registered_names.insert(arg_name, arg.pat.span);
}
}
}
}
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if expr.span.in_external_macro(cx.sess().source_map()) {
return;
@ -404,7 +351,7 @@ impl MiscEarlyLints {
// See <https://github.com/rust-lang/rust-clippy/issues/4507> for a regression.
// FIXME: Find a better way to detect those cases.
let lit_snip = match snippet_opt(cx, span) {
Some(snip) if snip.chars().next().is_some_and(|c| c.is_ascii_digit()) => snip,
Some(snip) if snip.starts_with(|c: char| c.is_ascii_digit()) => snip,
_ => return,
};

View file

@ -134,7 +134,7 @@ impl<'tcx> DivergenceVisitor<'_, 'tcx> {
}
}
fn report_diverging_sub_expr(&mut self, e: &Expr<'_>) {
fn report_diverging_sub_expr(&self, e: &Expr<'_>) {
if let Some(macro_call) = root_macro_call_first_node(self.cx, e)
&& self.cx.tcx.is_diagnostic_item(sym::todo_macro, macro_call.def_id)
{

View file

@ -166,7 +166,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
applicability,
);
};
if let Some((a, b)) = fetch_bool_block(then).and_then(|a| Some((a, fetch_bool_block(else_expr)?))) {
if let Some(a) = fetch_bool_block(then)
&& let Some(b) = fetch_bool_block(else_expr)
{
match (a, b) {
(RetBool(true), RetBool(true)) | (Bool(true), Bool(true)) => {
span_lint(

View file

@ -59,7 +59,7 @@ declare_clippy_lint! {
pub struct NeedlessBorrowsForGenericArgs<'tcx> {
/// Stack of (body owner, `PossibleBorrowerMap`) pairs. Used by
/// `needless_borrow_impl_arg_position` to determine when a borrowed expression can instead
/// [`needless_borrow_count`] to determine when a borrowed expression can instead
/// be moved.
possible_borrowers: Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,

View file

@ -305,11 +305,12 @@ fn check_unnecessary_operation(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
for e in reduced {
if let Some(snip) = e.span.get_source_text(cx) {
snippet.push_str(&snip);
snippet.push(';');
snippet.push_str("; ");
} else {
return;
}
}
snippet.pop(); // remove the last space
span_lint_hir_and_then(
cx,
UNNECESSARY_OPERATION,

View file

@ -92,7 +92,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "pre 1.29.0"]
pub DECLARE_INTERIOR_MUTABLE_CONST,
style,
suspicious,
"declaring `const` with interior mutability"
}

View file

@ -248,6 +248,11 @@ impl SimilarNamesNameVisitor<'_, '_, '_> {
continue;
}
// Skip similarity check if both names are exactly 3 characters
if count == 3 && existing_name.len == 3 {
continue;
}
let dissimilar = match existing_name.len.cmp(&count) {
Ordering::Greater => existing_name.len - count != 1 || levenstein_not_1(interned_name, existing_str),
Ordering::Less => count - existing_name.len != 1 || levenstein_not_1(existing_str, interned_name),

View file

@ -173,7 +173,7 @@ impl Params {
}
/// Sets the `apply_lint` flag on each parameter.
fn flag_for_linting(&mut self) {
fn flag_for_linting(&self) {
// Stores the list of parameters currently being resolved. Needed to avoid cycles.
let mut eval_stack = Vec::new();
for param in &self.params {

View file

@ -325,7 +325,7 @@ impl ArithmeticSideEffects {
self.issue_lint(cx, expr);
}
fn should_skip_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> bool {
fn should_skip_expr<'tcx>(&self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> bool {
is_lint_allowed(cx, ARITHMETIC_SIDE_EFFECTS, expr.hir_id)
|| self.expr_span.is_some()
|| self.const_span.is_some_and(|sp| sp.contains(expr.span))

View file

@ -13,7 +13,7 @@ pub struct Context {
const_span: Option<Span>,
}
impl Context {
fn skip_expr(&mut self, e: &hir::Expr<'_>) -> bool {
fn skip_expr(&self, e: &hir::Expr<'_>) -> bool {
self.expr_id.is_some() || self.const_span.is_some_and(|span| span.contains(e.span))
}

View file

@ -120,7 +120,7 @@ impl PassByRefOrValue {
}
}
fn check_poly_fn(&mut self, cx: &LateContext<'_>, def_id: LocalDefId, decl: &FnDecl<'_>, span: Option<Span>) {
fn check_poly_fn(&self, cx: &LateContext<'_>, def_id: LocalDefId, decl: &FnDecl<'_>, span: Option<Span>) {
if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) {
return;
}

View file

@ -237,7 +237,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
.collect();
let results = check_ptr_arg_usage(cx, body, &lint_args);
for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
for (result, args) in iter::zip(&results, &lint_args).filter(|(r, _)| !r.skip) {
span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, args.build_msg(), |diag| {
diag.multipart_suggestion(
"change this to",
@ -386,7 +386,6 @@ impl<'tcx> DerefTy<'tcx> {
}
}
#[expect(clippy::too_many_lines)]
fn check_fn_args<'cx, 'tcx: 'cx>(
cx: &'cx LateContext<'tcx>,
fn_sig: ty::FnSig<'tcx>,
@ -413,13 +412,13 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
Some(sym::Vec) => (
[(sym::clone, ".to_owned()")].as_slice(),
DerefTy::Slice(
name.args.and_then(|args| args.args.first()).and_then(|arg| {
if let GenericArg::Type(ty) = arg {
Some(ty.span)
} else {
None
}
}),
if let Some(name_args) = name.args
&& let [GenericArg::Type(ty), ..] = name_args.args
{
Some(ty.span)
} else {
None
},
args.type_at(0),
),
),
@ -432,33 +431,29 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
DerefTy::Path,
),
Some(sym::Cow) if mutability == Mutability::Not => {
if let Some((lifetime, ty)) = name.args.and_then(|args| {
if let [GenericArg::Lifetime(lifetime), ty] = args.args {
return Some((lifetime, ty));
}
None
}) {
if let Some(name_args) = name.args
&& let [GenericArg::Lifetime(lifetime), ty] = name_args.args
{
if let LifetimeKind::Param(param_def_id) = lifetime.kind
&& !lifetime.is_anonymous()
&& fn_sig
.output()
.walk()
.filter_map(|arg| {
arg.as_region().and_then(|lifetime| match lifetime.kind() {
ty::ReEarlyParam(r) => Some(
cx.tcx
.generics_of(cx.tcx.parent(param_def_id.to_def_id()))
.region_param(r, cx.tcx)
.def_id,
),
ty::ReBound(_, r) => r.kind.get_id(),
ty::ReLateParam(r) => r.kind.get_id(),
ty::ReStatic
| ty::ReVar(_)
| ty::RePlaceholder(_)
| ty::ReErased
| ty::ReError(_) => None,
})
.filter_map(ty::GenericArg::as_region)
.filter_map(|lifetime| match lifetime.kind() {
ty::ReEarlyParam(r) => Some(
cx.tcx
.generics_of(cx.tcx.parent(param_def_id.to_def_id()))
.region_param(r, cx.tcx)
.def_id,
),
ty::ReBound(_, r) => r.kind.get_id(),
ty::ReLateParam(r) => r.kind.get_id(),
ty::ReStatic
| ty::ReVar(_)
| ty::RePlaceholder(_)
| ty::ReErased
| ty::ReError(_) => None,
})
.any(|def_id| def_id.as_local().is_some_and(|def_id| def_id == param_def_id))
{
@ -627,12 +622,16 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[
}
}
// If the expression's type gets adjusted down to the deref type, we might as
// well have started with that deref type -- the lint should fire
let deref_ty = args.deref_ty.ty(self.cx);
let adjusted_ty = self.cx.typeck_results().expr_ty_adjusted(e).peel_refs();
if adjusted_ty == deref_ty {
return;
}
// If the expression's type is constrained by `dyn Trait`, see if the deref
// type implements the trait(s) as well, and if so, the lint should fire
if let ty::Dynamic(preds, ..) = adjusted_ty.kind()
&& matches_preds(self.cx, deref_ty, preds)
{

View file

@ -103,15 +103,7 @@ impl EarlyLintPass for RawStrings {
}
impl RawStrings {
fn check_raw_string(
&mut self,
cx: &EarlyContext<'_>,
str: &str,
lit_span: Span,
prefix: &str,
max: u8,
descr: &str,
) {
fn check_raw_string(&self, cx: &EarlyContext<'_>, str: &str, lit_span: Span, prefix: &str, max: u8, descr: &str) {
if !str.contains(['\\', '"']) {
span_lint_and_then(
cx,

View file

@ -88,8 +88,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantPubCrate {
// We ignore macro exports. And `ListStem` uses, which aren't interesting.
fn is_ignorable_export<'tcx>(item: &'tcx Item<'tcx>) -> bool {
if let ItemKind::Use(path, kind) = item.kind {
let ignore = matches!(path.res.macro_ns, Some(Res::Def(DefKind::Macro(_), _)))
|| kind == UseKind::ListStem;
let ignore = matches!(path.res.macro_ns, Some(Res::Def(DefKind::Macro(_), _))) || kind == UseKind::ListStem;
if ignore {
return true;
}

View file

@ -1,10 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{SpanRangeExt, snippet_with_applicability};
use rustc_ast::ast::{Expr, ExprKind, Mutability, UnOp};
use clippy_utils::source::snippet;
use clippy_utils::sugg::{Sugg, has_enclosing_paren};
use clippy_utils::ty::adjust_derefs_manually_drop;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_hir::{Expr, ExprKind, HirId, Node, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::{BytePos, Span};
declare_clippy_lint! {
/// ### What it does
@ -37,17 +38,12 @@ declare_clippy_lint! {
declare_lint_pass!(DerefAddrOf => [DEREF_ADDROF]);
fn without_parens(mut e: &Expr) -> &Expr {
while let ExprKind::Paren(ref child_e) = e.kind {
e = child_e;
}
e
}
impl EarlyLintPass for DerefAddrOf {
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind
&& let ExprKind::AddrOf(_, ref mutability, ref addrof_target) = without_parens(deref_target).kind
impl LateLintPass<'_> for DerefAddrOf {
fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) {
if !e.span.from_expansion()
&& let ExprKind::Unary(UnOp::Deref, deref_target) = e.kind
&& !deref_target.span.from_expansion()
&& let ExprKind::AddrOf(_, _, addrof_target) = deref_target.kind
// NOTE(tesuji): `*&` forces rustc to const-promote the array to `.rodata` section.
// See #12854 for details.
&& !matches!(addrof_target.kind, ExprKind::Array(_))
@ -55,57 +51,82 @@ impl EarlyLintPass for DerefAddrOf {
&& !addrof_target.span.from_expansion()
{
let mut applicability = Applicability::MachineApplicable;
let sugg = if e.span.from_expansion() {
if let Some(macro_source) = e.span.get_source_text(cx) {
// Remove leading whitespace from the given span
// e.g: ` $visitor` turns into `$visitor`
let trim_leading_whitespaces = |span: Span| {
span.get_source_text(cx)
.and_then(|snip| {
#[expect(clippy::cast_possible_truncation)]
snip.find(|c: char| !c.is_whitespace())
.map(|pos| span.lo() + BytePos(pos as u32))
})
.map_or(span, |start_no_whitespace| e.span.with_lo(start_no_whitespace))
};
let mut sugg = || Sugg::hir_with_applicability(cx, addrof_target, "_", &mut applicability);
let mut generate_snippet = |pattern: &str| {
#[expect(clippy::cast_possible_truncation)]
macro_source.rfind(pattern).map(|pattern_pos| {
let rpos = pattern_pos + pattern.len();
let span_after_ref = e.span.with_lo(BytePos(e.span.lo().0 + rpos as u32));
let span = trim_leading_whitespaces(span_after_ref);
snippet_with_applicability(cx, span, "_", &mut applicability)
})
};
if *mutability == Mutability::Mut {
generate_snippet("mut")
} else {
generate_snippet("&")
}
} else {
Some(snippet_with_applicability(cx, e.span, "_", &mut applicability))
}
} else {
Some(snippet_with_applicability(
cx,
addrof_target.span,
"_",
&mut applicability,
))
// If this expression is an explicit `DerefMut` of a `ManuallyDrop` reached through a
// union, we may remove the reference if we are at the point where the implicit
// dereference would take place. Otherwise, we should not lint.
let sugg = match is_manually_drop_through_union(cx, e.hir_id, addrof_target) {
ManuallyDropThroughUnion::Directly => sugg().deref(),
ManuallyDropThroughUnion::Indirect => return,
ManuallyDropThroughUnion::No => sugg(),
};
if let Some(sugg) = sugg {
span_lint_and_sugg(
cx,
DEREF_ADDROF,
e.span,
"immediately dereferencing a reference",
"try",
sugg.to_string(),
applicability,
);
}
let sugg = if has_enclosing_paren(snippet(cx, e.span, "")) {
sugg.maybe_paren()
} else {
sugg
};
span_lint_and_sugg(
cx,
DEREF_ADDROF,
e.span,
"immediately dereferencing a reference",
"try",
sugg.to_string(),
applicability,
);
}
}
}
/// Is this a `ManuallyDrop` reached through a union, and when is `DerefMut` called on it?
enum ManuallyDropThroughUnion {
/// `ManuallyDrop` reached through a union and immediately explicitely dereferenced
Directly,
/// `ManuallyDrop` reached through a union, and dereferenced later on
Indirect,
/// Any other situation
No,
}
/// Check if `addrof_target` is part of an access to a `ManuallyDrop` entity reached through a
/// union, and when it is dereferenced using `DerefMut` starting from `expr_id` and going up.
fn is_manually_drop_through_union(
cx: &LateContext<'_>,
expr_id: HirId,
addrof_target: &Expr<'_>,
) -> ManuallyDropThroughUnion {
if is_reached_through_union(cx, addrof_target) {
let typeck = cx.typeck_results();
for (idx, id) in std::iter::once(expr_id)
.chain(cx.tcx.hir_parent_id_iter(expr_id))
.enumerate()
{
if let Node::Expr(expr) = cx.tcx.hir_node(id) {
if adjust_derefs_manually_drop(typeck.expr_adjustments(expr), typeck.expr_ty(expr)) {
return if idx == 0 {
ManuallyDropThroughUnion::Directly
} else {
ManuallyDropThroughUnion::Indirect
};
}
} else {
break;
}
}
}
ManuallyDropThroughUnion::No
}
/// Checks whether `expr` denotes an object reached through a union
fn is_reached_through_union(cx: &LateContext<'_>, mut expr: &Expr<'_>) -> bool {
while let ExprKind::Field(parent, _) | ExprKind::Index(parent, _, _) = expr.kind {
if cx.typeck_results().expr_ty_adjusted(parent).is_union() {
return true;
}
expr = parent;
}
false
}

View file

@ -380,7 +380,7 @@ impl<'tcx> IndexBinding<'_, 'tcx> {
}
}
fn is_used_other_than_swapping(&mut self, idx_ident: Ident) -> bool {
fn is_used_other_than_swapping(&self, idx_ident: Ident) -> bool {
if Self::is_used_slice_indexed(self.swap1_idx, idx_ident)
|| Self::is_used_slice_indexed(self.swap2_idx, idx_ident)
{
@ -389,7 +389,7 @@ impl<'tcx> IndexBinding<'_, 'tcx> {
self.is_used_after_swap(idx_ident)
}
fn is_used_after_swap(&mut self, idx_ident: Ident) -> bool {
fn is_used_after_swap(&self, idx_ident: Ident) -> bool {
let mut v = IndexBindingVisitor {
idx: idx_ident,
suggest_span: self.suggest_span,

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{eq_expr_value, path_to_local, sym};
use clippy_utils::{eq_expr_value, path_to_local_with_projections, sym};
use rustc_abi::WrappingRange;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Node};
@ -63,11 +63,7 @@ fn binops_with_local(cx: &LateContext<'_>, local_expr: &Expr<'_>, expr: &Expr<'_
/// Checks if an expression is a path to a local variable (with optional projections), e.g.
/// `x.field[0].field2` would return true.
fn is_local_with_projections(expr: &Expr<'_>) -> bool {
match expr.kind {
ExprKind::Path(_) => path_to_local(expr).is_some(),
ExprKind::Field(expr, _) | ExprKind::Index(expr, ..) => is_local_with_projections(expr),
_ => false,
}
path_to_local_with_projections(expr).is_some()
}
pub(super) fn check<'tcx>(

View file

@ -105,7 +105,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "pre 1.29.0"]
pub CROSSPOINTER_TRANSMUTE,
complexity,
suspicious,
"transmutes that have to or from types that are a pointer to the other"
}

View file

@ -202,79 +202,41 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
};
let item_has_safety_comment = item_has_safety_comment(cx, item);
match (&item.kind, item_has_safety_comment) {
// lint unsafe impl without safety comment
(ItemKind::Impl(Impl { of_trait: Some(of_trait), .. }), HasSafetyComment::No) if of_trait.safety.is_unsafe() => {
if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, item.hir_id())
&& !is_unsafe_from_proc_macro(cx, item.span)
{
let source_map = cx.tcx.sess.source_map();
let span = if source_map.is_multiline(item.span) {
source_map.span_until_char(item.span, '\n')
} else {
item.span
};
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
UNDOCUMENTED_UNSAFE_BLOCKS,
span,
"unsafe impl missing a safety comment",
|diag| {
diag.help("consider adding a safety comment on the preceding line");
},
);
}
},
// lint safe impl with unnecessary safety comment
(ItemKind::Impl(Impl { of_trait: Some(of_trait), .. }), HasSafetyComment::Yes(pos)) if of_trait.safety.is_safe() => {
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
let (span, help_span) = mk_spans(pos);
span_lint_and_then(
cx,
UNNECESSARY_SAFETY_COMMENT,
span,
"impl has unnecessary safety comment",
|diag| {
diag.span_help(help_span, "consider removing the safety comment");
},
);
}
},
(ItemKind::Impl(_), _) => {},
// const and static items only need a safety comment if their body is an unsafe block, lint otherwise
(&ItemKind::Const(.., body) | &ItemKind::Static(.., body), HasSafetyComment::Yes(pos)) => {
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, body.hir_id) {
let body = cx.tcx.hir_body(body);
if !matches!(
body.value.kind, hir::ExprKind::Block(block, _)
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
) {
let (span, help_span) = mk_spans(pos);
span_lint_and_then(
cx,
UNNECESSARY_SAFETY_COMMENT,
span,
format!(
"{} has unnecessary safety comment",
cx.tcx.def_descr(item.owner_id.to_def_id()),
),
|diag| {
diag.span_help(help_span, "consider removing the safety comment");
},
);
}
}
},
// Aside from unsafe impls and consts/statics with an unsafe block, items in general
// do not have safety invariants that need to be documented, so lint those.
(_, HasSafetyComment::Yes(pos)) => {
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
let (span, help_span) = mk_spans(pos);
match item_has_safety_comment {
HasSafetyComment::Yes(pos) => check_has_safety_comment(cx, item, mk_spans(pos)),
HasSafetyComment::No => check_has_no_safety_comment(cx, item),
HasSafetyComment::Maybe => {},
}
}
}
fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, help_span): (Span, Span)) {
match &item.kind {
ItemKind::Impl(Impl {
of_trait: Some(of_trait),
..
}) if of_trait.safety.is_safe() => {
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
span_lint_and_then(
cx,
UNNECESSARY_SAFETY_COMMENT,
span,
"impl has unnecessary safety comment",
|diag| {
diag.span_help(help_span, "consider removing the safety comment");
},
);
}
},
ItemKind::Impl(_) => {},
// const and static items only need a safety comment if their body is an unsafe block, lint otherwise
&ItemKind::Const(.., body) | &ItemKind::Static(.., body) => {
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, body.hir_id) {
let body = cx.tcx.hir_body(body);
if !matches!(
body.value.kind, hir::ExprKind::Block(block, _)
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
) {
span_lint_and_then(
cx,
UNNECESSARY_SAFETY_COMMENT,
@ -288,12 +250,56 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
},
);
}
},
_ => (),
}
}
},
// Aside from unsafe impls and consts/statics with an unsafe block, items in general
// do not have safety invariants that need to be documented, so lint those.
_ => {
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
span_lint_and_then(
cx,
UNNECESSARY_SAFETY_COMMENT,
span,
format!(
"{} has unnecessary safety comment",
cx.tcx.def_descr(item.owner_id.to_def_id()),
),
|diag| {
diag.span_help(help_span, "consider removing the safety comment");
},
);
}
},
}
}
fn check_has_no_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) {
if let ItemKind::Impl(Impl {
of_trait: Some(of_trait),
..
}) = item.kind
&& of_trait.safety.is_unsafe()
&& !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, item.hir_id())
&& !is_unsafe_from_proc_macro(cx, item.span)
{
let source_map = cx.tcx.sess.source_map();
let span = if source_map.is_multiline(item.span) {
source_map.span_until_char(item.span, '\n')
} else {
item.span
};
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
UNDOCUMENTED_UNSAFE_BLOCKS,
span,
"unsafe impl missing a safety comment",
|diag| {
diag.help("consider adding a safety comment on the preceding line");
},
);
}
}
fn expr_has_unnecessary_safety_comment<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
@ -505,7 +511,8 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
},
Node::Stmt(stmt) => {
if let Node::Block(block) = cx.tcx.parent_hir_node(stmt.hir_id) {
walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo)
walk_span_to_context(block.span, SyntaxContext::root())
.map(|sp| CommentStartBeforeItem::Offset(sp.lo()))
} else {
// Problem getting the parent node. Pretend a comment was found.
return HasSafetyComment::Maybe;
@ -518,10 +525,12 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
};
let source_map = cx.sess().source_map();
// If the comment is in the first line of the file, there is no preceding line
if let Some(comment_start) = comment_start
&& let Ok(unsafe_line) = source_map.lookup_line(item.span.lo())
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
&& Arc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start.into())
&& let include_first_line_of_file = matches!(comment_start, CommentStartBeforeItem::Start)
&& (include_first_line_of_file || Arc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf))
&& let Some(src) = unsafe_line.sf.src.as_deref()
{
return if comment_start_line.line >= unsafe_line.line {
@ -529,7 +538,8 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
} else {
match text_has_safety_comment(
src,
&unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line],
&unsafe_line.sf.lines()
[(comment_start_line.line + usize::from(!include_first_line_of_file))..=unsafe_line.line],
unsafe_line.sf.start_pos,
) {
Some(b) => HasSafetyComment::Yes(b),
@ -592,12 +602,27 @@ fn stmt_has_safety_comment(
HasSafetyComment::Maybe
}
#[derive(Clone, Copy, Debug)]
enum CommentStartBeforeItem {
Offset(BytePos),
Start,
}
impl From<CommentStartBeforeItem> for BytePos {
fn from(value: CommentStartBeforeItem) -> Self {
match value {
CommentStartBeforeItem::Offset(loc) => loc,
CommentStartBeforeItem::Start => BytePos(0),
}
}
}
fn comment_start_before_item_in_mod(
cx: &LateContext<'_>,
parent_mod: &hir::Mod<'_>,
parent_mod_span: Span,
item: &hir::Item<'_>,
) -> Option<BytePos> {
) -> Option<CommentStartBeforeItem> {
parent_mod.item_ids.iter().enumerate().find_map(|(idx, item_id)| {
if *item_id == item.item_id() {
if idx == 0 {
@ -605,15 +630,18 @@ fn comment_start_before_item_in_mod(
// ^------------------------------------------^ returns the start of this span
// ^---------------------^ finally checks comments in this range
if let Some(sp) = walk_span_to_context(parent_mod_span, SyntaxContext::root()) {
return Some(sp.lo());
return Some(CommentStartBeforeItem::Offset(sp.lo()));
}
} else {
// some_item /* comment */ unsafe impl T {}
// ^-------^ returns the end of this span
// ^---------------^ finally checks comments in this range
let prev_item = cx.tcx.hir_item(parent_mod.item_ids[idx - 1]);
if prev_item.span.is_dummy() {
return Some(CommentStartBeforeItem::Start);
}
if let Some(sp) = walk_span_to_context(prev_item.span, SyntaxContext::root()) {
return Some(sp.hi());
return Some(CommentStartBeforeItem::Offset(sp.hi()));
}
}
}
@ -668,7 +696,7 @@ fn get_body_search_span(cx: &LateContext<'_>) -> Option<Span> {
}) => {
return maybe_mod_item
.and_then(|item| comment_start_before_item_in_mod(cx, mod_, *span, &item))
.map(|comment_start| mod_.spans.inner_span.with_lo(comment_start))
.map(|comment_start| mod_.spans.inner_span.with_lo(comment_start.into()))
.or(Some(*span));
},
node if let Some((span, _)) = span_and_hid_of_item_alike_node(&node)

View file

@ -55,7 +55,7 @@ impl UnnecessaryBoxReturns {
}
}
fn check_fn_item(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId, name: Symbol) {
fn check_fn_item(&self, cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId, name: Symbol) {
// we don't want to tell someone to break an exported function if they ask us not to
if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) {
return;

View file

@ -86,7 +86,9 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessarySemicolon {
expr.kind,
ExprKind::If(..) | ExprKind::Match(_, _, MatchSource::Normal | MatchSource::Postfix)
)
&& cx.typeck_results().expr_ty(expr) == cx.tcx.types.unit
&& cx.typeck_results().expr_ty(expr).is_unit()
// if a stmt has attrs, then turning it into an expr will break the code, since attrs aren't allowed on exprs
&& cx.tcx.hir_attrs(stmt.hir_id).is_empty()
{
if let Some(block_is_unit) = self.is_last_in_block(stmt) {
if cx.tcx.sess.edition() <= Edition2021 && leaks_droppable_temporary_with_limited_lifetime(cx, expr) {

View file

@ -284,14 +284,14 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec<Box<Pat>>, focus_idx:
|k, ps1, idx| matches!(
k,
TupleStruct(qself2, path2, ps2)
if eq_maybe_qself(qself1.as_ref(), qself2.as_ref())
if eq_maybe_qself(qself1.as_deref(), qself2.as_deref())
&& eq_path(path1, path2) && eq_pre_post(ps1, ps2, idx)
),
|k| always_pat!(k, TupleStruct(_, _, ps) => ps),
),
// Transform a record pattern `S { fp_0, ..., fp_n }`.
Struct(qself1, path1, fps1, rest1) => {
extend_with_struct_pat(qself1.as_ref(), path1, fps1, *rest1, start, alternatives)
extend_with_struct_pat(qself1.as_deref(), path1, fps1, *rest1, start, alternatives)
},
};
@ -304,7 +304,7 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec<Box<Pat>>, focus_idx:
/// So when we fixate on some `ident_k: pat_k`, we try to find `ident_k` in the other pattern
/// and check that all `fp_i` where `i ∈ ((0...n) \ k)` between two patterns are equal.
fn extend_with_struct_pat(
qself1: Option<&Box<ast::QSelf>>,
qself1: Option<&ast::QSelf>,
path1: &ast::Path,
fps1: &mut [ast::PatField],
rest1: ast::PatFieldsRest,
@ -319,7 +319,7 @@ fn extend_with_struct_pat(
|k| {
matches!(k, Struct(qself2, path2, fps2, rest2)
if rest1 == *rest2 // If one struct pattern has `..` so must the other.
&& eq_maybe_qself(qself1, qself2.as_ref())
&& eq_maybe_qself(qself1, qself2.as_deref())
&& eq_path(path1, path2)
&& fps1.len() == fps2.len()
&& fps1.iter().enumerate().all(|(idx_1, fp1)| {

View file

@ -141,43 +141,45 @@ fn collect_unwrap_info<'tcx>(
is_type_diagnostic_item(cx, ty, sym::Result) && matches!(method_name, sym::is_err | sym::is_ok)
}
if let ExprKind::Binary(op, left, right) = &expr.kind {
match (invert, op.node) {
(false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr) => {
let mut unwrap_info = collect_unwrap_info(cx, if_expr, left, branch, invert, false);
unwrap_info.append(&mut collect_unwrap_info(cx, if_expr, right, branch, invert, false));
return unwrap_info;
},
_ => (),
}
} else if let ExprKind::Unary(UnOp::Not, expr) = &expr.kind {
return collect_unwrap_info(cx, if_expr, expr, branch, !invert, false);
} else if let ExprKind::MethodCall(method_name, receiver, [], _) = &expr.kind
&& let Some(local_id) = path_to_local(receiver)
&& let ty = cx.typeck_results().expr_ty(receiver)
&& let name = method_name.ident.name
&& (is_relevant_option_call(cx, ty, name) || is_relevant_result_call(cx, ty, name))
{
let unwrappable = matches!(name, sym::is_some | sym::is_ok);
let safe_to_unwrap = unwrappable != invert;
let kind = if is_type_diagnostic_item(cx, ty, sym::Option) {
UnwrappableKind::Option
} else {
UnwrappableKind::Result
};
match expr.kind {
ExprKind::Binary(op, left, right)
if matches!(
(invert, op.node),
(false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr)
) =>
{
let mut unwrap_info = collect_unwrap_info(cx, if_expr, left, branch, invert, false);
unwrap_info.extend(collect_unwrap_info(cx, if_expr, right, branch, invert, false));
unwrap_info
},
ExprKind::Unary(UnOp::Not, expr) => collect_unwrap_info(cx, if_expr, expr, branch, !invert, false),
ExprKind::MethodCall(method_name, receiver, [], _)
if let Some(local_id) = path_to_local(receiver)
&& let ty = cx.typeck_results().expr_ty(receiver)
&& let name = method_name.ident.name
&& (is_relevant_option_call(cx, ty, name) || is_relevant_result_call(cx, ty, name)) =>
{
let unwrappable = matches!(name, sym::is_some | sym::is_ok);
let safe_to_unwrap = unwrappable != invert;
let kind = if is_type_diagnostic_item(cx, ty, sym::Option) {
UnwrappableKind::Option
} else {
UnwrappableKind::Result
};
return vec![UnwrapInfo {
local_id,
if_expr,
check: expr,
check_name: name,
branch,
safe_to_unwrap,
kind,
is_entire_condition,
}];
vec![UnwrapInfo {
local_id,
if_expr,
check: expr,
check_name: name,
branch,
safe_to_unwrap,
kind,
is_entire_condition,
}]
},
_ => vec![],
}
Vec::new()
}
/// A HIR visitor delegate that checks if a local variable of type `Option` or `Result` is mutated,

View file

@ -56,6 +56,9 @@ impl LateLintPass<'_> for ZeroSizedMapValues {
// cannot check if it is `Sized` or not, such as an incomplete associated type in a
// type alias. See an example in `issue14822()` of `tests/ui/zero_sized_hashmap_values.rs`.
&& !ty.has_non_region_param()
// Ensure that no region escapes to avoid an assertion error when computing the layout.
// See an example in `issue15429()` of `tests/ui/zero_sized_hashmap_values.rs`.
&& !ty.has_escaping_bound_vars()
&& let Ok(layout) = cx.layout_of(ty)
&& layout.is_zst()
{

View file

@ -6,7 +6,8 @@ use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::Res;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::{
AttrArgs, AttrItem, AttrPath, Attribute, HirId, Impl, Item, ItemKind, Path, QPath, TraitRef, Ty, TyKind, find_attr,
AttrArgs, AttrItem, AttrPath, Attribute, HirId, Impl, Item, ItemKind, Path, QPath, TraitImplHeader, TraitRef, Ty,
TyKind, find_attr,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint_defs::declare_tool_lint;
@ -56,10 +57,14 @@ impl<'tcx> LateLintPass<'tcx> for DeriveDeserializeAllowingUnknown {
// Is this an `impl` (of a certain form)?
let ItemKind::Impl(Impl {
of_trait:
Some(TraitRef {
path:
Path {
res: Res::Def(_, trait_def_id),
Some(TraitImplHeader {
trait_ref:
TraitRef {
path:
Path {
res: Res::Def(_, trait_def_id),
..
},
..
},
..

View file

@ -377,9 +377,9 @@ dependencies = [
[[package]]
name = "slab"
version = "0.4.10"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]]
name = "smallvec"

View file

@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
<!-- begin autogenerated nightly -->
```
nightly-2025-08-07
nightly-2025-08-22
```
<!-- end autogenerated nightly -->

View file

@ -41,21 +41,23 @@ pub fn eq_pat(l: &Pat, r: &Pat) -> bool {
b1 == b2 && eq_id(*i1, *i2) && both(s1.as_deref(), s2.as_deref(), eq_pat)
},
(Range(lf, lt, le), Range(rf, rt, re)) => {
eq_expr_opt(lf.as_ref(), rf.as_ref())
&& eq_expr_opt(lt.as_ref(), rt.as_ref())
eq_expr_opt(lf.as_deref(), rf.as_deref())
&& eq_expr_opt(lt.as_deref(), rt.as_deref())
&& eq_range_end(&le.node, &re.node)
},
(Box(l), Box(r))
| (Ref(l, Mutability::Not), Ref(r, Mutability::Not))
| (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r),
(Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)),
(Path(lq, lp), Path(rq, rp)) => both(lq.as_ref(), rq.as_ref(), eq_qself) && eq_path(lp, rp),
(Path(lq, lp), Path(rq, rp)) => both(lq.as_deref(), rq.as_deref(), eq_qself) && eq_path(lp, rp),
(TupleStruct(lqself, lp, lfs), TupleStruct(rqself, rp, rfs)) => {
eq_maybe_qself(lqself.as_ref(), rqself.as_ref()) && eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r))
eq_maybe_qself(lqself.as_deref(), rqself.as_deref())
&& eq_path(lp, rp)
&& over(lfs, rfs, |l, r| eq_pat(l, r))
},
(Struct(lqself, lp, lfs, lr), Struct(rqself, rp, rfs, rr)) => {
lr == rr
&& eq_maybe_qself(lqself.as_ref(), rqself.as_ref())
&& eq_maybe_qself(lqself.as_deref(), rqself.as_deref())
&& eq_path(lp, rp)
&& unordered_over(lfs, rfs, eq_field_pat)
},
@ -82,11 +84,11 @@ pub fn eq_field_pat(l: &PatField, r: &PatField) -> bool {
&& over(&l.attrs, &r.attrs, eq_attr)
}
pub fn eq_qself(l: &Box<QSelf>, r: &Box<QSelf>) -> bool {
pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool {
l.position == r.position && eq_ty(&l.ty, &r.ty)
}
pub fn eq_maybe_qself(l: Option<&Box<QSelf>>, r: Option<&Box<QSelf>>) -> bool {
pub fn eq_maybe_qself(l: Option<&QSelf>, r: Option<&QSelf>) -> bool {
match (l, r) {
(Some(l), Some(r)) => eq_qself(l, r),
(None, None) => true,
@ -129,8 +131,8 @@ pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool {
}
}
pub fn eq_expr_opt(l: Option<&Box<Expr>>, r: Option<&Box<Expr>>) -> bool {
both(l, r, |l, r| eq_expr(l, r))
pub fn eq_expr_opt(l: Option<&Expr>, r: Option<&Expr>) -> bool {
both(l, r, eq_expr)
}
pub fn eq_struct_rest(l: &StructRest, r: &StructRest) -> bool {
@ -177,7 +179,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
(Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt),
(Let(lp, le, _, _), Let(rp, re, _, _)) => eq_pat(lp, rp) && eq_expr(le, re),
(If(lc, lt, le), If(rc, rt, re)) => {
eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le.as_ref(), re.as_ref())
eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le.as_deref(), re.as_deref())
},
(While(lc, lt, ll), While(rc, rt, rl)) => {
eq_label(ll.as_ref(), rl.as_ref()) && eq_expr(lc, rc) && eq_block(lt, rt)
@ -201,9 +203,11 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
(Loop(lt, ll, _), Loop(rt, rl, _)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_block(lt, rt),
(Block(lb, ll), Block(rb, rl)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_block(lb, rb),
(TryBlock(l), TryBlock(r)) => eq_block(l, r),
(Yield(l), Yield(r)) => eq_expr_opt(l.expr(), r.expr()) && l.same_kind(r),
(Ret(l), Ret(r)) => eq_expr_opt(l.as_ref(), r.as_ref()),
(Break(ll, le), Break(rl, re)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_expr_opt(le.as_ref(), re.as_ref()),
(Yield(l), Yield(r)) => eq_expr_opt(l.expr().map(Box::as_ref), r.expr().map(Box::as_ref)) && l.same_kind(r),
(Ret(l), Ret(r)) => eq_expr_opt(l.as_deref(), r.as_deref()),
(Break(ll, le), Break(rl, re)) => {
eq_label(ll.as_ref(), rl.as_ref()) && eq_expr_opt(le.as_deref(), re.as_deref())
},
(Continue(ll), Continue(rl)) => eq_label(ll.as_ref(), rl.as_ref()),
(Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2, _), Index(r1, r2, _)) => {
eq_expr(l1, r1) && eq_expr(l2, r2)
@ -240,13 +244,13 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
},
(Gen(lc, lb, lk, _), Gen(rc, rb, rk, _)) => lc == rc && eq_block(lb, rb) && lk == rk,
(Range(lf, lt, ll), Range(rf, rt, rl)) => {
ll == rl && eq_expr_opt(lf.as_ref(), rf.as_ref()) && eq_expr_opt(lt.as_ref(), rt.as_ref())
ll == rl && eq_expr_opt(lf.as_deref(), rf.as_deref()) && eq_expr_opt(lt.as_deref(), rt.as_deref())
},
(AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re),
(Path(lq, lp), Path(rq, rp)) => both(lq.as_ref(), rq.as_ref(), eq_qself) && eq_path(lp, rp),
(Path(lq, lp), Path(rq, rp)) => both(lq.as_deref(), rq.as_deref(), eq_qself) && eq_path(lp, rp),
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
(Struct(lse), Struct(rse)) => {
eq_maybe_qself(lse.qself.as_ref(), rse.qself.as_ref())
eq_maybe_qself(lse.qself.as_deref(), rse.qself.as_deref())
&& eq_path(&lse.path, &rse.path)
&& eq_struct_rest(&lse.rest, &rse.rest)
&& unordered_over(&lse.fields, &rse.fields, eq_field)
@ -278,8 +282,8 @@ pub fn eq_field(l: &ExprField, r: &ExprField) -> bool {
pub fn eq_arm(l: &Arm, r: &Arm) -> bool {
l.is_placeholder == r.is_placeholder
&& eq_pat(&l.pat, &r.pat)
&& eq_expr_opt(l.body.as_ref(), r.body.as_ref())
&& eq_expr_opt(l.guard.as_ref(), r.guard.as_ref())
&& eq_expr_opt(l.body.as_deref(), r.body.as_deref())
&& eq_expr_opt(l.guard.as_deref(), r.guard.as_deref())
&& over(&l.attrs, &r.attrs, eq_attr)
}
@ -324,7 +328,7 @@ pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> b
over(&l.attrs, &r.attrs, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind)
}
#[expect(clippy::similar_names, clippy::too_many_lines)] // Just a big match statement
#[expect(clippy::too_many_lines)] // Just a big match statement
pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
use ItemKind::*;
match (l, r) {
@ -347,7 +351,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
safety: rs,
define_opaque: _,
}),
) => eq_id(*li, *ri) && lm == rm && ls == rs && eq_ty(lt, rt) && eq_expr_opt(le.as_ref(), re.as_ref()),
) => eq_id(*li, *ri) && lm == rm && ls == rs && eq_ty(lt, rt) && eq_expr_opt(le.as_deref(), re.as_deref()),
(
Const(box ConstItem {
defaultness: ld,
@ -370,7 +374,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
&& eq_id(*li, *ri)
&& eq_generics(lg, rg)
&& eq_ty(lt, rt)
&& eq_expr_opt(le.as_ref(), re.as_ref())
&& eq_expr_opt(le.as_deref(), re.as_deref())
},
(
Fn(box ast::Fn {
@ -525,7 +529,7 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
safety: rs,
define_opaque: _,
}),
) => eq_id(*li, *ri) && eq_ty(lt, rt) && lm == rm && eq_expr_opt(le.as_ref(), re.as_ref()) && ls == rs,
) => eq_id(*li, *ri) && eq_ty(lt, rt) && lm == rm && eq_expr_opt(le.as_deref(), re.as_deref()) && ls == rs,
(
Fn(box ast::Fn {
defaultness: ld,
@ -607,7 +611,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
&& eq_id(*li, *ri)
&& eq_generics(lg, rg)
&& eq_ty(lt, rt)
&& eq_expr_opt(le.as_ref(), re.as_ref())
&& eq_expr_opt(le.as_deref(), re.as_deref())
},
(
Fn(box ast::Fn {
@ -723,7 +727,8 @@ pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
pub fn eq_opt_fn_contract(l: &Option<Box<FnContract>>, r: &Option<Box<FnContract>>) -> bool {
match (l, r) {
(Some(l), Some(r)) => {
eq_expr_opt(l.requires.as_ref(), r.requires.as_ref()) && eq_expr_opt(l.ensures.as_ref(), r.ensures.as_ref())
eq_expr_opt(l.requires.as_deref(), r.requires.as_deref())
&& eq_expr_opt(l.ensures.as_deref(), r.ensures.as_deref())
},
(None, None) => true,
(Some(_), None) | (None, Some(_)) => false,
@ -841,7 +846,7 @@ pub fn eq_ty(l: &Ty, r: &Ty) -> bool {
&& eq_fn_decl(&l.decl, &r.decl)
},
(Tup(l), Tup(r)) => over(l, r, |l, r| eq_ty(l, r)),
(Path(lq, lp), Path(rq, rp)) => both(lq.as_ref(), rq.as_ref(), eq_qself) && eq_path(lp, rp),
(Path(lq, lp), Path(rq, rp)) => both(lq.as_deref(), rq.as_deref(), eq_qself) && eq_path(lp, rp),
(TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, eq_generic_bound),
(ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, eq_generic_bound),
(Typeof(l), Typeof(r)) => eq_expr(&l.value, &r.value),

View file

@ -19,8 +19,8 @@ use rustc_ast::token::CommentKind;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, FnRetTy, HirId, Impl,
ImplItem, ImplItemKind, TraitImplHeader, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path,
QPath, Safety, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource,
ImplItem, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, QPath, Safety,
TraitImplHeader, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource,
};
use rustc_lint::{EarlyContext, LateContext, LintContext};
use rustc_middle::ty::TyCtxt;
@ -254,7 +254,10 @@ fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) {
ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")),
ItemKind::Trait(_, _, Safety::Unsafe, ..)
| ItemKind::Impl(Impl {
of_trait: Some(TraitImplHeader { safety: Safety::Unsafe, .. }), ..
of_trait: Some(TraitImplHeader {
safety: Safety::Unsafe, ..
}),
..
}) => (Pat::Str("unsafe"), Pat::Str("}")),
ItemKind::Trait(_, IsAuto::Yes, ..) => (Pat::Str("auto"), Pat::Str("}")),
ItemKind::Trait(..) => (Pat::Str("trait"), Pat::Str("}")),

View file

@ -22,13 +22,14 @@ fn docs_link(diag: &mut Diag<'_, ()>, lint: &'static Lint) {
{
diag.help(format!(
"for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{lint}",
&option_env!("RUST_RELEASE_NUM").map_or_else(
|| "master".to_string(),
|n| {
// extract just major + minor version and ignore patch versions
format!("rust-{}", n.rsplit_once('.').unwrap().1)
}
)
match option_env!("CFG_RELEASE_CHANNEL") {
// Clippy version is 0.1.xx
//
// Always use .0 because we do not generate separate lint doc pages for rust patch releases
Some("stable") => concat!("rust-1.", env!("CARGO_PKG_VERSION_PATCH"), ".0"),
Some("beta") => "beta",
_ => "master",
}
));
}
}

View file

@ -258,7 +258,7 @@ impl HirEqInterExpr<'_, '_, '_> {
})
}
fn should_ignore(&mut self, expr: &Expr<'_>) -> bool {
fn should_ignore(&self, expr: &Expr<'_>) -> bool {
macro_backtrace(expr.span).last().is_some_and(|macro_call| {
matches!(
self.inner.cx.tcx.get_diagnostic_name(macro_call.def_id),

View file

@ -460,6 +460,23 @@ pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool {
path_to_local(expr) == Some(id)
}
/// If the expression is a path to a local (with optional projections),
/// returns the canonical `HirId` of the local.
///
/// For example, `x.field[0].field2` would return the `HirId` of `x`.
pub fn path_to_local_with_projections(expr: &Expr<'_>) -> Option<HirId> {
match expr.kind {
ExprKind::Field(recv, _) | ExprKind::Index(recv, _, _) => path_to_local_with_projections(recv),
ExprKind::Path(QPath::Resolved(
_,
Path {
res: Res::Local(local), ..
},
)) => Some(*local),
_ => None,
}
}
pub trait MaybePath<'hir> {
fn hir_id(&self) -> HirId;
fn qpath_opt(&self) -> Option<&QPath<'hir>>;

View file

@ -189,25 +189,25 @@ impl MsrvStack {
fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt]) -> Option<RustcVersion> {
let mut msrv_attrs = attrs.iter().filter(|attr| attr.path_matches(&[sym::clippy, sym::msrv]));
if let Some(msrv_attr) = msrv_attrs.next() {
if let Some(duplicate) = msrv_attrs.next_back() {
sess.dcx()
.struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times")
.with_span_note(msrv_attr.span(), "first definition found here")
.emit();
}
let msrv_attr = msrv_attrs.next()?;
if let Some(msrv) = msrv_attr.value_str() {
if let Some(version) = parse_version(msrv) {
return Some(version);
}
sess.dcx()
.span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version"));
} else {
sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute");
}
if let Some(duplicate) = msrv_attrs.next_back() {
sess.dcx()
.struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times")
.with_span_note(msrv_attr.span(), "first definition found here")
.emit();
}
None
let Some(msrv) = msrv_attr.value_str() else {
sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute");
return None;
};
let Some(version) = parse_version(msrv) else {
sess.dcx()
.span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version"));
return None;
};
Some(version)
}

View file

@ -18,6 +18,7 @@ use rustc_lint::LateContext;
use rustc_middle::mir::ConstValue;
use rustc_middle::mir::interpret::Scalar;
use rustc_middle::traits::EvaluationResult;
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::layout::ValidityRequirement;
use rustc_middle::ty::{
self, AdtDef, AliasTy, AssocItem, AssocTag, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
@ -31,7 +32,7 @@ use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
use rustc_trait_selection::traits::{Obligation, ObligationCause};
use std::assert_matches::debug_assert_matches;
use std::collections::hash_map::Entry;
use std::iter;
use std::{iter, mem};
use crate::path_res;
use crate::paths::{PathNS, lookup_path_str};
@ -1382,7 +1383,6 @@ pub fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|| matches!(ty.kind(), ty::Adt(adt_def, _) if cx.tcx.is_diagnostic_item(sym::Vec, adt_def.did()))
}
/// Gets the index of a field by name.
pub fn get_field_idx_by_name(ty: Ty<'_>, name: Symbol) -> Option<usize> {
match *ty.kind() {
ty::Adt(def, _) if def.is_union() || def.is_struct() => {
@ -1392,3 +1392,11 @@ pub fn get_field_idx_by_name(ty: Ty<'_>, name: Symbol) -> Option<usize> {
_ => None,
}
}
/// Checks if the adjustments contain a mutable dereference of a `ManuallyDrop<_>`.
pub fn adjust_derefs_manually_drop<'tcx>(adjustments: &'tcx [Adjustment<'tcx>], mut ty: Ty<'tcx>) -> bool {
adjustments.iter().any(|a| {
let ty = mem::replace(&mut ty, a.target);
matches!(a.kind, Adjust::Deref(Some(op)) if op.mutbl == Mutability::Mut) && is_manually_drop(ty)
})
}

View file

@ -1,6 +1,6 @@
[toolchain]
# begin autogenerated nightly
channel = "nightly-2025-08-07"
channel = "nightly-2025-08-22"
# end autogenerated nightly
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
profile = "minimal"

View file

@ -0,0 +1,26 @@
error: module has unnecessary safety comment
--> src/main.rs:2:1
|
2 | mod x {}
| ^^^^^^^^
|
help: consider removing the safety comment
--> src/main.rs:1:1
|
1 | // SAFETY: ...
| ^^^^^^^^^^^^^^
= note: requested on the command line with `-D clippy::unnecessary-safety-comment`
error: module has unnecessary safety comment
--> src/main.rs:5:1
|
5 | mod y {}
| ^^^^^^^^
|
help: consider removing the safety comment
--> src/main.rs:4:1
|
4 | // SAFETY: ...
| ^^^^^^^^^^^^^^
error: could not compile `undocumented_unsafe_blocks` (bin "undocumented_unsafe_blocks") due to 2 previous errors

View file

@ -0,0 +1,12 @@
# Reproducing #14553 requires the `# Safety` comment to be in the first line of
# the file. Since `unnecessary_safety_comment` is not enabled by default, we
# will set it up here.
[package]
name = "undocumented_unsafe_blocks"
edition = "2024"
publish = false
version = "0.1.0"
[lints.clippy]
unnecessary_safety_comment = "deny"

View file

@ -0,0 +1,7 @@
// SAFETY: ...
mod x {}
// SAFETY: ...
mod y {}
fn main() {}

View file

@ -1,12 +1,8 @@
error: this function has too many lines (2/1)
--> tests/ui-toml/functions_maxlines/test.rs:19:1
|
LL | / fn too_many_lines() {
LL | |
LL | | println!("This is bad.");
LL | | println!("This is bad.");
LL | | }
| |_^
LL | fn too_many_lines() {
| ^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::too-many-lines` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::too_many_lines)]`
@ -14,35 +10,20 @@ LL | | }
error: this function has too many lines (4/1)
--> tests/ui-toml/functions_maxlines/test.rs:26:1
|
LL | / async fn async_too_many_lines() {
LL | |
LL | | println!("This is bad.");
LL | | println!("This is bad.");
LL | | }
| |_^
LL | async fn async_too_many_lines() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this function has too many lines (4/1)
--> tests/ui-toml/functions_maxlines/test.rs:33:1
|
LL | / fn closure_too_many_lines() {
LL | |
LL | | let _ = {
LL | | println!("This is bad.");
LL | | println!("This is bad.");
LL | | };
LL | | }
| |_^
LL | fn closure_too_many_lines() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this function has too many lines (2/1)
--> tests/ui-toml/functions_maxlines/test.rs:56:1
|
LL | / fn comment_before_code() {
LL | |
LL | | let _ = "test";
LL | | /* This comment extends to the front of
LL | | the code but this line should still count. */ let _ = 5;
LL | | }
| |_^
LL | fn comment_before_code() {
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 4 previous errors

View file

@ -0,0 +1,38 @@
#![allow(unused)]
#![warn(clippy::as_ptr_cast_mut)]
#![allow(clippy::wrong_self_convention, clippy::unnecessary_cast)]
struct MutPtrWrapper(Vec<u8>);
impl MutPtrWrapper {
fn as_ptr(&mut self) -> *const u8 {
self.0.as_mut_ptr() as *const u8
}
}
struct Covariant<T>(*const T);
impl<T> Covariant<T> {
fn as_ptr(self) -> *const T {
self.0
}
}
fn main() {
let mut string = String::new();
let _ = string.as_mut_ptr();
//~^ as_ptr_cast_mut
let _ = string.as_ptr() as *const i8;
let _ = string.as_mut_ptr();
let _ = string.as_mut_ptr() as *mut u8;
let _ = string.as_mut_ptr() as *const u8;
let nn = std::ptr::NonNull::new(4 as *mut u8).unwrap();
let _ = nn.as_ptr() as *mut u8;
let mut wrap = MutPtrWrapper(Vec::new());
let _ = wrap.as_ptr() as *mut u8;
let mut local = 4;
let ref_with_write_perm = Covariant(std::ptr::addr_of_mut!(local) as *const _);
let _ = ref_with_write_perm.as_ptr() as *mut u8;
}

View file

@ -1,7 +1,6 @@
#![allow(unused)]
#![warn(clippy::as_ptr_cast_mut)]
#![allow(clippy::wrong_self_convention, clippy::unnecessary_cast)]
//@no-rustfix: incorrect suggestion
struct MutPtrWrapper(Vec<u8>);
impl MutPtrWrapper {
@ -22,9 +21,6 @@ fn main() {
let _ = string.as_ptr() as *mut u8;
//~^ as_ptr_cast_mut
let _: *mut i8 = string.as_ptr() as *mut _;
//~^ as_ptr_cast_mut
let _ = string.as_ptr() as *const i8;
let _ = string.as_mut_ptr();
let _ = string.as_mut_ptr() as *mut u8;

View file

@ -1,5 +1,5 @@
error: casting the result of `as_ptr` to *mut u8
--> tests/ui/as_ptr_cast_mut.rs:22:13
--> tests/ui/as_ptr_cast_mut.rs:21:13
|
LL | let _ = string.as_ptr() as *mut u8;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `string.as_mut_ptr()`
@ -7,11 +7,5 @@ LL | let _ = string.as_ptr() as *mut u8;
= note: `-D clippy::as-ptr-cast-mut` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::as_ptr_cast_mut)]`
error: casting the result of `as_ptr` to *mut i8
--> tests/ui/as_ptr_cast_mut.rs:25:22
|
LL | let _: *mut i8 = string.as_ptr() as *mut _;
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `string.as_mut_ptr()`
error: aborting due to 2 previous errors
error: aborting due to 1 previous error

View file

@ -0,0 +1,16 @@
//@no-rustfix
#![allow(unused)]
#![warn(clippy::as_ptr_cast_mut)]
fn main() {
let mut string = String::new();
// the `*mut _` is actually necessary since it does two things at once:
// - changes the mutability (caught by the lint)
// - changes the type
//
// and so replacing this with `as_mut_ptr` removes the second thing,
// resulting in a type mismatch
let _: *mut i8 = string.as_ptr() as *mut _;
//~^ as_ptr_cast_mut
}

View file

@ -0,0 +1,11 @@
error: casting the result of `as_ptr` to *mut i8
--> tests/ui/as_ptr_cast_mut_unfixable.rs:14:22
|
LL | let _: *mut i8 = string.as_ptr() as *mut _;
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `string.as_mut_ptr()`
|
= note: `-D clippy::as-ptr-cast-mut` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::as_ptr_cast_mut)]`
error: aborting due to 1 previous error

View file

@ -1,6 +1,9 @@
//@aux-build:proc_macros.rs
#![warn(clippy::borrow_as_ptr)]
#![allow(clippy::useless_vec)]
extern crate proc_macros;
fn a() -> i32 {
0
}
@ -53,3 +56,12 @@ fn issue_15141() {
// Don't lint cast to dyn trait pointers
let b = &a as *const dyn std::any::Any;
}
fn issue15389() {
proc_macros::with_span! {
span
let var = 0u32;
// Don't lint in proc-macros
let _ = &var as *const u32;
};
}

View file

@ -1,6 +1,9 @@
//@aux-build:proc_macros.rs
#![warn(clippy::borrow_as_ptr)]
#![allow(clippy::useless_vec)]
extern crate proc_macros;
fn a() -> i32 {
0
}
@ -53,3 +56,12 @@ fn issue_15141() {
// Don't lint cast to dyn trait pointers
let b = &a as *const dyn std::any::Any;
}
fn issue15389() {
proc_macros::with_span! {
span
let var = 0u32;
// Don't lint in proc-macros
let _ = &var as *const u32;
};
}

View file

@ -1,5 +1,5 @@
error: borrow as raw pointer
--> tests/ui/borrow_as_ptr.rs:11:14
--> tests/ui/borrow_as_ptr.rs:14:14
|
LL | let _p = &val as *const i32;
| ^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of!(val)`
@ -8,25 +8,25 @@ LL | let _p = &val as *const i32;
= help: to override `-D warnings` add `#[allow(clippy::borrow_as_ptr)]`
error: borrow as raw pointer
--> tests/ui/borrow_as_ptr.rs:19:18
--> tests/ui/borrow_as_ptr.rs:22:18
|
LL | let _p_mut = &mut val_mut as *mut i32;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of_mut!(val_mut)`
error: borrow as raw pointer
--> tests/ui/borrow_as_ptr.rs:23:16
--> tests/ui/borrow_as_ptr.rs:26:16
|
LL | let _raw = (&mut x[1] as *mut i32).wrapping_offset(-1);
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of_mut!(x[1])`
error: borrow as raw pointer
--> tests/ui/borrow_as_ptr.rs:29:17
--> tests/ui/borrow_as_ptr.rs:32:17
|
LL | let _raw = (&mut x[1] as *mut i32).wrapping_offset(-1);
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `&raw mut x[1]`
error: implicit borrow as raw pointer
--> tests/ui/borrow_as_ptr.rs:35:25
--> tests/ui/borrow_as_ptr.rs:38:25
|
LL | let p: *const i32 = &val;
| ^^^^
@ -37,7 +37,7 @@ LL | let p: *const i32 = &raw const val;
| +++++++++
error: implicit borrow as raw pointer
--> tests/ui/borrow_as_ptr.rs:39:23
--> tests/ui/borrow_as_ptr.rs:42:23
|
LL | let p: *mut i32 = &mut val;
| ^^^^^^^^
@ -48,7 +48,7 @@ LL | let p: *mut i32 = &raw mut val;
| +++
error: implicit borrow as raw pointer
--> tests/ui/borrow_as_ptr.rs:44:19
--> tests/ui/borrow_as_ptr.rs:47:19
|
LL | core::ptr::eq(&val, &1);
| ^^^^

View file

@ -1,7 +1,12 @@
#![warn(clippy::char_lit_as_u8)]
fn main() {
// no suggestion, since a byte literal won't work.
let _ = '❤' as u8;
let _ = 'a' as u8;
//~^ char_lit_as_u8
let _ = '\n' as u8;
//~^ char_lit_as_u8
let _ = '\0' as u8;
//~^ char_lit_as_u8
let _ = '\x01' as u8;
//~^ char_lit_as_u8
}

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