Merge commit 'c936595d17' into clippy-subtree-update
This commit is contained in:
parent
33232af2e4
commit
c71f7b63f8
234 changed files with 4520 additions and 2785 deletions
85
CHANGELOG.md
85
CHANGELOG.md
|
|
@ -8,6 +8,89 @@ document.
|
|||
|
||||
[e9b7045...master](https://github.com/rust-lang/rust-clippy/compare/e9b7045...master)
|
||||
|
||||
## Rust 1.91
|
||||
|
||||
Current stable, released 2025-10-30
|
||||
|
||||
[View all 146 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-07-25T21%3A05%3A11Z..2025-09-04T22%3A34%3A27Z+base%3Amaster)
|
||||
|
||||
### New Lints
|
||||
|
||||
* Added [`possible_missing_else`] to `suspicious`
|
||||
[#15317](https://github.com/rust-lang/rust-clippy/pull/15317)
|
||||
|
||||
### Moves and Deprecations
|
||||
|
||||
* Moved [`cognitive_complexity`] from `nursery` to `restriction`
|
||||
[#15415](https://github.com/rust-lang/rust-clippy/pull/15415)
|
||||
* Moved [`declare_interior_mutable_const`] from `style` to `suspicious`
|
||||
[#15454](https://github.com/rust-lang/rust-clippy/pull/15454)
|
||||
* Moved [`crosspointer_transmute`] from `complexity` to `suspicious`
|
||||
[#15403](https://github.com/rust-lang/rust-clippy/pull/15403)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [`excessive_precision`] added `const_literal_digits_threshold` option to suppress overly precise constants.
|
||||
[#15193](https://github.com/rust-lang/rust-clippy/pull/15193)
|
||||
* [`unwrap_in_result`] rewritten for better accuracy; now lints on `.unwrap()` and `.expect()`
|
||||
directly and no longer mixes `Result` and `Option`.
|
||||
[#15445](https://github.com/rust-lang/rust-clippy/pull/15445)
|
||||
* [`panic`] now works in `const` contexts.
|
||||
[#15565](https://github.com/rust-lang/rust-clippy/pull/15565)
|
||||
* [`implicit_clone`] now also lints `to_string` calls (merging [`string_to_string`] behavior).
|
||||
[#14177](https://github.com/rust-lang/rust-clippy/pull/14177)
|
||||
* [`collapsible_match`] improved suggestions to handle necessary ref/dereferencing.
|
||||
[#14221](https://github.com/rust-lang/rust-clippy/pull/14221)
|
||||
* [`map_identity`] now suggests making variables mutable when required; recognizes tuple struct restructuring.
|
||||
[#15261](https://github.com/rust-lang/rust-clippy/pull/15261)
|
||||
* [`option_map_unit_fn`] preserves `unsafe` blocks in suggestions.
|
||||
[#15570](https://github.com/rust-lang/rust-clippy/pull/15570)
|
||||
* [`unnecessary_mut_passed`] provides structured, clearer fix suggestions.
|
||||
[#15438](https://github.com/rust-lang/rust-clippy/pull/15438)
|
||||
* [`float_equality_without_abs`] now checks `f16` and `f128` types.
|
||||
[#15054](https://github.com/rust-lang/rust-clippy/pull/15054)
|
||||
* [`doc_markdown`] expanded whitelist (`InfiniBand`, `RoCE`, `PowerPC`) and improved handling of
|
||||
identifiers like NixOS.
|
||||
[#15558](https://github.com/rust-lang/rust-clippy/pull/15558)
|
||||
* [`clone_on_ref_ptr`] now suggests fully qualified paths to avoid resolution errors.
|
||||
[#15561](https://github.com/rust-lang/rust-clippy/pull/15561)
|
||||
* [`manual_assert`] simplifies boolean expressions in suggested fixes.
|
||||
[#15368](https://github.com/rust-lang/rust-clippy/pull/15368)
|
||||
* [`four_forward_slashes`] warns about bare CR in comments and avoids invalid autofixes.
|
||||
[#15175](https://github.com/rust-lang/rust-clippy/pull/15175)
|
||||
|
||||
### False Positive Fixes
|
||||
|
||||
* [`alloc_instead_of_core`] fixed FP when `alloc` is an alias
|
||||
[#15581](https://github.com/rust-lang/rust-clippy/pull/15581)
|
||||
* [`needless_range_loop`] fixed FP and FN when meeting multidimensional array
|
||||
[#15486](https://github.com/rust-lang/rust-clippy/pull/15486)
|
||||
* [`semicolon_inside_block`] fixed FP when attribute over expr is not enabled
|
||||
[#15476](https://github.com/rust-lang/rust-clippy/pull/15476)
|
||||
* [`unnested_or_patterns`] fixed FP on structs with only shorthand field patterns
|
||||
[#15343](https://github.com/rust-lang/rust-clippy/pull/15343)
|
||||
* [`match_ref_pats`] fixed FP on match scrutinee of never type
|
||||
[#15474](https://github.com/rust-lang/rust-clippy/pull/15474)
|
||||
* [`infinite_loop`] fixed FP in async blocks that are not awaited
|
||||
[#15157](https://github.com/rust-lang/rust-clippy/pull/15157)
|
||||
* [`iter_on_single_items`] fixed FP on function pointers and let statements
|
||||
[#15013](https://github.com/rust-lang/rust-clippy/pull/15013)
|
||||
|
||||
### ICE Fixes
|
||||
|
||||
* [`len_zero`] fix ICE when fn len has a return type without generic type params
|
||||
[#15660](https://github.com/rust-lang/rust-clippy/pull/15660)
|
||||
|
||||
### Documentation Improvements
|
||||
|
||||
* [`cognitive_complexity`] corrected documentation to state lint is in `restriction`, not `nursery`
|
||||
[#15563](https://github.com/rust-lang/rust-clippy/pull/15563)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* [`doc_broken_link`] optimized by 99.77% (12M → 27k instructions)
|
||||
[#15385](https://github.com/rust-lang/rust-clippy/pull/15385)
|
||||
|
||||
## Rust 1.90
|
||||
|
||||
Current stable, released 2025-09-18
|
||||
|
|
@ -6253,6 +6336,7 @@ Released 2018-09-13
|
|||
[`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop
|
||||
[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum
|
||||
[`empty_enum_variants_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum_variants_with_brackets
|
||||
[`empty_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enums
|
||||
[`empty_line_after_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_doc_comments
|
||||
[`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr
|
||||
[`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop
|
||||
|
|
@ -6583,6 +6667,7 @@ Released 2018-09-13
|
|||
[`needless_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_else
|
||||
[`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
|
||||
[`needless_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_if
|
||||
[`needless_ifs`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_ifs
|
||||
[`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
|
||||
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
|
||||
[`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# The Rust Code of Conduct
|
||||
|
||||
The Code of Conduct for this repository [can be found online](https://www.rust-lang.org/conduct.html).
|
||||
The Code of Conduct for this repository [can be found online](https://rust-lang.org/policies/code-of-conduct/).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy"
|
||||
version = "0.1.92"
|
||||
version = "0.1.93"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
[book]
|
||||
authors = ["The Rust Clippy Developers"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Clippy Documentation"
|
||||
|
||||
[rust]
|
||||
|
|
|
|||
|
|
@ -21,14 +21,13 @@ use clippy_utils::sym;
|
|||
impl<'tcx> LateLintPass<'tcx> for OurFancyMethodLint {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
|
||||
// Check our expr is calling a method with pattern matching
|
||||
if let hir::ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind
|
||||
if let hir::ExprKind::MethodCall(path, _, _, _) = &expr.kind
|
||||
// Check if the name of this method is `our_fancy_method`
|
||||
&& path.ident.name == sym::our_fancy_method
|
||||
// We can check the type of the self argument whenever necessary.
|
||||
// (It's necessary if we want to check that method is specifically belonging to a specific trait,
|
||||
// for example, a `map` method could belong to user-defined trait instead of to `Iterator`)
|
||||
// Check if the method belongs to the `sym::OurFancyTrait` trait.
|
||||
// (for example, a `map` method could belong to user-defined trait instead of to `Iterator`)
|
||||
// See the next section for more information.
|
||||
&& cx.ty_based_def(self_arg).opt_parent(cx).is_diag_item(cx, sym::OurFancyTrait)
|
||||
&& cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::OurFancyTrait)
|
||||
{
|
||||
println!("`expr` is a method call for `our_fancy_method`");
|
||||
}
|
||||
|
|
@ -45,6 +44,12 @@ New symbols such as `our_fancy_method` need to be added to the `clippy_utils::sy
|
|||
This module extends the list of symbols already provided by the compiler crates
|
||||
in `rustc_span::sym`.
|
||||
|
||||
If a trait defines only one method (such as the `std::ops::Deref` trait, which only has the `deref()` method),
|
||||
one might be tempted to omit the method name check. This would work, but is not always advisable because:
|
||||
- If a new method (possibly with a default implementation) were to be added to the trait, there would be a risk of
|
||||
matching the wrong method.
|
||||
- Comparing symbols is very cheap and might prevent a more expensive lookup.
|
||||
|
||||
## Checking if a `impl` block implements a method
|
||||
|
||||
While sometimes we want to check whether a method is being called or not, other
|
||||
|
|
|
|||
|
|
@ -859,6 +859,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
|
|||
* [`io_other_error`](https://rust-lang.github.io/rust-clippy/master/index.html#io_other_error)
|
||||
* [`iter_kv_map`](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map)
|
||||
* [`legacy_numeric_constants`](https://rust-lang.github.io/rust-clippy/master/index.html#legacy_numeric_constants)
|
||||
* [`len_zero`](https://rust-lang.github.io/rust-clippy/master/index.html#len_zero)
|
||||
* [`lines_filter_map_ok`](https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok)
|
||||
* [`manual_abs_diff`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_abs_diff)
|
||||
* [`manual_bits`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_config"
|
||||
version = "0.1.92"
|
||||
version = "0.1.93"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
|
|
|
|||
|
|
@ -748,6 +748,7 @@ define_Conf! {
|
|||
io_other_error,
|
||||
iter_kv_map,
|
||||
legacy_numeric_constants,
|
||||
len_zero,
|
||||
lines_filter_map_ok,
|
||||
manual_abs_diff,
|
||||
manual_bits,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::update_lints::{DeprecatedLint, Lint, find_lint_decls, generate_lint_files, read_deprecated_lints};
|
||||
use crate::parse::{DeprecatedLint, Lint, ParseCx};
|
||||
use crate::update_lints::generate_lint_files;
|
||||
use crate::utils::{UpdateMode, Version};
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -13,21 +14,20 @@ use std::{fs, io};
|
|||
/// # Panics
|
||||
///
|
||||
/// If a file path could not read from or written to
|
||||
pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
|
||||
if let Some((prefix, _)) = name.split_once("::") {
|
||||
panic!("`{name}` should not contain the `{prefix}` prefix");
|
||||
}
|
||||
|
||||
let mut lints = find_lint_decls();
|
||||
let (mut deprecated_lints, renamed_lints) = read_deprecated_lints();
|
||||
pub fn deprecate<'cx>(cx: ParseCx<'cx>, clippy_version: Version, name: &'cx str, reason: &'cx str) {
|
||||
let mut lints = cx.find_lint_decls();
|
||||
let (mut deprecated_lints, renamed_lints) = cx.read_deprecated_lints();
|
||||
|
||||
let Some(lint) = lints.iter().find(|l| l.name == name) else {
|
||||
eprintln!("error: failed to find lint `{name}`");
|
||||
return;
|
||||
};
|
||||
|
||||
let prefixed_name = String::from_iter(["clippy::", name]);
|
||||
match deprecated_lints.binary_search_by(|x| x.name.cmp(&prefixed_name)) {
|
||||
let prefixed_name = cx.str_buf.with(|buf| {
|
||||
buf.extend(["clippy::", name]);
|
||||
cx.arena.alloc_str(buf)
|
||||
});
|
||||
match deprecated_lints.binary_search_by(|x| x.name.cmp(prefixed_name)) {
|
||||
Ok(_) => {
|
||||
println!("`{name}` is already deprecated");
|
||||
return;
|
||||
|
|
@ -36,8 +36,8 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
|
|||
idx,
|
||||
DeprecatedLint {
|
||||
name: prefixed_name,
|
||||
reason: reason.into(),
|
||||
version: clippy_version.rust_display().to_string(),
|
||||
reason,
|
||||
version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()),
|
||||
},
|
||||
),
|
||||
}
|
||||
|
|
@ -61,8 +61,8 @@ pub fn deprecate(clippy_version: Version, name: &str, reason: &str) {
|
|||
}
|
||||
}
|
||||
|
||||
fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io::Result<bool> {
|
||||
fn remove_lint(name: &str, lints: &mut Vec<Lint>) {
|
||||
fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint<'_>>) -> io::Result<bool> {
|
||||
fn remove_lint(name: &str, lints: &mut Vec<Lint<'_>>) {
|
||||
lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos));
|
||||
}
|
||||
|
||||
|
|
@ -135,14 +135,14 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io
|
|||
);
|
||||
|
||||
assert!(
|
||||
content[lint.declaration_range.clone()].contains(&name.to_uppercase()),
|
||||
content[lint.declaration_range].contains(&name.to_uppercase()),
|
||||
"error: `{}` does not contain lint `{}`'s declaration",
|
||||
path.display(),
|
||||
lint.name
|
||||
);
|
||||
|
||||
// Remove lint declaration (declare_clippy_lint!)
|
||||
content.replace_range(lint.declaration_range.clone(), "");
|
||||
content.replace_range(lint.declaration_range, "");
|
||||
|
||||
// Remove the module declaration (mod xyz;)
|
||||
let mod_decl = format!("\nmod {name};");
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ fn run_rustfmt(update_mode: UpdateMode) {
|
|||
.expect("invalid rustfmt path");
|
||||
rustfmt_path.truncate(rustfmt_path.trim_end().len());
|
||||
|
||||
let args: Vec<_> = walk_dir_no_dot_or_target()
|
||||
let args: Vec<_> = walk_dir_no_dot_or_target(".")
|
||||
.filter_map(|e| {
|
||||
let e = expect_action(e, ErrAction::Read, ".");
|
||||
e.path()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
#![feature(
|
||||
rustc_private,
|
||||
exit_status_error,
|
||||
if_let_guard,
|
||||
new_range,
|
||||
new_range_api,
|
||||
os_str_slice,
|
||||
os_string_truncate,
|
||||
pattern,
|
||||
rustc_private,
|
||||
slice_split_once
|
||||
)]
|
||||
#![warn(
|
||||
|
|
@ -15,6 +18,7 @@
|
|||
)]
|
||||
#![allow(clippy::missing_panics_doc)]
|
||||
|
||||
extern crate rustc_arena;
|
||||
#[expect(unused_extern_crates, reason = "required to link to rustc crates")]
|
||||
extern crate rustc_driver;
|
||||
extern crate rustc_lexer;
|
||||
|
|
@ -32,5 +36,8 @@ pub mod setup;
|
|||
pub mod sync;
|
||||
pub mod update_lints;
|
||||
|
||||
mod parse;
|
||||
mod utils;
|
||||
pub use utils::{ClippyInfo, UpdateMode};
|
||||
|
||||
pub use self::parse::{ParseCx, new_parse_cx};
|
||||
pub use self::utils::{ClippyInfo, UpdateMode};
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@
|
|||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use clippy_dev::{
|
||||
ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, release, rename_lint, serve, setup, sync,
|
||||
update_lints,
|
||||
ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, new_parse_cx, release, rename_lint, serve,
|
||||
setup, sync, update_lints,
|
||||
};
|
||||
use std::convert::Infallible;
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
|
|
@ -28,7 +27,7 @@ fn main() {
|
|||
allow_no_vcs,
|
||||
} => dogfood::dogfood(fix, allow_dirty, allow_staged, allow_no_vcs),
|
||||
DevCommand::Fmt { check } => fmt::run(UpdateMode::from_check(check)),
|
||||
DevCommand::UpdateLints { check } => update_lints::update(UpdateMode::from_check(check)),
|
||||
DevCommand::UpdateLints { check } => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::from_check(check))),
|
||||
DevCommand::NewLint {
|
||||
pass,
|
||||
name,
|
||||
|
|
@ -36,7 +35,7 @@ fn main() {
|
|||
r#type,
|
||||
msrv,
|
||||
} => match new_lint::create(clippy.version, pass, &name, &category, r#type.as_deref(), msrv) {
|
||||
Ok(()) => update_lints::update(UpdateMode::Change),
|
||||
Ok(()) => new_parse_cx(|cx| update_lints::update(cx, UpdateMode::Change)),
|
||||
Err(e) => eprintln!("Unable to create lint: {e}"),
|
||||
},
|
||||
DevCommand::Setup(SetupCommand { subcommand }) => match subcommand {
|
||||
|
|
@ -79,13 +78,18 @@ fn main() {
|
|||
old_name,
|
||||
new_name,
|
||||
uplift,
|
||||
} => rename_lint::rename(
|
||||
clippy.version,
|
||||
&old_name,
|
||||
new_name.as_ref().unwrap_or(&old_name),
|
||||
uplift,
|
||||
),
|
||||
DevCommand::Deprecate { name, reason } => deprecate_lint::deprecate(clippy.version, &name, &reason),
|
||||
} => new_parse_cx(|cx| {
|
||||
rename_lint::rename(
|
||||
cx,
|
||||
clippy.version,
|
||||
&old_name,
|
||||
new_name.as_ref().unwrap_or(&old_name),
|
||||
uplift,
|
||||
);
|
||||
}),
|
||||
DevCommand::Deprecate { name, reason } => {
|
||||
new_parse_cx(|cx| deprecate_lint::deprecate(cx, clippy.version, &name, &reason));
|
||||
},
|
||||
DevCommand::Sync(SyncCommand { subcommand }) => match subcommand {
|
||||
SyncSubcommand::UpdateNightly => sync::update_nightly(),
|
||||
},
|
||||
|
|
@ -95,6 +99,20 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
fn lint_name(name: &str) -> Result<String, String> {
|
||||
let name = name.replace('-', "_");
|
||||
if let Some((pre, _)) = name.split_once("::") {
|
||||
Err(format!("lint name should not contain the `{pre}` prefix"))
|
||||
} else if name
|
||||
.bytes()
|
||||
.any(|x| !matches!(x, b'_' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z'))
|
||||
{
|
||||
Err("lint name contains invalid characters".to_owned())
|
||||
} else {
|
||||
Ok(name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "dev", about)]
|
||||
struct Dev {
|
||||
|
|
@ -150,7 +168,7 @@ enum DevCommand {
|
|||
#[arg(
|
||||
short,
|
||||
long,
|
||||
value_parser = |name: &str| Ok::<_, Infallible>(name.replace('-', "_")),
|
||||
value_parser = lint_name,
|
||||
)]
|
||||
/// Name of the new lint in snake case, ex: `fn_too_long`
|
||||
name: String,
|
||||
|
|
@ -223,8 +241,12 @@ enum DevCommand {
|
|||
/// Rename a lint
|
||||
RenameLint {
|
||||
/// The name of the lint to rename
|
||||
#[arg(value_parser = lint_name)]
|
||||
old_name: String,
|
||||
#[arg(required_unless_present = "uplift")]
|
||||
#[arg(
|
||||
required_unless_present = "uplift",
|
||||
value_parser = lint_name,
|
||||
)]
|
||||
/// The new name of the lint
|
||||
new_name: Option<String>,
|
||||
#[arg(long)]
|
||||
|
|
@ -234,6 +256,7 @@ enum DevCommand {
|
|||
/// Deprecate the given lint
|
||||
Deprecate {
|
||||
/// The name of the lint to deprecate
|
||||
#[arg(value_parser = lint_name)]
|
||||
name: String,
|
||||
#[arg(long, short)]
|
||||
/// The reason for deprecation
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::utils::{RustSearcher, Token, Version};
|
||||
use crate::parse::cursor::{self, Capture, Cursor};
|
||||
use crate::utils::Version;
|
||||
use clap::ValueEnum;
|
||||
use indoc::{formatdoc, writedoc};
|
||||
use std::fmt::{self, Write as _};
|
||||
|
|
@ -516,22 +517,22 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str>
|
|||
// Find both the last lint declaration (declare_clippy_lint!) and the lint pass impl
|
||||
fn parse_mod_file(path: &Path, contents: &str) -> (&'static str, usize) {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use Token::*;
|
||||
use cursor::Pat::*;
|
||||
|
||||
let mut context = None;
|
||||
let mut decl_end = None;
|
||||
let mut searcher = RustSearcher::new(contents);
|
||||
while let Some(name) = searcher.find_capture_token(CaptureIdent) {
|
||||
match name {
|
||||
let mut cursor = Cursor::new(contents);
|
||||
let mut captures = [Capture::EMPTY];
|
||||
while let Some(name) = cursor.find_any_ident() {
|
||||
match cursor.get_text(name) {
|
||||
"declare_clippy_lint" => {
|
||||
if searcher.match_tokens(&[Bang, OpenBrace], &mut []) && searcher.find_token(CloseBrace) {
|
||||
decl_end = Some(searcher.pos());
|
||||
if cursor.match_all(&[Bang, OpenBrace], &mut []) && cursor.find_pat(CloseBrace) {
|
||||
decl_end = Some(cursor.pos());
|
||||
}
|
||||
},
|
||||
"impl" => {
|
||||
let mut capture = "";
|
||||
if searcher.match_tokens(&[Lt, Lifetime, Gt, CaptureIdent], &mut [&mut capture]) {
|
||||
match capture {
|
||||
if cursor.match_all(&[Lt, Lifetime, Gt, CaptureIdent], &mut captures) {
|
||||
match cursor.get_text(captures[0]) {
|
||||
"LateLintPass" => context = Some("LateContext"),
|
||||
"EarlyLintPass" => context = Some("EarlyContext"),
|
||||
_ => {},
|
||||
|
|
|
|||
285
clippy_dev/src/parse.rs
Normal file
285
clippy_dev/src/parse.rs
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
pub mod cursor;
|
||||
|
||||
use self::cursor::{Capture, Cursor};
|
||||
use crate::utils::{ErrAction, File, Scoped, expect_action, walk_dir_no_dot_or_target};
|
||||
use core::fmt::{Display, Write as _};
|
||||
use core::range::Range;
|
||||
use rustc_arena::DroplessArena;
|
||||
use std::fs;
|
||||
use std::path::{self, Path, PathBuf};
|
||||
use std::str::pattern::Pattern;
|
||||
|
||||
pub struct ParseCxImpl<'cx> {
|
||||
pub arena: &'cx DroplessArena,
|
||||
pub str_buf: StrBuf,
|
||||
}
|
||||
pub type ParseCx<'cx> = &'cx mut ParseCxImpl<'cx>;
|
||||
|
||||
/// Calls the given function inside a newly created parsing context.
|
||||
pub fn new_parse_cx<'env, T>(f: impl for<'cx> FnOnce(&'cx mut Scoped<'cx, 'env, ParseCxImpl<'cx>>) -> T) -> T {
|
||||
let arena = DroplessArena::default();
|
||||
f(&mut Scoped::new(ParseCxImpl {
|
||||
arena: &arena,
|
||||
str_buf: StrBuf::with_capacity(128),
|
||||
}))
|
||||
}
|
||||
|
||||
/// A string used as a temporary buffer used to avoid allocating for short lived strings.
|
||||
pub struct StrBuf(String);
|
||||
impl StrBuf {
|
||||
/// Creates a new buffer with the specified initial capacity.
|
||||
pub fn with_capacity(cap: usize) -> Self {
|
||||
Self(String::with_capacity(cap))
|
||||
}
|
||||
|
||||
/// Allocates the result of formatting the given value onto the arena.
|
||||
pub fn alloc_display<'cx>(&mut self, arena: &'cx DroplessArena, value: impl Display) -> &'cx str {
|
||||
self.0.clear();
|
||||
write!(self.0, "{value}").expect("`Display` impl returned an error");
|
||||
arena.alloc_str(&self.0)
|
||||
}
|
||||
|
||||
/// Allocates the string onto the arena with all ascii characters converted to
|
||||
/// lowercase.
|
||||
pub fn alloc_ascii_lower<'cx>(&mut self, arena: &'cx DroplessArena, s: &str) -> &'cx str {
|
||||
self.0.clear();
|
||||
self.0.push_str(s);
|
||||
self.0.make_ascii_lowercase();
|
||||
arena.alloc_str(&self.0)
|
||||
}
|
||||
|
||||
/// Allocates the result of replacing all instances the pattern with the given string
|
||||
/// onto the arena.
|
||||
pub fn alloc_replaced<'cx>(
|
||||
&mut self,
|
||||
arena: &'cx DroplessArena,
|
||||
s: &str,
|
||||
pat: impl Pattern,
|
||||
replacement: &str,
|
||||
) -> &'cx str {
|
||||
let mut parts = s.split(pat);
|
||||
let Some(first) = parts.next() else {
|
||||
return "";
|
||||
};
|
||||
self.0.clear();
|
||||
self.0.push_str(first);
|
||||
for part in parts {
|
||||
self.0.push_str(replacement);
|
||||
self.0.push_str(part);
|
||||
}
|
||||
if self.0.is_empty() {
|
||||
""
|
||||
} else {
|
||||
arena.alloc_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs an operation with the freshly cleared buffer.
|
||||
pub fn with<T>(&mut self, f: impl FnOnce(&mut String) -> T) -> T {
|
||||
self.0.clear();
|
||||
f(&mut self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Lint<'cx> {
|
||||
pub name: &'cx str,
|
||||
pub group: &'cx str,
|
||||
pub module: &'cx str,
|
||||
pub path: PathBuf,
|
||||
pub declaration_range: Range<usize>,
|
||||
}
|
||||
|
||||
pub struct DeprecatedLint<'cx> {
|
||||
pub name: &'cx str,
|
||||
pub reason: &'cx str,
|
||||
pub version: &'cx str,
|
||||
}
|
||||
|
||||
pub struct RenamedLint<'cx> {
|
||||
pub old_name: &'cx str,
|
||||
pub new_name: &'cx str,
|
||||
pub version: &'cx str,
|
||||
}
|
||||
|
||||
impl<'cx> ParseCxImpl<'cx> {
|
||||
/// Finds all lint declarations (`declare_clippy_lint!`)
|
||||
#[must_use]
|
||||
pub fn find_lint_decls(&mut self) -> Vec<Lint<'cx>> {
|
||||
let mut lints = Vec::with_capacity(1000);
|
||||
let mut contents = String::new();
|
||||
for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") {
|
||||
let e = expect_action(e, ErrAction::Read, ".");
|
||||
|
||||
// Skip if this isn't a lint crate's directory.
|
||||
let mut crate_path = if expect_action(e.file_type(), ErrAction::Read, ".").is_dir()
|
||||
&& let Ok(crate_path) = e.file_name().into_string()
|
||||
&& crate_path.starts_with("clippy_lints")
|
||||
&& crate_path != "clippy_lints_internal"
|
||||
{
|
||||
crate_path
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
crate_path.push(path::MAIN_SEPARATOR);
|
||||
crate_path.push_str("src");
|
||||
for e in walk_dir_no_dot_or_target(&crate_path) {
|
||||
let e = expect_action(e, ErrAction::Read, &crate_path);
|
||||
if let Some(path) = e.path().to_str()
|
||||
&& let Some(path) = path.strip_suffix(".rs")
|
||||
&& let Some(path) = path.get(crate_path.len() + 1..)
|
||||
{
|
||||
let module = if path == "lib" {
|
||||
""
|
||||
} else {
|
||||
let path = path
|
||||
.strip_suffix("mod")
|
||||
.and_then(|x| x.strip_suffix(path::MAIN_SEPARATOR))
|
||||
.unwrap_or(path);
|
||||
self.str_buf
|
||||
.alloc_replaced(self.arena, path, path::MAIN_SEPARATOR, "::")
|
||||
};
|
||||
self.parse_clippy_lint_decls(
|
||||
e.path(),
|
||||
File::open_read_to_cleared_string(e.path(), &mut contents),
|
||||
module,
|
||||
&mut lints,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
lints.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name));
|
||||
lints
|
||||
}
|
||||
|
||||
/// Parse a source file looking for `declare_clippy_lint` macro invocations.
|
||||
fn parse_clippy_lint_decls(&mut self, path: &Path, contents: &str, module: &'cx str, lints: &mut Vec<Lint<'cx>>) {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use cursor::Pat::*;
|
||||
#[rustfmt::skip]
|
||||
static DECL_TOKENS: &[cursor::Pat<'_>] = &[
|
||||
// !{ /// docs
|
||||
Bang, OpenBrace, AnyComment,
|
||||
// #[clippy::version = "version"]
|
||||
Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket,
|
||||
// pub NAME, GROUP,
|
||||
Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma,
|
||||
];
|
||||
|
||||
let mut cursor = Cursor::new(contents);
|
||||
let mut captures = [Capture::EMPTY; 2];
|
||||
while let Some(start) = cursor.find_ident("declare_clippy_lint") {
|
||||
if cursor.match_all(DECL_TOKENS, &mut captures) && cursor.find_pat(CloseBrace) {
|
||||
lints.push(Lint {
|
||||
name: self.str_buf.alloc_ascii_lower(self.arena, cursor.get_text(captures[0])),
|
||||
group: self.arena.alloc_str(cursor.get_text(captures[1])),
|
||||
module,
|
||||
path: path.into(),
|
||||
declaration_range: start as usize..cursor.pos() as usize,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn read_deprecated_lints(&mut self) -> (Vec<DeprecatedLint<'cx>>, Vec<RenamedLint<'cx>>) {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use cursor::Pat::*;
|
||||
#[rustfmt::skip]
|
||||
static DECL_TOKENS: &[cursor::Pat<'_>] = &[
|
||||
// #[clippy::version = "version"]
|
||||
Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, CaptureLitStr, CloseBracket,
|
||||
// ("first", "second"),
|
||||
OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, Comma,
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
static DEPRECATED_TOKENS: &[cursor::Pat<'_>] = &[
|
||||
// !{ DEPRECATED(DEPRECATED_VERSION) = [
|
||||
Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket,
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
static RENAMED_TOKENS: &[cursor::Pat<'_>] = &[
|
||||
// !{ RENAMED(RENAMED_VERSION) = [
|
||||
Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket,
|
||||
];
|
||||
|
||||
let path = "clippy_lints/src/deprecated_lints.rs";
|
||||
let mut deprecated = Vec::with_capacity(30);
|
||||
let mut renamed = Vec::with_capacity(80);
|
||||
let mut contents = String::new();
|
||||
File::open_read_to_cleared_string(path, &mut contents);
|
||||
|
||||
let mut cursor = Cursor::new(&contents);
|
||||
let mut captures = [Capture::EMPTY; 3];
|
||||
|
||||
// First instance is the macro definition.
|
||||
assert!(
|
||||
cursor.find_ident("declare_with_version").is_some(),
|
||||
"error reading deprecated lints"
|
||||
);
|
||||
|
||||
if cursor.find_ident("declare_with_version").is_some() && cursor.match_all(DEPRECATED_TOKENS, &mut []) {
|
||||
while cursor.match_all(DECL_TOKENS, &mut captures) {
|
||||
deprecated.push(DeprecatedLint {
|
||||
name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[1])),
|
||||
reason: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[2])),
|
||||
version: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
panic!("error reading deprecated lints");
|
||||
}
|
||||
|
||||
if cursor.find_ident("declare_with_version").is_some() && cursor.match_all(RENAMED_TOKENS, &mut []) {
|
||||
while cursor.match_all(DECL_TOKENS, &mut captures) {
|
||||
renamed.push(RenamedLint {
|
||||
old_name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[1])),
|
||||
new_name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[2])),
|
||||
version: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
panic!("error reading renamed lints");
|
||||
}
|
||||
|
||||
deprecated.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name));
|
||||
renamed.sort_by(|lhs, rhs| lhs.old_name.cmp(rhs.old_name));
|
||||
(deprecated, renamed)
|
||||
}
|
||||
|
||||
/// Removes the line splices and surrounding quotes from a string literal
|
||||
fn parse_str_lit(&mut self, s: &str) -> &'cx str {
|
||||
let (s, is_raw) = if let Some(s) = s.strip_prefix("r") {
|
||||
(s.trim_matches('#'), true)
|
||||
} else {
|
||||
(s, false)
|
||||
};
|
||||
let s = s
|
||||
.strip_prefix('"')
|
||||
.and_then(|s| s.strip_suffix('"'))
|
||||
.unwrap_or_else(|| panic!("expected quoted string, found `{s}`"));
|
||||
|
||||
if is_raw {
|
||||
if s.is_empty() { "" } else { self.arena.alloc_str(s) }
|
||||
} else {
|
||||
self.str_buf.with(|buf| {
|
||||
rustc_literal_escaper::unescape_str(s, &mut |_, ch| {
|
||||
if let Ok(ch) = ch {
|
||||
buf.push(ch);
|
||||
}
|
||||
});
|
||||
if buf.is_empty() { "" } else { self.arena.alloc_str(buf) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_str_single_line(&mut self, path: &Path, s: &str) -> &'cx str {
|
||||
let value = self.parse_str_lit(s);
|
||||
assert!(
|
||||
!value.contains('\n'),
|
||||
"error parsing `{}`: `{s}` should be a single line string",
|
||||
path.display(),
|
||||
);
|
||||
value
|
||||
}
|
||||
}
|
||||
263
clippy_dev/src/parse/cursor.rs
Normal file
263
clippy_dev/src/parse/cursor.rs
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
use core::slice;
|
||||
use rustc_lexer::{self as lex, LiteralKind, Token, TokenKind};
|
||||
|
||||
/// A token pattern used for searching and matching by the [`Cursor`].
|
||||
///
|
||||
/// In the event that a pattern is a multi-token sequence, earlier tokens will be consumed
|
||||
/// even if the pattern ultimately isn't matched. e.g. With the sequence `:*` matching
|
||||
/// `DoubleColon` will consume the first `:` and then fail to match, leaving the cursor at
|
||||
/// the `*`.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Pat<'a> {
|
||||
/// Matches any number of comments and doc comments.
|
||||
AnyComment,
|
||||
Ident(&'a str),
|
||||
CaptureIdent,
|
||||
LitStr,
|
||||
CaptureLitStr,
|
||||
Bang,
|
||||
CloseBrace,
|
||||
CloseBracket,
|
||||
CloseParen,
|
||||
Comma,
|
||||
DoubleColon,
|
||||
Eq,
|
||||
Lifetime,
|
||||
Lt,
|
||||
Gt,
|
||||
OpenBrace,
|
||||
OpenBracket,
|
||||
OpenParen,
|
||||
Pound,
|
||||
Semi,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Capture {
|
||||
pub pos: u32,
|
||||
pub len: u32,
|
||||
}
|
||||
impl Capture {
|
||||
pub const EMPTY: Self = Self { pos: 0, len: 0 };
|
||||
}
|
||||
|
||||
/// A unidirectional cursor over a token stream that is lexed on demand.
|
||||
pub struct Cursor<'txt> {
|
||||
next_token: Token,
|
||||
pos: u32,
|
||||
inner: lex::Cursor<'txt>,
|
||||
text: &'txt str,
|
||||
}
|
||||
impl<'txt> Cursor<'txt> {
|
||||
#[must_use]
|
||||
pub fn new(text: &'txt str) -> Self {
|
||||
let mut inner = lex::Cursor::new(text, lex::FrontmatterAllowed::Yes);
|
||||
Self {
|
||||
next_token: inner.advance_token(),
|
||||
pos: 0,
|
||||
inner,
|
||||
text,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the text of the captured token assuming it came from this cursor.
|
||||
#[must_use]
|
||||
pub fn get_text(&self, capture: Capture) -> &'txt str {
|
||||
&self.text[capture.pos as usize..(capture.pos + capture.len) as usize]
|
||||
}
|
||||
|
||||
/// Gets the text that makes up the next token in the stream, or the empty string if
|
||||
/// stream is exhausted.
|
||||
#[must_use]
|
||||
pub fn peek_text(&self) -> &'txt str {
|
||||
&self.text[self.pos as usize..(self.pos + self.next_token.len) as usize]
|
||||
}
|
||||
|
||||
/// Gets the length of the next token in bytes, or zero if the stream is exhausted.
|
||||
#[must_use]
|
||||
pub fn peek_len(&self) -> u32 {
|
||||
self.next_token.len
|
||||
}
|
||||
|
||||
/// Gets the next token in the stream, or [`TokenKind::Eof`] if the stream is
|
||||
/// exhausted.
|
||||
#[must_use]
|
||||
pub fn peek(&self) -> TokenKind {
|
||||
self.next_token.kind
|
||||
}
|
||||
|
||||
/// Gets the offset of the next token in the source string, or the string's length if
|
||||
/// the stream is exhausted.
|
||||
#[must_use]
|
||||
pub fn pos(&self) -> u32 {
|
||||
self.pos
|
||||
}
|
||||
|
||||
/// Gets whether the cursor has exhausted its input.
|
||||
#[must_use]
|
||||
pub fn at_end(&self) -> bool {
|
||||
self.next_token.kind == TokenKind::Eof
|
||||
}
|
||||
|
||||
/// Advances the cursor to the next token. If the stream is exhausted this will set
|
||||
/// the next token to [`TokenKind::Eof`].
|
||||
pub fn step(&mut self) {
|
||||
// `next_token.len` is zero for the eof marker.
|
||||
self.pos += self.next_token.len;
|
||||
self.next_token = self.inner.advance_token();
|
||||
}
|
||||
|
||||
/// Consumes tokens until the given pattern is either fully matched of fails to match.
|
||||
/// Returns whether the pattern was fully matched.
|
||||
///
|
||||
/// For each capture made by the pattern one item will be taken from the capture
|
||||
/// sequence with the result placed inside.
|
||||
fn match_impl(&mut self, pat: Pat<'_>, captures: &mut slice::IterMut<'_, Capture>) -> bool {
|
||||
loop {
|
||||
match (pat, self.next_token.kind) {
|
||||
#[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/6697
|
||||
(_, TokenKind::Whitespace)
|
||||
| (
|
||||
Pat::AnyComment,
|
||||
TokenKind::BlockComment { terminated: true, .. } | TokenKind::LineComment { .. },
|
||||
) => self.step(),
|
||||
(Pat::AnyComment, _) => return true,
|
||||
(Pat::Bang, TokenKind::Bang)
|
||||
| (Pat::CloseBrace, TokenKind::CloseBrace)
|
||||
| (Pat::CloseBracket, TokenKind::CloseBracket)
|
||||
| (Pat::CloseParen, TokenKind::CloseParen)
|
||||
| (Pat::Comma, TokenKind::Comma)
|
||||
| (Pat::Eq, TokenKind::Eq)
|
||||
| (Pat::Lifetime, TokenKind::Lifetime { .. })
|
||||
| (Pat::Lt, TokenKind::Lt)
|
||||
| (Pat::Gt, TokenKind::Gt)
|
||||
| (Pat::OpenBrace, TokenKind::OpenBrace)
|
||||
| (Pat::OpenBracket, TokenKind::OpenBracket)
|
||||
| (Pat::OpenParen, TokenKind::OpenParen)
|
||||
| (Pat::Pound, TokenKind::Pound)
|
||||
| (Pat::Semi, TokenKind::Semi)
|
||||
| (
|
||||
Pat::LitStr,
|
||||
TokenKind::Literal {
|
||||
kind: LiteralKind::Str { terminated: true } | LiteralKind::RawStr { .. },
|
||||
..
|
||||
},
|
||||
) => {
|
||||
self.step();
|
||||
return true;
|
||||
},
|
||||
(Pat::Ident(x), TokenKind::Ident) if x == self.peek_text() => {
|
||||
self.step();
|
||||
return true;
|
||||
},
|
||||
(Pat::DoubleColon, TokenKind::Colon) => {
|
||||
self.step();
|
||||
if !self.at_end() && matches!(self.next_token.kind, TokenKind::Colon) {
|
||||
self.step();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
#[rustfmt::skip]
|
||||
(
|
||||
Pat::CaptureLitStr,
|
||||
TokenKind::Literal {
|
||||
kind:
|
||||
LiteralKind::Str { terminated: true }
|
||||
| LiteralKind::RawStr { n_hashes: Some(_) },
|
||||
..
|
||||
},
|
||||
)
|
||||
| (Pat::CaptureIdent, TokenKind::Ident) => {
|
||||
*captures.next().unwrap() = Capture { pos: self.pos, len: self.next_token.len };
|
||||
self.step();
|
||||
return true;
|
||||
},
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes all tokens until the specified identifier is found and returns its
|
||||
/// position. Returns `None` if the identifier could not be found.
|
||||
///
|
||||
/// The cursor will be positioned immediately after the identifier, or at the end if
|
||||
/// it is not.
|
||||
pub fn find_ident(&mut self, ident: &str) -> Option<u32> {
|
||||
loop {
|
||||
match self.next_token.kind {
|
||||
TokenKind::Ident if self.peek_text() == ident => {
|
||||
let pos = self.pos;
|
||||
self.step();
|
||||
return Some(pos);
|
||||
},
|
||||
TokenKind::Eof => return None,
|
||||
_ => self.step(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes all tokens until the next identifier is found and captures it. Returns
|
||||
/// `None` if no identifier could be found.
|
||||
///
|
||||
/// The cursor will be positioned immediately after the identifier, or at the end if
|
||||
/// it is not.
|
||||
pub fn find_any_ident(&mut self) -> Option<Capture> {
|
||||
loop {
|
||||
match self.next_token.kind {
|
||||
TokenKind::Ident => {
|
||||
let res = Capture {
|
||||
pos: self.pos,
|
||||
len: self.next_token.len,
|
||||
};
|
||||
self.step();
|
||||
return Some(res);
|
||||
},
|
||||
TokenKind::Eof => return None,
|
||||
_ => self.step(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Continually attempt to match the pattern on subsequent tokens until a match is
|
||||
/// found. Returns whether the pattern was successfully matched.
|
||||
///
|
||||
/// Not generally suitable for multi-token patterns or patterns that can match
|
||||
/// nothing.
|
||||
#[must_use]
|
||||
pub fn find_pat(&mut self, pat: Pat<'_>) -> bool {
|
||||
let mut capture = [].iter_mut();
|
||||
while !self.match_impl(pat, &mut capture) {
|
||||
self.step();
|
||||
if self.at_end() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Attempts to match a sequence of patterns at the current position. Returns whether
|
||||
/// all patterns were successfully matched.
|
||||
///
|
||||
/// Captures will be written to the given slice in the order they're matched. If a
|
||||
/// capture is matched, but there are no more capture slots this will panic. If the
|
||||
/// match is completed without filling all the capture slots they will be left
|
||||
/// unmodified.
|
||||
///
|
||||
/// If the match fails the cursor will be positioned at the first failing token.
|
||||
#[must_use]
|
||||
pub fn match_all(&mut self, pats: &[Pat<'_>], captures: &mut [Capture]) -> bool {
|
||||
let mut captures = captures.iter_mut();
|
||||
pats.iter().all(|&p| self.match_impl(p, &mut captures))
|
||||
}
|
||||
|
||||
/// Attempts to match a single pattern at the current position. Returns whether the
|
||||
/// pattern was successfully matched.
|
||||
///
|
||||
/// If the pattern attempts to capture anything this will panic. If the match fails
|
||||
/// the cursor will be positioned at the first failing token.
|
||||
#[must_use]
|
||||
pub fn match_pat(&mut self, pat: Pat<'_>) -> bool {
|
||||
self.match_impl(pat, &mut [].iter_mut())
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ pub fn bump_version(mut version: Version) {
|
|||
dst.push_str(&src[..package.version_range.start]);
|
||||
write!(dst, "\"{}\"", version.toml_display()).unwrap();
|
||||
dst.push_str(&src[package.version_range.end..]);
|
||||
UpdateStatus::from_changed(src.get(package.version_range.clone()) != dst.get(package.version_range))
|
||||
UpdateStatus::from_changed(src.get(package.version_range) != dst.get(package.version_range))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use crate::update_lints::{RenamedLint, find_lint_decls, generate_lint_files, read_deprecated_lints};
|
||||
use crate::parse::cursor::{self, Capture, Cursor};
|
||||
use crate::parse::{ParseCx, RenamedLint};
|
||||
use crate::update_lints::generate_lint_files;
|
||||
use crate::utils::{
|
||||
ErrAction, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, Version, delete_dir_if_exists,
|
||||
delete_file_if_exists, expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target,
|
||||
ErrAction, FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists,
|
||||
expect_action, try_rename_dir, try_rename_file, walk_dir_no_dot_or_target,
|
||||
};
|
||||
use rustc_lexer::TokenKind;
|
||||
use std::ffi::OsString;
|
||||
|
|
@ -24,36 +26,35 @@ use std::path::Path;
|
|||
/// * If `old_name` doesn't name an existing lint.
|
||||
/// * If `old_name` names a deprecated or renamed lint.
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: bool) {
|
||||
if let Some((prefix, _)) = old_name.split_once("::") {
|
||||
panic!("`{old_name}` should not contain the `{prefix}` prefix");
|
||||
}
|
||||
if let Some((prefix, _)) = new_name.split_once("::") {
|
||||
panic!("`{new_name}` should not contain the `{prefix}` prefix");
|
||||
}
|
||||
|
||||
pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str, new_name: &'cx str, uplift: bool) {
|
||||
let mut updater = FileUpdater::default();
|
||||
let mut lints = find_lint_decls();
|
||||
let (deprecated_lints, mut renamed_lints) = read_deprecated_lints();
|
||||
let mut lints = cx.find_lint_decls();
|
||||
let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints();
|
||||
|
||||
let Ok(lint_idx) = lints.binary_search_by(|x| x.name.as_str().cmp(old_name)) else {
|
||||
let Ok(lint_idx) = lints.binary_search_by(|x| x.name.cmp(old_name)) else {
|
||||
panic!("could not find lint `{old_name}`");
|
||||
};
|
||||
let lint = &lints[lint_idx];
|
||||
|
||||
let old_name_prefixed = String::from_iter(["clippy::", old_name]);
|
||||
let old_name_prefixed = cx.str_buf.with(|buf| {
|
||||
buf.extend(["clippy::", old_name]);
|
||||
cx.arena.alloc_str(buf)
|
||||
});
|
||||
let new_name_prefixed = if uplift {
|
||||
new_name.to_owned()
|
||||
new_name
|
||||
} else {
|
||||
String::from_iter(["clippy::", new_name])
|
||||
cx.str_buf.with(|buf| {
|
||||
buf.extend(["clippy::", new_name]);
|
||||
cx.arena.alloc_str(buf)
|
||||
})
|
||||
};
|
||||
|
||||
for lint in &mut renamed_lints {
|
||||
if lint.new_name == old_name_prefixed {
|
||||
lint.new_name.clone_from(&new_name_prefixed);
|
||||
lint.new_name = new_name_prefixed;
|
||||
}
|
||||
}
|
||||
match renamed_lints.binary_search_by(|x| x.old_name.cmp(&old_name_prefixed)) {
|
||||
match renamed_lints.binary_search_by(|x| x.old_name.cmp(old_name_prefixed)) {
|
||||
Ok(_) => {
|
||||
println!("`{old_name}` already has a rename registered");
|
||||
return;
|
||||
|
|
@ -63,12 +64,8 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
|
|||
idx,
|
||||
RenamedLint {
|
||||
old_name: old_name_prefixed,
|
||||
new_name: if uplift {
|
||||
new_name.to_owned()
|
||||
} else {
|
||||
String::from_iter(["clippy::", new_name])
|
||||
},
|
||||
version: clippy_version.rust_display().to_string(),
|
||||
new_name: new_name_prefixed,
|
||||
version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()),
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
@ -104,7 +101,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
|
|||
}
|
||||
delete_test_files(old_name, change_prefixed_tests);
|
||||
lints.remove(lint_idx);
|
||||
} else if lints.binary_search_by(|x| x.name.as_str().cmp(new_name)).is_err() {
|
||||
} else if lints.binary_search_by(|x| x.name.cmp(new_name)).is_err() {
|
||||
let lint = &mut lints[lint_idx];
|
||||
if lint.module.ends_with(old_name)
|
||||
&& lint
|
||||
|
|
@ -118,13 +115,15 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
|
|||
mod_edit = ModEdit::Rename;
|
||||
}
|
||||
|
||||
let mod_len = lint.module.len();
|
||||
lint.module.truncate(mod_len - old_name.len());
|
||||
lint.module.push_str(new_name);
|
||||
lint.module = cx.str_buf.with(|buf| {
|
||||
buf.push_str(&lint.module[..lint.module.len() - old_name.len()]);
|
||||
buf.push_str(new_name);
|
||||
cx.arena.alloc_str(buf)
|
||||
});
|
||||
}
|
||||
rename_test_files(old_name, new_name, change_prefixed_tests);
|
||||
new_name.clone_into(&mut lints[lint_idx].name);
|
||||
lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
|
||||
lints[lint_idx].name = new_name;
|
||||
lints.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name));
|
||||
} else {
|
||||
println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`");
|
||||
println!("Since `{new_name}` already exists the existing code has not been changed");
|
||||
|
|
@ -132,7 +131,7 @@ pub fn rename(clippy_version: Version, old_name: &str, new_name: &str, uplift: b
|
|||
}
|
||||
|
||||
let mut update_fn = file_update_fn(old_name, new_name, mod_edit);
|
||||
for e in walk_dir_no_dot_or_target() {
|
||||
for e in walk_dir_no_dot_or_target(".") {
|
||||
let e = expect_action(e, ErrAction::Read, ".");
|
||||
if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") {
|
||||
updater.update_file(e.path(), &mut update_fn);
|
||||
|
|
@ -285,47 +284,46 @@ fn file_update_fn<'a, 'b>(
|
|||
move |_, src, dst| {
|
||||
let mut copy_pos = 0u32;
|
||||
let mut changed = false;
|
||||
let mut searcher = RustSearcher::new(src);
|
||||
let mut capture = "";
|
||||
let mut cursor = Cursor::new(src);
|
||||
let mut captures = [Capture::EMPTY];
|
||||
loop {
|
||||
match searcher.peek() {
|
||||
match cursor.peek() {
|
||||
TokenKind::Eof => break,
|
||||
TokenKind::Ident => {
|
||||
let match_start = searcher.pos();
|
||||
let text = searcher.peek_text();
|
||||
searcher.step();
|
||||
let match_start = cursor.pos();
|
||||
let text = cursor.peek_text();
|
||||
cursor.step();
|
||||
match text {
|
||||
// clippy::line_name or clippy::lint-name
|
||||
"clippy" => {
|
||||
if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture])
|
||||
&& capture == old_name
|
||||
if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures)
|
||||
&& cursor.get_text(captures[0]) == old_name
|
||||
{
|
||||
dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]);
|
||||
dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]);
|
||||
dst.push_str(new_name);
|
||||
copy_pos = searcher.pos();
|
||||
copy_pos = cursor.pos();
|
||||
changed = true;
|
||||
}
|
||||
},
|
||||
// mod lint_name
|
||||
"mod" => {
|
||||
if !matches!(mod_edit, ModEdit::None)
|
||||
&& searcher.match_tokens(&[Token::CaptureIdent], &mut [&mut capture])
|
||||
&& capture == old_name
|
||||
&& let Some(pos) = cursor.find_ident(old_name)
|
||||
{
|
||||
match mod_edit {
|
||||
ModEdit::Rename => {
|
||||
dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]);
|
||||
dst.push_str(&src[copy_pos as usize..pos as usize]);
|
||||
dst.push_str(new_name);
|
||||
copy_pos = searcher.pos();
|
||||
copy_pos = cursor.pos();
|
||||
changed = true;
|
||||
},
|
||||
ModEdit::Delete if searcher.match_tokens(&[Token::Semi], &mut []) => {
|
||||
ModEdit::Delete if cursor.match_pat(cursor::Pat::Semi) => {
|
||||
let mut start = &src[copy_pos as usize..match_start as usize];
|
||||
if start.ends_with("\n\n") {
|
||||
start = &start[..start.len() - 1];
|
||||
}
|
||||
dst.push_str(start);
|
||||
copy_pos = searcher.pos();
|
||||
copy_pos = cursor.pos();
|
||||
if src[copy_pos as usize..].starts_with("\n\n") {
|
||||
copy_pos += 1;
|
||||
}
|
||||
|
|
@ -337,8 +335,8 @@ fn file_update_fn<'a, 'b>(
|
|||
},
|
||||
// lint_name::
|
||||
name if matches!(mod_edit, ModEdit::Rename) && name == old_name => {
|
||||
let name_end = searcher.pos();
|
||||
if searcher.match_tokens(&[Token::DoubleColon], &mut []) {
|
||||
let name_end = cursor.pos();
|
||||
if cursor.match_pat(cursor::Pat::DoubleColon) {
|
||||
dst.push_str(&src[copy_pos as usize..match_start as usize]);
|
||||
dst.push_str(new_name);
|
||||
copy_pos = name_end;
|
||||
|
|
@ -356,36 +354,36 @@ fn file_update_fn<'a, 'b>(
|
|||
};
|
||||
dst.push_str(&src[copy_pos as usize..match_start as usize]);
|
||||
dst.push_str(replacement);
|
||||
copy_pos = searcher.pos();
|
||||
copy_pos = cursor.pos();
|
||||
changed = true;
|
||||
},
|
||||
}
|
||||
},
|
||||
// //~ lint_name
|
||||
TokenKind::LineComment { doc_style: None } => {
|
||||
let text = searcher.peek_text();
|
||||
let text = cursor.peek_text();
|
||||
if text.starts_with("//~")
|
||||
&& let Some(text) = text.strip_suffix(old_name)
|
||||
&& !text.ends_with(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_'))
|
||||
{
|
||||
dst.push_str(&src[copy_pos as usize..searcher.pos() as usize + text.len()]);
|
||||
dst.push_str(&src[copy_pos as usize..cursor.pos() as usize + text.len()]);
|
||||
dst.push_str(new_name);
|
||||
copy_pos = searcher.pos() + searcher.peek_len();
|
||||
copy_pos = cursor.pos() + cursor.peek_len();
|
||||
changed = true;
|
||||
}
|
||||
searcher.step();
|
||||
cursor.step();
|
||||
},
|
||||
// ::lint_name
|
||||
TokenKind::Colon
|
||||
if searcher.match_tokens(&[Token::DoubleColon, Token::CaptureIdent], &mut [&mut capture])
|
||||
&& capture == old_name =>
|
||||
if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::CaptureIdent], &mut captures)
|
||||
&& cursor.get_text(captures[0]) == old_name =>
|
||||
{
|
||||
dst.push_str(&src[copy_pos as usize..searcher.pos() as usize - capture.len()]);
|
||||
dst.push_str(&src[copy_pos as usize..captures[0].pos as usize]);
|
||||
dst.push_str(new_name);
|
||||
copy_pos = searcher.pos();
|
||||
copy_pos = cursor.pos();
|
||||
changed = true;
|
||||
},
|
||||
_ => searcher.step(),
|
||||
_ => cursor.step(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
use crate::utils::{
|
||||
ErrAction, File, FileUpdater, RustSearcher, Token, UpdateMode, UpdateStatus, expect_action, update_text_region_fn,
|
||||
};
|
||||
use crate::parse::cursor::Cursor;
|
||||
use crate::parse::{DeprecatedLint, Lint, ParseCx, RenamedLint};
|
||||
use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn};
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Write;
|
||||
use std::fs;
|
||||
use std::ops::Range;
|
||||
use std::path::{self, Path, PathBuf};
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
use std::path::{self, Path};
|
||||
|
||||
const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\
|
||||
// Use that command to update this file and do not edit by hand.\n\
|
||||
|
|
@ -24,18 +21,18 @@ const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.ht
|
|||
/// # Panics
|
||||
///
|
||||
/// Panics if a file path could not read from or then written to
|
||||
pub fn update(update_mode: UpdateMode) {
|
||||
let lints = find_lint_decls();
|
||||
let (deprecated, renamed) = read_deprecated_lints();
|
||||
pub fn update(cx: ParseCx<'_>, update_mode: UpdateMode) {
|
||||
let lints = cx.find_lint_decls();
|
||||
let (deprecated, renamed) = cx.read_deprecated_lints();
|
||||
generate_lint_files(update_mode, &lints, &deprecated, &renamed);
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn generate_lint_files(
|
||||
update_mode: UpdateMode,
|
||||
lints: &[Lint],
|
||||
deprecated: &[DeprecatedLint],
|
||||
renamed: &[RenamedLint],
|
||||
lints: &[Lint<'_>],
|
||||
deprecated: &[DeprecatedLint<'_>],
|
||||
renamed: &[RenamedLint<'_>],
|
||||
) {
|
||||
let mut updater = FileUpdater::default();
|
||||
updater.update_file_checked(
|
||||
|
|
@ -64,7 +61,7 @@ pub fn generate_lint_files(
|
|||
|dst| {
|
||||
for lint in lints
|
||||
.iter()
|
||||
.map(|l| &*l.name)
|
||||
.map(|l| l.name)
|
||||
.chain(deprecated.iter().filter_map(|l| l.name.strip_prefix("clippy::")))
|
||||
.chain(renamed.iter().filter_map(|l| l.old_name.strip_prefix("clippy::")))
|
||||
.sorted()
|
||||
|
|
@ -79,13 +76,13 @@ pub fn generate_lint_files(
|
|||
update_mode,
|
||||
"clippy_lints/src/deprecated_lints.rs",
|
||||
&mut |_, src, dst| {
|
||||
let mut searcher = RustSearcher::new(src);
|
||||
let mut cursor = Cursor::new(src);
|
||||
assert!(
|
||||
searcher.find_token(Token::Ident("declare_with_version"))
|
||||
&& searcher.find_token(Token::Ident("declare_with_version")),
|
||||
cursor.find_ident("declare_with_version").is_some()
|
||||
&& cursor.find_ident("declare_with_version").is_some(),
|
||||
"error reading deprecated lints"
|
||||
);
|
||||
dst.push_str(&src[..searcher.pos() as usize]);
|
||||
dst.push_str(&src[..cursor.pos() as usize]);
|
||||
dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n");
|
||||
for lint in deprecated {
|
||||
write!(
|
||||
|
|
@ -135,13 +132,13 @@ pub fn generate_lint_files(
|
|||
dst.push_str(GENERATED_FILE_COMMENT);
|
||||
dst.push_str("#![allow(clippy::duplicated_attributes)]\n");
|
||||
for lint in renamed {
|
||||
if seen_lints.insert(&lint.new_name) {
|
||||
if seen_lints.insert(lint.new_name) {
|
||||
writeln!(dst, "#![allow({})]", lint.new_name).unwrap();
|
||||
}
|
||||
}
|
||||
seen_lints.clear();
|
||||
for lint in renamed {
|
||||
if seen_lints.insert(&lint.old_name) {
|
||||
if seen_lints.insert(lint.old_name) {
|
||||
writeln!(dst, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
@ -167,7 +164,7 @@ pub fn generate_lint_files(
|
|||
for lint_mod in lints
|
||||
.iter()
|
||||
.filter(|l| !l.module.is_empty())
|
||||
.map(|l| l.module.split_once("::").map_or(&*l.module, |x| x.0))
|
||||
.map(|l| l.module.split_once("::").map_or(l.module, |x| x.0))
|
||||
.sorted()
|
||||
.dedup()
|
||||
{
|
||||
|
|
@ -200,260 +197,3 @@ pub fn generate_lint_files(
|
|||
fn round_to_fifty(count: usize) -> usize {
|
||||
count / 50 * 50
|
||||
}
|
||||
|
||||
/// Lint data parsed from the Clippy source code.
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct Lint {
|
||||
pub name: String,
|
||||
pub group: String,
|
||||
pub module: String,
|
||||
pub path: PathBuf,
|
||||
pub declaration_range: Range<usize>,
|
||||
}
|
||||
|
||||
pub struct DeprecatedLint {
|
||||
pub name: String,
|
||||
pub reason: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
pub struct RenamedLint {
|
||||
pub old_name: String,
|
||||
pub new_name: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
/// Finds all lint declarations (`declare_clippy_lint!`)
|
||||
#[must_use]
|
||||
pub fn find_lint_decls() -> Vec<Lint> {
|
||||
let mut lints = Vec::with_capacity(1000);
|
||||
let mut contents = String::new();
|
||||
for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") {
|
||||
let e = expect_action(e, ErrAction::Read, ".");
|
||||
if !expect_action(e.file_type(), ErrAction::Read, ".").is_dir() {
|
||||
continue;
|
||||
}
|
||||
let Ok(mut name) = e.file_name().into_string() else {
|
||||
continue;
|
||||
};
|
||||
if name.starts_with("clippy_lints") && name != "clippy_lints_internal" {
|
||||
name.push_str("/src");
|
||||
for (file, module) in read_src_with_module(name.as_ref()) {
|
||||
parse_clippy_lint_decls(
|
||||
file.path(),
|
||||
File::open_read_to_cleared_string(file.path(), &mut contents),
|
||||
&module,
|
||||
&mut lints,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
|
||||
lints
|
||||
}
|
||||
|
||||
/// Reads the source files from the given root directory
|
||||
fn read_src_with_module(src_root: &Path) -> impl use<'_> + Iterator<Item = (DirEntry, String)> {
|
||||
WalkDir::new(src_root).into_iter().filter_map(move |e| {
|
||||
let e = expect_action(e, ErrAction::Read, src_root);
|
||||
let path = e.path().as_os_str().as_encoded_bytes();
|
||||
if let Some(path) = path.strip_suffix(b".rs")
|
||||
&& let Some(path) = path.get(src_root.as_os_str().len() + 1..)
|
||||
{
|
||||
if path == b"lib" {
|
||||
Some((e, String::new()))
|
||||
} else {
|
||||
let path = if let Some(path) = path.strip_suffix(b"mod")
|
||||
&& let Some(path) = path.strip_suffix(b"/").or_else(|| path.strip_suffix(b"\\"))
|
||||
{
|
||||
path
|
||||
} else {
|
||||
path
|
||||
};
|
||||
if let Ok(path) = str::from_utf8(path) {
|
||||
let path = path.replace(['/', '\\'], "::");
|
||||
Some((e, path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a source file looking for `declare_clippy_lint` macro invocations.
|
||||
fn parse_clippy_lint_decls(path: &Path, contents: &str, module: &str, lints: &mut Vec<Lint>) {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use Token::*;
|
||||
#[rustfmt::skip]
|
||||
static DECL_TOKENS: &[Token<'_>] = &[
|
||||
// !{ /// docs
|
||||
Bang, OpenBrace, AnyComment,
|
||||
// #[clippy::version = "version"]
|
||||
Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket,
|
||||
// pub NAME, GROUP,
|
||||
Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma,
|
||||
];
|
||||
|
||||
let mut searcher = RustSearcher::new(contents);
|
||||
while searcher.find_token(Ident("declare_clippy_lint")) {
|
||||
let start = searcher.pos() as usize - "declare_clippy_lint".len();
|
||||
let (mut name, mut group) = ("", "");
|
||||
if searcher.match_tokens(DECL_TOKENS, &mut [&mut name, &mut group]) && searcher.find_token(CloseBrace) {
|
||||
lints.push(Lint {
|
||||
name: name.to_lowercase(),
|
||||
group: group.into(),
|
||||
module: module.into(),
|
||||
path: path.into(),
|
||||
declaration_range: start..searcher.pos() as usize,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn read_deprecated_lints() -> (Vec<DeprecatedLint>, Vec<RenamedLint>) {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use Token::*;
|
||||
#[rustfmt::skip]
|
||||
static DECL_TOKENS: &[Token<'_>] = &[
|
||||
// #[clippy::version = "version"]
|
||||
Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, CaptureLitStr, CloseBracket,
|
||||
// ("first", "second"),
|
||||
OpenParen, CaptureLitStr, Comma, CaptureLitStr, CloseParen, Comma,
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
static DEPRECATED_TOKENS: &[Token<'_>] = &[
|
||||
// !{ DEPRECATED(DEPRECATED_VERSION) = [
|
||||
Bang, OpenBrace, Ident("DEPRECATED"), OpenParen, Ident("DEPRECATED_VERSION"), CloseParen, Eq, OpenBracket,
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
static RENAMED_TOKENS: &[Token<'_>] = &[
|
||||
// !{ RENAMED(RENAMED_VERSION) = [
|
||||
Bang, OpenBrace, Ident("RENAMED"), OpenParen, Ident("RENAMED_VERSION"), CloseParen, Eq, OpenBracket,
|
||||
];
|
||||
|
||||
let path = "clippy_lints/src/deprecated_lints.rs";
|
||||
let mut deprecated = Vec::with_capacity(30);
|
||||
let mut renamed = Vec::with_capacity(80);
|
||||
let mut contents = String::new();
|
||||
File::open_read_to_cleared_string(path, &mut contents);
|
||||
|
||||
let mut searcher = RustSearcher::new(&contents);
|
||||
|
||||
// First instance is the macro definition.
|
||||
assert!(
|
||||
searcher.find_token(Ident("declare_with_version")),
|
||||
"error reading deprecated lints"
|
||||
);
|
||||
|
||||
if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(DEPRECATED_TOKENS, &mut []) {
|
||||
let mut version = "";
|
||||
let mut name = "";
|
||||
let mut reason = "";
|
||||
while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut name, &mut reason]) {
|
||||
deprecated.push(DeprecatedLint {
|
||||
name: parse_str_single_line(path.as_ref(), name),
|
||||
reason: parse_str_single_line(path.as_ref(), reason),
|
||||
version: parse_str_single_line(path.as_ref(), version),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
panic!("error reading deprecated lints");
|
||||
}
|
||||
|
||||
if searcher.find_token(Ident("declare_with_version")) && searcher.match_tokens(RENAMED_TOKENS, &mut []) {
|
||||
let mut version = "";
|
||||
let mut old_name = "";
|
||||
let mut new_name = "";
|
||||
while searcher.match_tokens(DECL_TOKENS, &mut [&mut version, &mut old_name, &mut new_name]) {
|
||||
renamed.push(RenamedLint {
|
||||
old_name: parse_str_single_line(path.as_ref(), old_name),
|
||||
new_name: parse_str_single_line(path.as_ref(), new_name),
|
||||
version: parse_str_single_line(path.as_ref(), version),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
panic!("error reading renamed lints");
|
||||
}
|
||||
|
||||
deprecated.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
|
||||
renamed.sort_by(|lhs, rhs| lhs.old_name.cmp(&rhs.old_name));
|
||||
(deprecated, renamed)
|
||||
}
|
||||
|
||||
/// Removes the line splices and surrounding quotes from a string literal
|
||||
fn parse_str_lit(s: &str) -> String {
|
||||
let s = s.strip_prefix("r").unwrap_or(s).trim_matches('#');
|
||||
let s = s
|
||||
.strip_prefix('"')
|
||||
.and_then(|s| s.strip_suffix('"'))
|
||||
.unwrap_or_else(|| panic!("expected quoted string, found `{s}`"));
|
||||
let mut res = String::with_capacity(s.len());
|
||||
rustc_literal_escaper::unescape_str(s, &mut |_, ch| {
|
||||
if let Ok(ch) = ch {
|
||||
res.push(ch);
|
||||
}
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
fn parse_str_single_line(path: &Path, s: &str) -> String {
|
||||
let value = parse_str_lit(s);
|
||||
assert!(
|
||||
!value.contains('\n'),
|
||||
"error parsing `{}`: `{s}` should be a single line string",
|
||||
path.display(),
|
||||
);
|
||||
value
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_clippy_lint_decls() {
|
||||
static CONTENTS: &str = r#"
|
||||
declare_clippy_lint! {
|
||||
#[clippy::version = "Hello Clippy!"]
|
||||
pub PTR_ARG,
|
||||
style,
|
||||
"really long \
|
||||
text"
|
||||
}
|
||||
|
||||
declare_clippy_lint!{
|
||||
#[clippy::version = "Test version"]
|
||||
pub DOC_MARKDOWN,
|
||||
pedantic,
|
||||
"single line"
|
||||
}
|
||||
"#;
|
||||
let mut result = Vec::new();
|
||||
parse_clippy_lint_decls("".as_ref(), CONTENTS, "module_name", &mut result);
|
||||
for r in &mut result {
|
||||
r.declaration_range = Range::default();
|
||||
}
|
||||
|
||||
let expected = vec![
|
||||
Lint {
|
||||
name: "ptr_arg".into(),
|
||||
group: "style".into(),
|
||||
module: "module_name".into(),
|
||||
path: PathBuf::new(),
|
||||
declaration_range: Range::default(),
|
||||
},
|
||||
Lint {
|
||||
name: "doc_markdown".into(),
|
||||
group: "pedantic".into(),
|
||||
module: "module_name".into(),
|
||||
path: PathBuf::new(),
|
||||
declaration_range: Range::default(),
|
||||
},
|
||||
];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use core::fmt::{self, Display};
|
||||
use core::marker::PhantomData;
|
||||
use core::num::NonZero;
|
||||
use core::ops::Range;
|
||||
use core::slice;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use core::range::Range;
|
||||
use core::str::FromStr;
|
||||
use rustc_lexer::{self as lexer, FrontmatterAllowed};
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::{self, Read as _, Seek as _, SeekFrom, Write};
|
||||
|
|
@ -12,6 +12,24 @@ use std::process::{self, Command, Stdio};
|
|||
use std::{env, thread};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub struct Scoped<'inner, 'outer: 'inner, T>(T, PhantomData<&'inner mut T>, PhantomData<&'outer mut ()>);
|
||||
impl<T> Scoped<'_, '_, T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self(value, PhantomData, PhantomData)
|
||||
}
|
||||
}
|
||||
impl<T> Deref for Scoped<'_, '_, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl<T> DerefMut for Scoped<'_, '_, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ErrAction {
|
||||
Open,
|
||||
|
|
@ -410,179 +428,6 @@ pub fn update_text_region_fn(
|
|||
move |path, src, dst| update_text_region(path, start, end, src, dst, &mut insert)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Token<'a> {
|
||||
/// Matches any number of comments / doc comments.
|
||||
AnyComment,
|
||||
Ident(&'a str),
|
||||
CaptureIdent,
|
||||
LitStr,
|
||||
CaptureLitStr,
|
||||
Bang,
|
||||
CloseBrace,
|
||||
CloseBracket,
|
||||
CloseParen,
|
||||
/// This will consume the first colon even if the second doesn't exist.
|
||||
DoubleColon,
|
||||
Comma,
|
||||
Eq,
|
||||
Lifetime,
|
||||
Lt,
|
||||
Gt,
|
||||
OpenBrace,
|
||||
OpenBracket,
|
||||
OpenParen,
|
||||
Pound,
|
||||
Semi,
|
||||
}
|
||||
|
||||
pub struct RustSearcher<'txt> {
|
||||
text: &'txt str,
|
||||
cursor: lexer::Cursor<'txt>,
|
||||
pos: u32,
|
||||
next_token: lexer::Token,
|
||||
}
|
||||
impl<'txt> RustSearcher<'txt> {
|
||||
#[must_use]
|
||||
#[expect(clippy::inconsistent_struct_constructor)]
|
||||
pub fn new(text: &'txt str) -> Self {
|
||||
let mut cursor = lexer::Cursor::new(text, FrontmatterAllowed::Yes);
|
||||
Self {
|
||||
text,
|
||||
pos: 0,
|
||||
next_token: cursor.advance_token(),
|
||||
cursor,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn peek_text(&self) -> &'txt str {
|
||||
&self.text[self.pos as usize..(self.pos + self.next_token.len) as usize]
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn peek_len(&self) -> u32 {
|
||||
self.next_token.len
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn peek(&self) -> lexer::TokenKind {
|
||||
self.next_token.kind
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn pos(&self) -> u32 {
|
||||
self.pos
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn at_end(&self) -> bool {
|
||||
self.next_token.kind == lexer::TokenKind::Eof
|
||||
}
|
||||
|
||||
pub fn step(&mut self) {
|
||||
// `next_len` is zero for the sentinel value and the eof marker.
|
||||
self.pos += self.next_token.len;
|
||||
self.next_token = self.cursor.advance_token();
|
||||
}
|
||||
|
||||
/// Consumes the next token if it matches the requested value and captures the value if
|
||||
/// requested. Returns true if a token was matched.
|
||||
fn read_token(&mut self, token: Token<'_>, captures: &mut slice::IterMut<'_, &mut &'txt str>) -> bool {
|
||||
loop {
|
||||
match (token, self.next_token.kind) {
|
||||
(_, lexer::TokenKind::Whitespace)
|
||||
| (
|
||||
Token::AnyComment,
|
||||
lexer::TokenKind::BlockComment { terminated: true, .. } | lexer::TokenKind::LineComment { .. },
|
||||
) => self.step(),
|
||||
(Token::AnyComment, _) => return true,
|
||||
(Token::Bang, lexer::TokenKind::Bang)
|
||||
| (Token::CloseBrace, lexer::TokenKind::CloseBrace)
|
||||
| (Token::CloseBracket, lexer::TokenKind::CloseBracket)
|
||||
| (Token::CloseParen, lexer::TokenKind::CloseParen)
|
||||
| (Token::Comma, lexer::TokenKind::Comma)
|
||||
| (Token::Eq, lexer::TokenKind::Eq)
|
||||
| (Token::Lifetime, lexer::TokenKind::Lifetime { .. })
|
||||
| (Token::Lt, lexer::TokenKind::Lt)
|
||||
| (Token::Gt, lexer::TokenKind::Gt)
|
||||
| (Token::OpenBrace, lexer::TokenKind::OpenBrace)
|
||||
| (Token::OpenBracket, lexer::TokenKind::OpenBracket)
|
||||
| (Token::OpenParen, lexer::TokenKind::OpenParen)
|
||||
| (Token::Pound, lexer::TokenKind::Pound)
|
||||
| (Token::Semi, lexer::TokenKind::Semi)
|
||||
| (
|
||||
Token::LitStr,
|
||||
lexer::TokenKind::Literal {
|
||||
kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. },
|
||||
..
|
||||
},
|
||||
) => {
|
||||
self.step();
|
||||
return true;
|
||||
},
|
||||
(Token::Ident(x), lexer::TokenKind::Ident) if x == self.peek_text() => {
|
||||
self.step();
|
||||
return true;
|
||||
},
|
||||
(Token::DoubleColon, lexer::TokenKind::Colon) => {
|
||||
self.step();
|
||||
if !self.at_end() && matches!(self.next_token.kind, lexer::TokenKind::Colon) {
|
||||
self.step();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
(
|
||||
Token::CaptureLitStr,
|
||||
lexer::TokenKind::Literal {
|
||||
kind: lexer::LiteralKind::Str { terminated: true } | lexer::LiteralKind::RawStr { .. },
|
||||
..
|
||||
},
|
||||
)
|
||||
| (Token::CaptureIdent, lexer::TokenKind::Ident) => {
|
||||
**captures.next().unwrap() = self.peek_text();
|
||||
self.step();
|
||||
return true;
|
||||
},
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn find_token(&mut self, token: Token<'_>) -> bool {
|
||||
let mut capture = [].iter_mut();
|
||||
while !self.read_token(token, &mut capture) {
|
||||
self.step();
|
||||
if self.at_end() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn find_capture_token(&mut self, token: Token<'_>) -> Option<&'txt str> {
|
||||
let mut res = "";
|
||||
let mut capture = &mut res;
|
||||
let mut capture = slice::from_mut(&mut capture).iter_mut();
|
||||
while !self.read_token(token, &mut capture) {
|
||||
self.step();
|
||||
if self.at_end() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(res)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn match_tokens(&mut self, tokens: &[Token<'_>], captures: &mut [&mut &'txt str]) -> bool {
|
||||
let mut captures = captures.iter_mut();
|
||||
tokens.iter().all(|&t| self.read_token(t, &mut captures))
|
||||
}
|
||||
}
|
||||
|
||||
#[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) {
|
||||
|
|
@ -748,8 +593,8 @@ pub fn delete_dir_if_exists(path: &Path) {
|
|||
}
|
||||
|
||||
/// Walks all items excluding top-level dot files/directories and any target directories.
|
||||
pub fn walk_dir_no_dot_or_target() -> impl Iterator<Item = ::walkdir::Result<::walkdir::DirEntry>> {
|
||||
WalkDir::new(".").into_iter().filter_entry(|e| {
|
||||
pub fn walk_dir_no_dot_or_target(p: impl AsRef<Path>) -> impl Iterator<Item = ::walkdir::Result<::walkdir::DirEntry>> {
|
||||
WalkDir::new(p).into_iter().filter_entry(|e| {
|
||||
e.path()
|
||||
.file_name()
|
||||
.is_none_or(|x| x != "target" && x.as_encoded_bytes().first().copied() != Some(b'.'))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_lints"
|
||||
version = "0.1.92"
|
||||
version = "0.1.93"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
|
|||
crate::duplicate_mod::DUPLICATE_MOD_INFO,
|
||||
crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO,
|
||||
crate::empty_drop::EMPTY_DROP_INFO,
|
||||
crate::empty_enum::EMPTY_ENUM_INFO,
|
||||
crate::empty_enums::EMPTY_ENUMS_INFO,
|
||||
crate::empty_line_after::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
|
||||
crate::empty_line_after::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
|
||||
crate::empty_with_brackets::EMPTY_ENUM_VARIANTS_WITH_BRACKETS_INFO,
|
||||
|
|
@ -227,8 +227,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
|
|||
crate::init_numbered_fields::INIT_NUMBERED_FIELDS_INFO,
|
||||
crate::inline_fn_without_body::INLINE_FN_WITHOUT_BODY_INFO,
|
||||
crate::int_plus_one::INT_PLUS_ONE_INFO,
|
||||
crate::integer_division_remainder_used::INTEGER_DIVISION_REMAINDER_USED_INFO,
|
||||
crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO,
|
||||
crate::item_name_repetitions::ENUM_VARIANT_NAMES_INFO,
|
||||
crate::item_name_repetitions::MODULE_INCEPTION_INFO,
|
||||
crate::item_name_repetitions::MODULE_NAME_REPETITIONS_INFO,
|
||||
|
|
@ -258,7 +256,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
|
|||
crate::lifetimes::ELIDABLE_LIFETIME_NAMES_INFO,
|
||||
crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO,
|
||||
crate::lifetimes::NEEDLESS_LIFETIMES_INFO,
|
||||
crate::lines_filter_map_ok::LINES_FILTER_MAP_OK_INFO,
|
||||
crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO,
|
||||
crate::literal_representation::INCONSISTENT_DIGIT_GROUPING_INFO,
|
||||
crate::literal_representation::LARGE_DIGIT_GROUPS_INFO,
|
||||
|
|
@ -298,7 +295,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
|
|||
crate::manual_async_fn::MANUAL_ASYNC_FN_INFO,
|
||||
crate::manual_bits::MANUAL_BITS_INFO,
|
||||
crate::manual_clamp::MANUAL_CLAMP_INFO,
|
||||
crate::manual_div_ceil::MANUAL_DIV_CEIL_INFO,
|
||||
crate::manual_float_methods::MANUAL_IS_FINITE_INFO,
|
||||
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
|
||||
crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
|
||||
|
|
@ -404,6 +400,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
|
|||
crate::methods::ITER_SKIP_ZERO_INFO,
|
||||
crate::methods::ITER_WITH_DRAIN_INFO,
|
||||
crate::methods::JOIN_ABSOLUTE_PATHS_INFO,
|
||||
crate::methods::LINES_FILTER_MAP_OK_INFO,
|
||||
crate::methods::MANUAL_CONTAINS_INFO,
|
||||
crate::methods::MANUAL_C_STR_LITERALS_INFO,
|
||||
crate::methods::MANUAL_FILTER_MAP_INFO,
|
||||
|
|
@ -545,7 +542,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
|
|||
crate::needless_continue::NEEDLESS_CONTINUE_INFO,
|
||||
crate::needless_else::NEEDLESS_ELSE_INFO,
|
||||
crate::needless_for_each::NEEDLESS_FOR_EACH_INFO,
|
||||
crate::needless_if::NEEDLESS_IF_INFO,
|
||||
crate::needless_ifs::NEEDLESS_IFS_INFO,
|
||||
crate::needless_late_init::NEEDLESS_LATE_INIT_INFO,
|
||||
crate::needless_maybe_sized::NEEDLESS_MAYBE_SIZED_INFO,
|
||||
crate::needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS_INFO,
|
||||
|
|
@ -592,6 +589,9 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
|
|||
crate::operators::IMPOSSIBLE_COMPARISONS_INFO,
|
||||
crate::operators::INEFFECTIVE_BIT_MASK_INFO,
|
||||
crate::operators::INTEGER_DIVISION_INFO,
|
||||
crate::operators::INTEGER_DIVISION_REMAINDER_USED_INFO,
|
||||
crate::operators::INVALID_UPCAST_COMPARISONS_INFO,
|
||||
crate::operators::MANUAL_DIV_CEIL_INFO,
|
||||
crate::operators::MANUAL_IS_MULTIPLE_OF_INFO,
|
||||
crate::operators::MANUAL_MIDPOINT_INFO,
|
||||
crate::operators::MISREFACTORED_ASSIGN_OP_INFO,
|
||||
|
|
|
|||
|
|
@ -85,6 +85,8 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [
|
|||
("clippy::drop_copy", "dropping_copy_types"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::drop_ref", "dropping_references"),
|
||||
#[clippy::version = "1.92.0"]
|
||||
("clippy::empty_enum", "clippy::empty_enums"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::eval_order_dependence", "clippy::mixed_read_write_in_expression"),
|
||||
#[clippy::version = "1.53.0"]
|
||||
|
|
@ -137,6 +139,8 @@ declare_with_version! { RENAMED(RENAMED_VERSION) = [
|
|||
("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums"),
|
||||
#[clippy::version = "1.80.0"]
|
||||
("clippy::mismatched_target_os", "unexpected_cfgs"),
|
||||
#[clippy::version = "1.92.0"]
|
||||
("clippy::needless_if", "clippy::needless_ifs"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::new_without_default_derive", "clippy::new_without_default"),
|
||||
#[clippy::version = ""]
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ use rustc_resolve::rustdoc::{
|
|||
};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::edition::Edition;
|
||||
use std::ops::Range;
|
||||
use url::Url;
|
||||
|
||||
|
|
@ -36,6 +35,7 @@ mod markdown;
|
|||
mod missing_headers;
|
||||
mod needless_doctest_main;
|
||||
mod suspicious_doc_comments;
|
||||
mod test_attr_in_doctest;
|
||||
mod too_long_first_doc_paragraph;
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
|
@ -900,8 +900,6 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
|
|||
))
|
||||
}
|
||||
|
||||
const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
|
||||
|
||||
enum Container {
|
||||
Blockquote,
|
||||
List(usize),
|
||||
|
|
@ -966,6 +964,70 @@ fn check_for_code_clusters<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
struct CodeTags {
|
||||
no_run: bool,
|
||||
ignore: bool,
|
||||
compile_fail: bool,
|
||||
|
||||
rust: bool,
|
||||
}
|
||||
|
||||
impl Default for CodeTags {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
no_run: false,
|
||||
ignore: false,
|
||||
compile_fail: false,
|
||||
|
||||
rust: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeTags {
|
||||
/// Based on <https://github.com/rust-lang/rust/blob/1.90.0/src/librustdoc/html/markdown.rs#L1169>
|
||||
fn parse(lang: &str) -> Self {
|
||||
let mut tags = Self::default();
|
||||
|
||||
let mut seen_rust_tags = false;
|
||||
let mut seen_other_tags = false;
|
||||
for item in lang.split([',', ' ', '\t']) {
|
||||
match item.trim() {
|
||||
"" => {},
|
||||
"rust" => {
|
||||
tags.rust = true;
|
||||
seen_rust_tags = true;
|
||||
},
|
||||
"ignore" => {
|
||||
tags.ignore = true;
|
||||
seen_rust_tags = !seen_other_tags;
|
||||
},
|
||||
"no_run" => {
|
||||
tags.no_run = true;
|
||||
seen_rust_tags = !seen_other_tags;
|
||||
},
|
||||
"should_panic" => seen_rust_tags = !seen_other_tags,
|
||||
"compile_fail" => {
|
||||
tags.compile_fail = true;
|
||||
seen_rust_tags = !seen_other_tags || seen_rust_tags;
|
||||
},
|
||||
"test_harness" | "standalone_crate" => {
|
||||
seen_rust_tags = !seen_other_tags || seen_rust_tags;
|
||||
},
|
||||
_ if item.starts_with("ignore-") => seen_rust_tags = true,
|
||||
_ if item.starts_with("edition") => {},
|
||||
_ => seen_other_tags = true,
|
||||
}
|
||||
}
|
||||
|
||||
tags.rust &= seen_rust_tags || !seen_other_tags;
|
||||
|
||||
tags
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks parsed documentation.
|
||||
/// This walks the "events" (think sections of markdown) produced by `pulldown_cmark`,
|
||||
/// so lints here will generally access that information.
|
||||
|
|
@ -981,14 +1043,10 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
|
|||
) -> DocHeaders {
|
||||
// true if a safety header was found
|
||||
let mut headers = DocHeaders::default();
|
||||
let mut in_code = false;
|
||||
let mut code = None;
|
||||
let mut in_link = None;
|
||||
let mut in_heading = false;
|
||||
let mut in_footnote_definition = false;
|
||||
let mut is_rust = false;
|
||||
let mut no_test = false;
|
||||
let mut ignore = false;
|
||||
let mut edition = None;
|
||||
let mut ticks_unbalanced = false;
|
||||
let mut text_to_check: Vec<(CowStr<'_>, Range<usize>, isize)> = Vec::new();
|
||||
let mut paragraph_range = 0..0;
|
||||
|
|
@ -1048,31 +1106,12 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
|
|||
containers.pop();
|
||||
},
|
||||
Start(CodeBlock(ref kind)) => {
|
||||
in_code = true;
|
||||
if let CodeBlockKind::Fenced(lang) = kind {
|
||||
for item in lang.split(',') {
|
||||
if item == "ignore" {
|
||||
is_rust = false;
|
||||
break;
|
||||
} else if item == "no_test" {
|
||||
no_test = true;
|
||||
} else if item == "no_run" || item == "compile_fail" {
|
||||
ignore = true;
|
||||
}
|
||||
if let Some(stripped) = item.strip_prefix("edition") {
|
||||
is_rust = true;
|
||||
edition = stripped.parse::<Edition>().ok();
|
||||
} else if item.is_empty() || RUST_CODE.contains(&item) {
|
||||
is_rust = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
End(TagEnd::CodeBlock) => {
|
||||
in_code = false;
|
||||
is_rust = false;
|
||||
ignore = false;
|
||||
code = Some(match kind {
|
||||
CodeBlockKind::Indented => CodeTags::default(),
|
||||
CodeBlockKind::Fenced(lang) => CodeTags::parse(lang),
|
||||
});
|
||||
},
|
||||
End(TagEnd::CodeBlock) => code = None,
|
||||
Start(Link { dest_url, .. }) => in_link = Some(dest_url),
|
||||
End(TagEnd::Link) => in_link = None,
|
||||
Start(Heading { .. } | Paragraph | Item) => {
|
||||
|
|
@ -1182,7 +1221,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
|
|||
paragraph_range.end = range.end;
|
||||
let range_ = range.clone();
|
||||
ticks_unbalanced |= text.contains('`')
|
||||
&& !in_code
|
||||
&& code.is_none()
|
||||
&& doc[range.clone()].bytes().enumerate().any(|(i, c)| {
|
||||
// scan the markdown source code bytes for backquotes that aren't preceded by backslashes
|
||||
// - use bytes, instead of chars, to avoid utf8 decoding overhead (special chars are ascii)
|
||||
|
|
@ -1205,10 +1244,14 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
|
|||
headers.safety |= in_heading && trimmed_text == "Implementation Safety";
|
||||
headers.errors |= in_heading && trimmed_text == "Errors";
|
||||
headers.panics |= in_heading && trimmed_text == "Panics";
|
||||
if in_code {
|
||||
if is_rust && !no_test {
|
||||
let edition = edition.unwrap_or_else(|| cx.tcx.sess.edition());
|
||||
needless_doctest_main::check(cx, &text, edition, range.clone(), fragments, ignore);
|
||||
|
||||
if let Some(tags) = code {
|
||||
if tags.rust && !tags.compile_fail && !tags.ignore {
|
||||
needless_doctest_main::check(cx, &text, range.start, fragments);
|
||||
|
||||
if !tags.no_run {
|
||||
test_attr_in_doctest::check(cx, &text, range.start, fragments);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if in_link.is_some() {
|
||||
|
|
|
|||
|
|
@ -1,160 +1,63 @@
|
|||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
use std::{io, thread};
|
||||
|
||||
use crate::doc::{NEEDLESS_DOCTEST_MAIN, TEST_ATTR_IN_DOCTEST};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_ast::{CoroutineKind, Fn, FnRetTy, Item, ItemKind};
|
||||
use rustc_errors::emitter::HumanEmitter;
|
||||
use rustc_errors::{AutoStream, Diag, DiagCtxt};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_parse::lexer::StripTokens;
|
||||
use rustc_parse::new_parser_from_source_str;
|
||||
use rustc_parse::parser::ForceCollect;
|
||||
use rustc_session::parse::ParseSess;
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::source_map::{FilePathMapping, SourceMap};
|
||||
use rustc_span::{FileName, Ident, Pos, sym};
|
||||
|
||||
use super::Fragments;
|
||||
use crate::doc::NEEDLESS_DOCTEST_MAIN;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::tokenize_with_text;
|
||||
use rustc_lexer::TokenKind;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::InnerSpan;
|
||||
|
||||
fn get_test_spans(item: &Item, ident: Ident, test_attr_spans: &mut Vec<Range<usize>>) {
|
||||
test_attr_spans.extend(
|
||||
item.attrs
|
||||
.iter()
|
||||
.find(|attr| attr.has_name(sym::test))
|
||||
.map(|attr| attr.span.lo().to_usize()..ident.span.hi().to_usize()),
|
||||
);
|
||||
fn returns_unit<'a>(mut tokens: impl Iterator<Item = (TokenKind, &'a str, InnerSpan)>) -> bool {
|
||||
let mut next = || tokens.next().map_or(TokenKind::Whitespace, |(kind, ..)| kind);
|
||||
|
||||
match next() {
|
||||
// {
|
||||
TokenKind::OpenBrace => true,
|
||||
// - > ( ) {
|
||||
TokenKind::Minus => {
|
||||
next() == TokenKind::Gt
|
||||
&& next() == TokenKind::OpenParen
|
||||
&& next() == TokenKind::CloseParen
|
||||
&& next() == TokenKind::OpenBrace
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(
|
||||
cx: &LateContext<'_>,
|
||||
text: &str,
|
||||
edition: Edition,
|
||||
range: Range<usize>,
|
||||
fragments: Fragments<'_>,
|
||||
ignore: bool,
|
||||
) {
|
||||
// return whether the code contains a needless `fn main` plus a vector of byte position ranges
|
||||
// of all `#[test]` attributes in not ignored code examples
|
||||
fn check_code_sample(code: String, edition: Edition, ignore: bool) -> (bool, Vec<Range<usize>>) {
|
||||
rustc_driver::catch_fatal_errors(|| {
|
||||
rustc_span::create_session_globals_then(edition, &[], None, || {
|
||||
let mut test_attr_spans = vec![];
|
||||
let filename = FileName::anon_source_code(&code);
|
||||
|
||||
let translator = rustc_driver::default_translator();
|
||||
let emitter = HumanEmitter::new(AutoStream::never(Box::new(io::sink())), translator);
|
||||
let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings();
|
||||
#[expect(clippy::arc_with_non_send_sync)] // `Arc` is expected by with_dcx
|
||||
let sm = Arc::new(SourceMap::new(FilePathMapping::empty()));
|
||||
let psess = ParseSess::with_dcx(dcx, sm);
|
||||
|
||||
let mut parser =
|
||||
match new_parser_from_source_str(&psess, filename, code, StripTokens::ShebangAndFrontmatter) {
|
||||
Ok(p) => p,
|
||||
Err(errs) => {
|
||||
errs.into_iter().for_each(Diag::cancel);
|
||||
return (false, test_attr_spans);
|
||||
},
|
||||
};
|
||||
|
||||
let mut relevant_main_found = false;
|
||||
let mut eligible = true;
|
||||
loop {
|
||||
match parser.parse_item(ForceCollect::No) {
|
||||
Ok(Some(item)) => match &item.kind {
|
||||
ItemKind::Fn(box Fn {
|
||||
ident,
|
||||
sig,
|
||||
body: Some(block),
|
||||
..
|
||||
}) if ident.name == sym::main => {
|
||||
if !ignore {
|
||||
get_test_spans(&item, *ident, &mut test_attr_spans);
|
||||
}
|
||||
|
||||
let is_async = matches!(sig.header.coroutine_kind, Some(CoroutineKind::Async { .. }));
|
||||
let returns_nothing = match &sig.decl.output {
|
||||
FnRetTy::Default(..) => true,
|
||||
FnRetTy::Ty(ty) if ty.kind.is_unit() => true,
|
||||
FnRetTy::Ty(_) => false,
|
||||
};
|
||||
|
||||
if returns_nothing && !is_async && !block.stmts.is_empty() {
|
||||
// This main function should be linted, but only if there are no other functions
|
||||
relevant_main_found = true;
|
||||
} else {
|
||||
// This main function should not be linted, we're done
|
||||
eligible = false;
|
||||
}
|
||||
},
|
||||
// Another function was found; this case is ignored for needless_doctest_main
|
||||
ItemKind::Fn(fn_) => {
|
||||
eligible = false;
|
||||
if ignore {
|
||||
// If ignore is active invalidating one lint,
|
||||
// and we already found another function thus
|
||||
// invalidating the other one, we have no
|
||||
// business continuing.
|
||||
return (false, test_attr_spans);
|
||||
}
|
||||
get_test_spans(&item, fn_.ident, &mut test_attr_spans);
|
||||
},
|
||||
// Tests with one of these items are ignored
|
||||
ItemKind::Static(..)
|
||||
| ItemKind::Const(..)
|
||||
| ItemKind::ExternCrate(..)
|
||||
| ItemKind::ForeignMod(..) => {
|
||||
eligible = false;
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
Ok(None) => break,
|
||||
Err(e) => {
|
||||
// See issue #15041. When calling `.cancel()` on the `Diag`, Clippy will unexpectedly panic
|
||||
// when the `Diag` is unwinded. Meanwhile, we can just call `.emit()`, since the `DiagCtxt`
|
||||
// is just a sink, nothing will be printed.
|
||||
e.emit();
|
||||
return (false, test_attr_spans);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
(relevant_main_found & eligible, test_attr_spans)
|
||||
})
|
||||
})
|
||||
.ok()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
let trailing_whitespace = text.len() - text.trim_end().len();
|
||||
|
||||
// We currently only test for "fn main". Checking for the real
|
||||
// entrypoint (with tcx.entry_fn(())) in each block would be unnecessarily
|
||||
// expensive, as those are probably intended and relevant. Same goes for
|
||||
// macros and other weird ways of declaring a main function.
|
||||
//
|
||||
// Also, as we only check for attribute names and don't do macro expansion,
|
||||
// we can check only for #[test]
|
||||
|
||||
if !((text.contains("main") && text.contains("fn")) || text.contains("#[test]")) {
|
||||
pub fn check(cx: &LateContext<'_>, text: &str, offset: usize, fragments: Fragments<'_>) {
|
||||
if !text.contains("main") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Because of the global session, we need to create a new session in a different thread with
|
||||
// the edition we need.
|
||||
let text = text.to_owned();
|
||||
let (has_main, test_attr_spans) = thread::spawn(move || check_code_sample(text, edition, ignore))
|
||||
.join()
|
||||
.expect("thread::spawn failed");
|
||||
if has_main && let Some(span) = fragments.span(cx, range.start..range.end - trailing_whitespace) {
|
||||
span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
|
||||
}
|
||||
for span in test_attr_spans {
|
||||
let span = (range.start + span.start)..(range.start + span.end);
|
||||
if let Some(span) = fragments.span(cx, span) {
|
||||
span_lint(cx, TEST_ATTR_IN_DOCTEST, span, "unit tests in doctest are not executed");
|
||||
let mut tokens = tokenize_with_text(text).filter(|&(kind, ..)| {
|
||||
!matches!(
|
||||
kind,
|
||||
TokenKind::Whitespace | TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }
|
||||
)
|
||||
});
|
||||
if let Some((TokenKind::Ident, "fn", fn_span)) = tokens.next()
|
||||
&& let Some((TokenKind::Ident, "main", main_span)) = tokens.next()
|
||||
&& let Some((TokenKind::OpenParen, ..)) = tokens.next()
|
||||
&& let Some((TokenKind::CloseParen, ..)) = tokens.next()
|
||||
&& returns_unit(&mut tokens)
|
||||
{
|
||||
let mut depth = 1;
|
||||
for (kind, ..) in &mut tokens {
|
||||
match kind {
|
||||
TokenKind::OpenBrace => depth += 1,
|
||||
TokenKind::CloseBrace => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
if tokens.next().is_none()
|
||||
&& let Some(span) = fragments.span(cx, fn_span.start + offset..main_span.end + offset)
|
||||
{
|
||||
span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
34
clippy_lints/src/doc/test_attr_in_doctest.rs
Normal file
34
clippy_lints/src/doc/test_attr_in_doctest.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use super::Fragments;
|
||||
use crate::doc::TEST_ATTR_IN_DOCTEST;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::tokenize_with_text;
|
||||
use rustc_lexer::TokenKind;
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
pub fn check(cx: &LateContext<'_>, text: &str, offset: usize, fragments: Fragments<'_>) {
|
||||
if !text.contains("#[test]") {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut spans = Vec::new();
|
||||
let mut tokens = tokenize_with_text(text).filter(|&(kind, ..)| kind != TokenKind::Whitespace);
|
||||
while let Some(token) = tokens.next() {
|
||||
if let (TokenKind::Pound, _, pound_span) = token
|
||||
&& let Some((TokenKind::OpenBracket, ..)) = tokens.next()
|
||||
&& let Some((TokenKind::Ident, "test", _)) = tokens.next()
|
||||
&& let Some((TokenKind::CloseBracket, _, close_span)) = tokens.next()
|
||||
&& let Some(span) = fragments.span(cx, pound_span.start + offset..close_span.end + offset)
|
||||
{
|
||||
spans.push(span);
|
||||
}
|
||||
}
|
||||
|
||||
if !spans.is_empty() {
|
||||
span_lint(
|
||||
cx,
|
||||
TEST_ATTR_IN_DOCTEST,
|
||||
spans,
|
||||
"unit tests in doctest are not executed",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::{HasSession, snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::source::{HasSession, SpanRangeExt, snippet_with_applicability, snippet_with_context};
|
||||
use rustc_ast::ast::{Expr, ExprKind, MethodCall};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
|
|
@ -47,8 +47,12 @@ impl EarlyLintPass for DoubleParens {
|
|||
// ^^^^^^ expr
|
||||
// ^^^^ inner
|
||||
ExprKind::Paren(inner) if matches!(inner.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => {
|
||||
// suggest removing the outer parens
|
||||
if expr.span.eq_ctxt(inner.span) {
|
||||
if expr.span.eq_ctxt(inner.span)
|
||||
&& !expr.span.in_external_macro(cx.sess().source_map())
|
||||
&& check_source(cx, inner)
|
||||
{
|
||||
// suggest removing the outer parens
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
// We don't need to use `snippet_with_context` here, because:
|
||||
// - if `inner`'s `ctxt` is from macro, we don't lint in the first place (see the check above)
|
||||
|
|
@ -74,8 +78,12 @@ impl EarlyLintPass for DoubleParens {
|
|||
if let [arg] = &**args
|
||||
&& let ExprKind::Paren(inner) = &arg.kind =>
|
||||
{
|
||||
// suggest removing the inner parens
|
||||
if expr.span.eq_ctxt(arg.span) {
|
||||
if expr.span.eq_ctxt(arg.span)
|
||||
&& !arg.span.in_external_macro(cx.sess().source_map())
|
||||
&& check_source(cx, arg)
|
||||
{
|
||||
// suggest removing the inner parens
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let sugg = snippet_with_context(cx.sess(), inner.span, arg.span.ctxt(), "_", &mut applicability).0;
|
||||
span_lint_and_sugg(
|
||||
|
|
@ -93,3 +101,22 @@ impl EarlyLintPass for DoubleParens {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that the span does indeed look like `( (..) )`
|
||||
fn check_source(cx: &EarlyContext<'_>, inner: &Expr) -> bool {
|
||||
if let Some(sfr) = inner.span.get_source_range(cx)
|
||||
// this is the same as `SourceFileRange::as_str`, but doesn't apply the range right away, because
|
||||
// we're interested in the source code outside it
|
||||
&& let Some(src) = sfr.sf.src.as_ref().map(|src| src.as_str())
|
||||
&& let Some((start, outer_after_inner)) = src.split_at_checked(sfr.range.end)
|
||||
&& let Some((outer_before_inner, inner)) = start.split_at_checked(sfr.range.start)
|
||||
&& outer_before_inner.trim_end().ends_with('(')
|
||||
&& inner.starts_with('(')
|
||||
&& inner.ends_with(')')
|
||||
&& outer_after_inner.trim_start().starts_with(')')
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::span_contains_cfg;
|
||||
use rustc_hir::{Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
|
@ -25,10 +26,6 @@ declare_clippy_lint! {
|
|||
/// matched to mark code unreachable. If the field is not visible, then the struct
|
||||
/// acts like any other struct with private fields.
|
||||
///
|
||||
/// * If the enum has no variants only because all variants happen to be
|
||||
/// [disabled by conditional compilation][cfg], then it would be appropriate
|
||||
/// to allow the lint, with `#[allow(empty_enum)]`.
|
||||
///
|
||||
/// For further information, visit
|
||||
/// [the never type’s documentation][`!`].
|
||||
///
|
||||
|
|
@ -53,24 +50,24 @@ declare_clippy_lint! {
|
|||
/// [newtype]: https://doc.rust-lang.org/book/ch19-04-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction
|
||||
/// [visibility]: https://doc.rust-lang.org/reference/visibility-and-privacy.html
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub EMPTY_ENUM,
|
||||
pub EMPTY_ENUMS,
|
||||
pedantic,
|
||||
"enum with no variants"
|
||||
}
|
||||
|
||||
declare_lint_pass!(EmptyEnum => [EMPTY_ENUM]);
|
||||
declare_lint_pass!(EmptyEnums => [EMPTY_ENUMS]);
|
||||
|
||||
impl LateLintPass<'_> for EmptyEnum {
|
||||
impl LateLintPass<'_> for EmptyEnums {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
if let ItemKind::Enum(..) = item.kind
|
||||
if let ItemKind::Enum(.., def) = item.kind
|
||||
&& def.variants.is_empty()
|
||||
// Only suggest the `never_type` if the feature is enabled
|
||||
&& cx.tcx.features().never_type()
|
||||
&& let Some(adt) = cx.tcx.type_of(item.owner_id).instantiate_identity().ty_adt_def()
|
||||
&& adt.variants().is_empty()
|
||||
&& !span_contains_cfg(cx, item.span)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
EMPTY_ENUM,
|
||||
EMPTY_ENUMS,
|
||||
item.span,
|
||||
"enum with no variants",
|
||||
None,
|
||||
|
|
@ -157,6 +157,11 @@ impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> {
|
|||
let spans = if explicit_params.len() == extra_params.len() {
|
||||
vec![self.generics.span] // Remove the entire list of generics
|
||||
} else {
|
||||
// 1. Start from the last extra param
|
||||
// 2. While the params preceding it are also extra, construct spans going from the current param to
|
||||
// the comma before it
|
||||
// 3. Once this chain of extra params stops, switch to constructing spans going from the current
|
||||
// param to the comma _after_ it
|
||||
let mut end: Option<LocalDefId> = None;
|
||||
extra_params
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ declare_clippy_lint! {
|
|||
/// } if bar { // looks like an `else` is missing here
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.90.0"]
|
||||
#[clippy::version = "1.91.0"]
|
||||
pub POSSIBLE_MISSING_ELSE,
|
||||
suspicious,
|
||||
"possibly missing `else`"
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
|||
use clippy_utils::msrvs::Msrv;
|
||||
use clippy_utils::{is_in_const_context, is_in_test};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, RustcVersion, StabilityLevel, StableSince};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::{CrateNum, DefId};
|
||||
use rustc_span::{ExpnKind, Span, sym};
|
||||
|
|
@ -83,6 +82,10 @@ pub struct IncompatibleMsrv {
|
|||
availability_cache: FxHashMap<(DefId, bool), Availability>,
|
||||
check_in_tests: bool,
|
||||
core_crate: Option<CrateNum>,
|
||||
|
||||
// The most recently called path. Used to skip checking the path after it's
|
||||
// been checked when visiting the call expression.
|
||||
called_path: Option<HirId>,
|
||||
}
|
||||
|
||||
impl_lint_pass!(IncompatibleMsrv => [INCOMPATIBLE_MSRV]);
|
||||
|
|
@ -98,6 +101,7 @@ impl IncompatibleMsrv {
|
|||
.iter()
|
||||
.find(|krate| tcx.crate_name(**krate) == sym::core)
|
||||
.copied(),
|
||||
called_path: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +144,14 @@ impl IncompatibleMsrv {
|
|||
}
|
||||
|
||||
/// Emit lint if `def_id`, associated with `node` and `span`, is below the current MSRV.
|
||||
fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, node: HirId, span: Span) {
|
||||
fn emit_lint_if_under_msrv(
|
||||
&mut self,
|
||||
cx: &LateContext<'_>,
|
||||
needs_const: bool,
|
||||
def_id: DefId,
|
||||
node: HirId,
|
||||
span: Span,
|
||||
) {
|
||||
if def_id.is_local() {
|
||||
// We don't check local items since their MSRV is supposed to always be valid.
|
||||
return;
|
||||
|
|
@ -158,10 +169,6 @@ impl IncompatibleMsrv {
|
|||
return;
|
||||
}
|
||||
|
||||
let needs_const = cx.enclosing_body.is_some()
|
||||
&& is_in_const_context(cx)
|
||||
&& matches!(cx.tcx.def_kind(def_id), DefKind::AssocFn | DefKind::Fn);
|
||||
|
||||
if (self.check_in_tests || !is_in_test(cx.tcx, node))
|
||||
&& let Some(current) = self.msrv.current(cx)
|
||||
&& let Availability::Since(version) = self.get_def_id_availability(cx.tcx, def_id, needs_const)
|
||||
|
|
@ -190,13 +197,33 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
|
|||
match expr.kind {
|
||||
ExprKind::MethodCall(_, _, _, span) => {
|
||||
if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
|
||||
self.emit_lint_if_under_msrv(cx, method_did, expr.hir_id, span);
|
||||
self.emit_lint_if_under_msrv(cx, is_in_const_context(cx), method_did, expr.hir_id, span);
|
||||
}
|
||||
},
|
||||
ExprKind::Path(qpath) => {
|
||||
if let Some(path_def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id() {
|
||||
self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, expr.span);
|
||||
}
|
||||
ExprKind::Call(callee, _) if let ExprKind::Path(qpath) = callee.kind => {
|
||||
self.called_path = Some(callee.hir_id);
|
||||
let needs_const = is_in_const_context(cx);
|
||||
let def_id = if let Some(def_id) = cx.qpath_res(&qpath, callee.hir_id).opt_def_id() {
|
||||
def_id
|
||||
} else if needs_const && let ty::FnDef(def_id, _) = *cx.typeck_results().expr_ty(callee).kind() {
|
||||
// Edge case where a function is first assigned then called.
|
||||
// We previously would have warned for the non-const MSRV, when
|
||||
// checking the path, but now that it's called the const MSRV
|
||||
// must also be met.
|
||||
def_id
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
self.emit_lint_if_under_msrv(cx, needs_const, def_id, expr.hir_id, callee.span);
|
||||
},
|
||||
// Desugaring into function calls by the compiler will use `QPath::LangItem` variants. Those should
|
||||
// not be linted as they will not be generated in older compilers if the function is not available,
|
||||
// and the compiler is allowed to call unstable functions.
|
||||
ExprKind::Path(qpath)
|
||||
if let Some(path_def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id()
|
||||
&& self.called_path != Some(expr.hir_id) =>
|
||||
{
|
||||
self.emit_lint_if_under_msrv(cx, false, path_def_id, expr.hir_id, expr.span);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
|
@ -208,7 +235,7 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
|
|||
// `CStr` and `CString` have been moved around but have been available since Rust 1.0.0
|
||||
&& !matches!(cx.tcx.get_diagnostic_name(ty_def_id), Some(sym::cstr_type | sym::cstring_type))
|
||||
{
|
||||
self.emit_lint_if_under_msrv(cx, ty_def_id, hir_ty.hir_id, hir_ty.span);
|
||||
self.emit_lint_if_under_msrv(cx, false, ty_def_id, hir_ty.hir_id, hir_ty.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_ast::BinOpKind;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for the usage of division (`/`) and remainder (`%`) operations
|
||||
/// when performed on any integer types using the default `Div` and `Rem` trait implementations.
|
||||
///
|
||||
/// ### Why restrict this?
|
||||
/// In cryptographic contexts, division can result in timing sidechannel vulnerabilities,
|
||||
/// and needs to be replaced with constant-time code instead (e.g. Barrett reduction).
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let my_div = 10 / 2;
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let my_div = 10 >> 1;
|
||||
/// ```
|
||||
#[clippy::version = "1.79.0"]
|
||||
pub INTEGER_DIVISION_REMAINDER_USED,
|
||||
restriction,
|
||||
"use of disallowed default division and remainder operations"
|
||||
}
|
||||
|
||||
declare_lint_pass!(IntegerDivisionRemainderUsed => [INTEGER_DIVISION_REMAINDER_USED]);
|
||||
|
||||
impl LateLintPass<'_> for IntegerDivisionRemainderUsed {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if let ExprKind::Binary(op, lhs, rhs) = &expr.kind
|
||||
&& let BinOpKind::Div | BinOpKind::Rem = op.node
|
||||
&& let lhs_ty = cx.typeck_results().expr_ty(lhs)
|
||||
&& let rhs_ty = cx.typeck_results().expr_ty(rhs)
|
||||
&& let ty::Int(_) | ty::Uint(_) = lhs_ty.peel_refs().kind()
|
||||
&& let ty::Int(_) | ty::Uint(_) = rhs_ty.peel_refs().kind()
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
INTEGER_DIVISION_REMAINDER_USED,
|
||||
expr.span.source_callsite(),
|
||||
format!("use of {} has been disallowed in this context", op.node.as_str()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
|
||||
use clippy_utils::source::{SpanRangeExt, snippet_with_context};
|
||||
use clippy_utils::sugg::{Sugg, has_enclosing_paren};
|
||||
|
|
@ -10,12 +12,12 @@ use rustc_hir::def::Res;
|
|||
use rustc_hir::def_id::{DefId, DefIdSet};
|
||||
use rustc_hir::{
|
||||
BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind, ImplicitSelfKind,
|
||||
Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy, QPath, TraitItemId,
|
||||
TyKind,
|
||||
Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy, QPath, RustcVersion,
|
||||
StabilityLevel, StableSince, TraitItemId, TyKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, FnSig, Ty};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::symbol::kw;
|
||||
use rustc_span::{Ident, Span, Symbol};
|
||||
|
|
@ -120,7 +122,17 @@ declare_clippy_lint! {
|
|||
"checking `x == \"\"` or `x == []` (or similar) when `.is_empty()` could be used instead"
|
||||
}
|
||||
|
||||
declare_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMPTY]);
|
||||
pub struct LenZero {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
||||
impl_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMPTY]);
|
||||
|
||||
impl LenZero {
|
||||
pub fn new(conf: &'static Conf) -> Self {
|
||||
Self { msrv: conf.msrv }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for LenZero {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
|
|
@ -184,7 +196,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
|
|||
_ => false,
|
||||
}
|
||||
&& !expr.span.from_expansion()
|
||||
&& has_is_empty(cx, lt.init)
|
||||
&& has_is_empty(cx, lt.init, self.msrv)
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
|
|
@ -206,7 +218,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
|
|||
&& cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::PartialEq)
|
||||
&& !expr.span.from_expansion()
|
||||
{
|
||||
check_empty_expr(
|
||||
self.check_empty_expr(
|
||||
cx,
|
||||
expr.span,
|
||||
lhs_expr,
|
||||
|
|
@ -226,29 +238,110 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
|
|||
let actual_span = span_without_enclosing_paren(cx, expr.span);
|
||||
match cmp {
|
||||
BinOpKind::Eq => {
|
||||
check_cmp(cx, actual_span, left, right, "", 0); // len == 0
|
||||
check_cmp(cx, actual_span, right, left, "", 0); // 0 == len
|
||||
self.check_cmp(cx, actual_span, left, right, "", 0); // len == 0
|
||||
self.check_cmp(cx, actual_span, right, left, "", 0); // 0 == len
|
||||
},
|
||||
BinOpKind::Ne => {
|
||||
check_cmp(cx, actual_span, left, right, "!", 0); // len != 0
|
||||
check_cmp(cx, actual_span, right, left, "!", 0); // 0 != len
|
||||
self.check_cmp(cx, actual_span, left, right, "!", 0); // len != 0
|
||||
self.check_cmp(cx, actual_span, right, left, "!", 0); // 0 != len
|
||||
},
|
||||
BinOpKind::Gt => {
|
||||
check_cmp(cx, actual_span, left, right, "!", 0); // len > 0
|
||||
check_cmp(cx, actual_span, right, left, "", 1); // 1 > len
|
||||
self.check_cmp(cx, actual_span, left, right, "!", 0); // len > 0
|
||||
self.check_cmp(cx, actual_span, right, left, "", 1); // 1 > len
|
||||
},
|
||||
BinOpKind::Lt => {
|
||||
check_cmp(cx, actual_span, left, right, "", 1); // len < 1
|
||||
check_cmp(cx, actual_span, right, left, "!", 0); // 0 < len
|
||||
self.check_cmp(cx, actual_span, left, right, "", 1); // len < 1
|
||||
self.check_cmp(cx, actual_span, right, left, "!", 0); // 0 < len
|
||||
},
|
||||
BinOpKind::Ge => check_cmp(cx, actual_span, left, right, "!", 1), // len >= 1
|
||||
BinOpKind::Le => check_cmp(cx, actual_span, right, left, "!", 1), // 1 <= len
|
||||
BinOpKind::Ge => self.check_cmp(cx, actual_span, left, right, "!", 1), // len >= 1
|
||||
BinOpKind::Le => self.check_cmp(cx, actual_span, right, left, "!", 1), // 1 <= len
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LenZero {
|
||||
fn check_cmp(
|
||||
&self,
|
||||
cx: &LateContext<'_>,
|
||||
span: Span,
|
||||
method: &Expr<'_>,
|
||||
lit: &Expr<'_>,
|
||||
op: &str,
|
||||
compare_to: u32,
|
||||
) {
|
||||
if method.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let (&ExprKind::MethodCall(method_path, receiver, [], _), ExprKind::Lit(lit)) = (&method.kind, &lit.kind) {
|
||||
// check if we are in an is_empty() method
|
||||
if parent_item_name(cx, method) == Some(sym::is_empty) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.check_len(cx, span, method_path.ident.name, receiver, &lit.node, op, compare_to);
|
||||
} else {
|
||||
self.check_empty_expr(cx, span, method, lit, op);
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn check_len(
|
||||
&self,
|
||||
cx: &LateContext<'_>,
|
||||
span: Span,
|
||||
method_name: Symbol,
|
||||
receiver: &Expr<'_>,
|
||||
lit: &LitKind,
|
||||
op: &str,
|
||||
compare_to: u32,
|
||||
) {
|
||||
if let LitKind::Int(lit, _) = *lit {
|
||||
// check if length is compared to the specified number
|
||||
if lit != u128::from(compare_to) {
|
||||
return;
|
||||
}
|
||||
|
||||
if method_name == sym::len && has_is_empty(cx, receiver, self.msrv) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
LEN_ZERO,
|
||||
span,
|
||||
format!("length comparison to {}", if compare_to == 0 { "zero" } else { "one" }),
|
||||
format!("using `{op}is_empty` is clearer and more explicit"),
|
||||
format!(
|
||||
"{op}{}.is_empty()",
|
||||
snippet_with_context(cx, receiver.span, span.ctxt(), "_", &mut applicability).0,
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_empty_expr(&self, cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Expr<'_>, op: &str) {
|
||||
if (is_empty_array(lit2) || is_empty_string(lit2)) && has_is_empty(cx, lit1, self.msrv) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
let lit1 = peel_ref_operators(cx, lit1);
|
||||
let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_paren();
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
COMPARISON_TO_EMPTY,
|
||||
span,
|
||||
"comparison to empty slice",
|
||||
format!("using `{op}is_empty` is clearer and more explicit"),
|
||||
format!("{op}{lit_str}.is_empty()"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn span_without_enclosing_paren(cx: &LateContext<'_>, span: Span) -> Span {
|
||||
let Some(snippet) = span.get_source_text(cx) else {
|
||||
return span;
|
||||
|
|
@ -513,75 +606,6 @@ fn check_for_is_empty(
|
|||
}
|
||||
}
|
||||
|
||||
fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) {
|
||||
if method.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let (&ExprKind::MethodCall(method_path, receiver, [], _), ExprKind::Lit(lit)) = (&method.kind, &lit.kind) {
|
||||
// check if we are in an is_empty() method
|
||||
if parent_item_name(cx, method) == Some(sym::is_empty) {
|
||||
return;
|
||||
}
|
||||
|
||||
check_len(cx, span, method_path.ident.name, receiver, &lit.node, op, compare_to);
|
||||
} else {
|
||||
check_empty_expr(cx, span, method, lit, op);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_len(
|
||||
cx: &LateContext<'_>,
|
||||
span: Span,
|
||||
method_name: Symbol,
|
||||
receiver: &Expr<'_>,
|
||||
lit: &LitKind,
|
||||
op: &str,
|
||||
compare_to: u32,
|
||||
) {
|
||||
if let LitKind::Int(lit, _) = *lit {
|
||||
// check if length is compared to the specified number
|
||||
if lit != u128::from(compare_to) {
|
||||
return;
|
||||
}
|
||||
|
||||
if method_name == sym::len && has_is_empty(cx, receiver) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
LEN_ZERO,
|
||||
span,
|
||||
format!("length comparison to {}", if compare_to == 0 { "zero" } else { "one" }),
|
||||
format!("using `{op}is_empty` is clearer and more explicit"),
|
||||
format!(
|
||||
"{op}{}.is_empty()",
|
||||
snippet_with_context(cx, receiver.span, span.ctxt(), "_", &mut applicability).0,
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Expr<'_>, op: &str) {
|
||||
if (is_empty_array(lit2) || is_empty_string(lit2)) && has_is_empty(cx, lit1) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
let lit1 = peel_ref_operators(cx, lit1);
|
||||
let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_paren();
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
COMPARISON_TO_EMPTY,
|
||||
span,
|
||||
"comparison to empty slice",
|
||||
format!("using `{op}is_empty` is clearer and more explicit"),
|
||||
format!("{op}{lit_str}.is_empty()"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty_string(expr: &Expr<'_>) -> bool {
|
||||
if let ExprKind::Lit(lit) = expr.kind
|
||||
&& let LitKind::Str(lit, _) = lit.node
|
||||
|
|
@ -600,45 +624,59 @@ fn is_empty_array(expr: &Expr<'_>) -> bool {
|
|||
}
|
||||
|
||||
/// Checks if this type has an `is_empty` method.
|
||||
fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) -> bool {
|
||||
/// Gets an `AssocItem` and return true if it matches `is_empty(self)`.
|
||||
fn is_is_empty(cx: &LateContext<'_>, item: &ty::AssocItem) -> bool {
|
||||
fn is_is_empty_and_stable(cx: &LateContext<'_>, item: &ty::AssocItem, msrv: Msrv) -> bool {
|
||||
if item.is_fn() {
|
||||
let sig = cx.tcx.fn_sig(item.def_id).skip_binder();
|
||||
let ty = sig.skip_binder();
|
||||
ty.inputs().len() == 1
|
||||
&& cx.tcx.lookup_stability(item.def_id).is_none_or(|stability| {
|
||||
if let StabilityLevel::Stable { since, .. } = stability.level {
|
||||
let version = match since {
|
||||
StableSince::Version(version) => version,
|
||||
StableSince::Current => RustcVersion::CURRENT,
|
||||
StableSince::Err(_) => return false,
|
||||
};
|
||||
|
||||
msrv.meets(cx, version)
|
||||
} else {
|
||||
// Unstable fn, check if the feature is enabled.
|
||||
cx.tcx.features().enabled(stability.feature) && msrv.current(cx).is_none()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks the inherent impl's items for an `is_empty(self)` method.
|
||||
fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId) -> bool {
|
||||
fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId, msrv: Msrv) -> bool {
|
||||
cx.tcx.inherent_impls(id).iter().any(|imp| {
|
||||
cx.tcx
|
||||
.associated_items(*imp)
|
||||
.filter_by_name_unhygienic(sym::is_empty)
|
||||
.any(|item| is_is_empty(cx, item))
|
||||
.any(|item| is_is_empty_and_stable(cx, item, msrv))
|
||||
})
|
||||
}
|
||||
|
||||
fn ty_has_is_empty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, depth: usize) -> bool {
|
||||
fn ty_has_is_empty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, depth: usize, msrv: Msrv) -> bool {
|
||||
match ty.kind() {
|
||||
ty::Dynamic(tt, ..) => tt.principal().is_some_and(|principal| {
|
||||
cx.tcx
|
||||
.associated_items(principal.def_id())
|
||||
.filter_by_name_unhygienic(sym::is_empty)
|
||||
.any(|item| is_is_empty(cx, item))
|
||||
.any(|item| is_is_empty_and_stable(cx, item, msrv))
|
||||
}),
|
||||
ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id),
|
||||
ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id, msrv),
|
||||
ty::Adt(id, _) => {
|
||||
has_is_empty_impl(cx, id.did())
|
||||
has_is_empty_impl(cx, id.did(), msrv)
|
||||
|| (cx.tcx.recursion_limit().value_within_limit(depth)
|
||||
&& cx.tcx.get_diagnostic_item(sym::Deref).is_some_and(|deref_id| {
|
||||
implements_trait(cx, ty, deref_id, &[])
|
||||
&& cx
|
||||
.get_associated_type(ty, deref_id, sym::Target)
|
||||
.is_some_and(|deref_ty| ty_has_is_empty(cx, deref_ty, depth + 1))
|
||||
.is_some_and(|deref_ty| ty_has_is_empty(cx, deref_ty, depth + 1, msrv))
|
||||
}))
|
||||
},
|
||||
ty::Array(..) | ty::Slice(..) | ty::Str => true,
|
||||
|
|
@ -646,5 +684,5 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
ty_has_is_empty(cx, cx.typeck_results().expr_ty(expr).peel_refs(), 0)
|
||||
ty_has_is_empty(cx, cx.typeck_results().expr_ty(expr).peel_refs(), 0, msrv)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ extern crate rustc_arena;
|
|||
extern crate rustc_ast;
|
||||
extern crate rustc_ast_pretty;
|
||||
extern crate rustc_data_structures;
|
||||
extern crate rustc_driver;
|
||||
extern crate rustc_errors;
|
||||
extern crate rustc_hir;
|
||||
extern crate rustc_hir_analysis;
|
||||
|
|
@ -43,7 +42,6 @@ extern crate rustc_infer;
|
|||
extern crate rustc_lexer;
|
||||
extern crate rustc_lint;
|
||||
extern crate rustc_middle;
|
||||
extern crate rustc_parse;
|
||||
extern crate rustc_parse_format;
|
||||
extern crate rustc_resolve;
|
||||
extern crate rustc_session;
|
||||
|
|
@ -117,7 +115,7 @@ mod drop_forget_ref;
|
|||
mod duplicate_mod;
|
||||
mod else_if_without_else;
|
||||
mod empty_drop;
|
||||
mod empty_enum;
|
||||
mod empty_enums;
|
||||
mod empty_line_after;
|
||||
mod empty_with_brackets;
|
||||
mod endian_bytes;
|
||||
|
|
@ -171,8 +169,6 @@ mod inherent_to_string;
|
|||
mod init_numbered_fields;
|
||||
mod inline_fn_without_body;
|
||||
mod int_plus_one;
|
||||
mod integer_division_remainder_used;
|
||||
mod invalid_upcast_comparisons;
|
||||
mod item_name_repetitions;
|
||||
mod items_after_statements;
|
||||
mod items_after_test_module;
|
||||
|
|
@ -191,7 +187,6 @@ mod let_if_seq;
|
|||
mod let_underscore;
|
||||
mod let_with_type_underscore;
|
||||
mod lifetimes;
|
||||
mod lines_filter_map_ok;
|
||||
mod literal_representation;
|
||||
mod literal_string_with_formatting_args;
|
||||
mod loops;
|
||||
|
|
@ -203,7 +198,6 @@ mod manual_assert;
|
|||
mod manual_async_fn;
|
||||
mod manual_bits;
|
||||
mod manual_clamp;
|
||||
mod manual_div_ceil;
|
||||
mod manual_float_methods;
|
||||
mod manual_hash_one;
|
||||
mod manual_ignore_case_cmp;
|
||||
|
|
@ -255,7 +249,7 @@ mod needless_borrows_for_generic_args;
|
|||
mod needless_continue;
|
||||
mod needless_else;
|
||||
mod needless_for_each;
|
||||
mod needless_if;
|
||||
mod needless_ifs;
|
||||
mod needless_late_init;
|
||||
mod needless_maybe_sized;
|
||||
mod needless_parens_on_range_literals;
|
||||
|
|
@ -481,7 +475,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
|||
store.register_late_pass(|_| Box::new(mut_mut::MutMut::default()));
|
||||
store.register_late_pass(|_| Box::new(unnecessary_mut_passed::UnnecessaryMutPassed));
|
||||
store.register_late_pass(|_| Box::<significant_drop_tightening::SignificantDropTightening<'_>>::default());
|
||||
store.register_late_pass(|_| Box::new(len_zero::LenZero));
|
||||
store.register_late_pass(move |_| Box::new(len_zero::LenZero::new(conf)));
|
||||
store.register_late_pass(move |_| Box::new(attrs::Attributes::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(blocks_in_conditions::BlocksInConditions));
|
||||
store.register_late_pass(|_| Box::new(unicode::Unicode));
|
||||
|
|
@ -542,8 +536,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
|||
store.register_late_pass(|_| Box::new(derive::Derive));
|
||||
store.register_late_pass(move |_| Box::new(derivable_impls::DerivableImpls::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(drop_forget_ref::DropForgetRef));
|
||||
store.register_late_pass(|_| Box::new(empty_enum::EmptyEnum));
|
||||
store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
|
||||
store.register_late_pass(|_| Box::new(empty_enums::EmptyEnums));
|
||||
store.register_late_pass(|_| Box::<regex::Regex>::default());
|
||||
store.register_late_pass(move |tcx| Box::new(ifs::CopyAndPaste::new(tcx, conf)));
|
||||
store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator));
|
||||
|
|
@ -743,7 +736,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
|||
store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct));
|
||||
store.register_late_pass(move |_| Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(conf)));
|
||||
store.register_late_pass(move |_| Box::new(lines_filter_map_ok::LinesFilterMapOk::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule));
|
||||
store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation::new(conf)));
|
||||
store.register_early_pass(move || Box::new(excessive_nesting::ExcessiveNesting::new(conf)));
|
||||
|
|
@ -755,7 +747,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
|||
store.register_late_pass(|_| Box::new(endian_bytes::EndianBytes));
|
||||
store.register_late_pass(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations));
|
||||
store.register_late_pass(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync));
|
||||
store.register_late_pass(|_| Box::new(needless_if::NeedlessIf));
|
||||
store.register_late_pass(|_| Box::new(needless_ifs::NeedlessIfs));
|
||||
store.register_late_pass(move |_| Box::new(min_ident_chars::MinIdentChars::new(conf)));
|
||||
store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
|
||||
|
|
@ -798,7 +790,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
|||
store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations));
|
||||
store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects));
|
||||
store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed));
|
||||
store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf)));
|
||||
store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf)));
|
||||
store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers));
|
||||
|
|
@ -807,7 +798,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
|||
store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest));
|
||||
store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses));
|
||||
store.register_late_pass(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock));
|
||||
store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf)));
|
||||
store.register_late_pass(move |_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions));
|
||||
store.register_late_pass(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg));
|
||||
|
|
|
|||
|
|
@ -856,89 +856,49 @@ fn elision_suggestions(
|
|||
.filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !elidable_lts
|
||||
.iter()
|
||||
.all(|lt| explicit_params.iter().any(|param| param.def_id == *lt))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut suggestions = if elidable_lts.is_empty() {
|
||||
vec![]
|
||||
} else if elidable_lts.len() == explicit_params.len() {
|
||||
let mut suggestions = if elidable_lts.len() == explicit_params.len() {
|
||||
// if all the params are elided remove the whole generic block
|
||||
//
|
||||
// fn x<'a>() {}
|
||||
// ^^^^
|
||||
vec![(generics.span, String::new())]
|
||||
} else {
|
||||
match &explicit_params[..] {
|
||||
// no params, nothing to elide
|
||||
[] => unreachable!("handled by `elidable_lts.is_empty()`"),
|
||||
[param] => {
|
||||
if elidable_lts.contains(¶m.def_id) {
|
||||
unreachable!("handled by `elidable_lts.len() == explicit_params.len()`")
|
||||
// 1. Start from the last elidable lifetime
|
||||
// 2. While the lifetimes preceding it are also elidable, construct spans going from the current
|
||||
// lifetime to the comma before it
|
||||
// 3. Once this chain of elidable lifetimes stops, switch to constructing spans going from the
|
||||
// current lifetime to the comma _after_ it
|
||||
let mut end: Option<LocalDefId> = None;
|
||||
elidable_lts
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|&id| {
|
||||
let (idx, param) = explicit_params.iter().find_position(|param| param.def_id == id)?;
|
||||
|
||||
let span = if let Some(next) = explicit_params.get(idx + 1)
|
||||
&& end != Some(next.def_id)
|
||||
{
|
||||
// Extend the current span forward, up until the next param in the list.
|
||||
// fn x<'prev, 'a, 'next>() {}
|
||||
// ^^^^
|
||||
param.span.until(next.span)
|
||||
} else {
|
||||
unreachable!("handled by `elidable_lts.is_empty()`")
|
||||
}
|
||||
},
|
||||
[_, _, ..] => {
|
||||
// Given a list like `<'a, 'b, 'c, 'd, ..>`,
|
||||
//
|
||||
// If there is a cluster of elidable lifetimes at the beginning, say `'a` and `'b`, we should
|
||||
// suggest removing them _and_ the trailing comma. The span for that is `a.span.until(c.span)`:
|
||||
// <'a, 'b, 'c, 'd, ..> => <'a, 'b, 'c, 'd, ..>
|
||||
// ^^ ^^ ^^^^^^^^
|
||||
//
|
||||
// And since we know that `'c` isn't elidable--otherwise it would've been in the cluster--we can go
|
||||
// over all the lifetimes after it, and for each elidable one, add a suggestion spanning the
|
||||
// lifetime itself and the comma before, because each individual suggestion is guaranteed to leave
|
||||
// the list valid:
|
||||
// <.., 'c, 'd, 'e, 'f, 'g, ..> => <.., 'c, 'd, 'e, 'f, 'g, ..>
|
||||
// ^^ ^^ ^^ ^^^^ ^^^^^^^^
|
||||
//
|
||||
// In case there is no such starting cluster, we only need to do the second part of the algorithm:
|
||||
// <'a, 'b, 'c, 'd, 'e, 'f, 'g, ..> => <'a, 'b , 'c, 'd, 'e, 'f, 'g, ..>
|
||||
// ^^ ^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^
|
||||
// Extend the current span back to include the comma following the previous
|
||||
// param. If the span of the next param in the list has already been
|
||||
// extended, we continue the chain. This is why we're iterating in reverse.
|
||||
end = Some(param.def_id);
|
||||
|
||||
// Split off the starting cluster
|
||||
// TODO: use `slice::split_once` once stabilized (github.com/rust-lang/rust/issues/112811):
|
||||
// ```
|
||||
// let Some(split) = explicit_params.split_once(|param| !elidable_lts.contains(¶m.def_id)) else {
|
||||
// // there were no lifetime param that couldn't be elided
|
||||
// unreachable!("handled by `elidable_lts.len() == explicit_params.len()`")
|
||||
// };
|
||||
// match split { /* .. */ }
|
||||
// ```
|
||||
let Some(split_pos) = explicit_params
|
||||
.iter()
|
||||
.position(|param| !elidable_lts.contains(¶m.def_id))
|
||||
else {
|
||||
// there were no lifetime param that couldn't be elided
|
||||
unreachable!("handled by `elidable_lts.len() == explicit_params.len()`")
|
||||
// `idx` will never be 0, else we'd be removing the entire list of generics
|
||||
let prev = explicit_params.get(idx - 1)?;
|
||||
|
||||
// fn x<'prev, 'a>() {}
|
||||
// ^^^^
|
||||
param.span.with_lo(prev.span.hi())
|
||||
};
|
||||
let split = explicit_params
|
||||
.split_at_checked(split_pos)
|
||||
.expect("got `split_pos` from `position` on the same Vec");
|
||||
|
||||
match split {
|
||||
([..], []) => unreachable!("handled by `elidable_lts.len() == explicit_params.len()`"),
|
||||
([], [_]) => unreachable!("handled by `explicit_params.len() == 1`"),
|
||||
(cluster, rest @ [rest_first, ..]) => {
|
||||
// the span for the cluster
|
||||
(cluster.first().map(|fw| fw.span.until(rest_first.span)).into_iter())
|
||||
// the span for the remaining lifetimes (calculations independent of the cluster)
|
||||
.chain(
|
||||
rest.array_windows()
|
||||
.filter(|[_, curr]| elidable_lts.contains(&curr.def_id))
|
||||
.map(|[prev, curr]| curr.span.with_lo(prev.span.hi())),
|
||||
)
|
||||
.map(|sp| (sp, String::new()))
|
||||
.collect()
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
Some((span, String::new()))
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?
|
||||
};
|
||||
|
||||
suggestions.extend(usages.iter().map(|&usage| {
|
||||
|
|
|
|||
|
|
@ -1,141 +0,0 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes};
|
||||
use clippy_utils::sym;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Body, Closure, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Symbol;
|
||||
|
||||
pub struct LinesFilterMapOk {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
||||
impl LinesFilterMapOk {
|
||||
pub fn new(conf: &Conf) -> Self {
|
||||
Self { msrv: conf.msrv }
|
||||
}
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `lines.filter_map(Result::ok)` or `lines.flat_map(Result::ok)`
|
||||
/// when `lines` has type `std::io::Lines`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `Lines` instances might produce a never-ending stream of `Err`, in which case
|
||||
/// `filter_map(Result::ok)` will enter an infinite loop while waiting for an
|
||||
/// `Ok` variant. Calling `next()` once is sufficient to enter the infinite loop,
|
||||
/// even in the absence of explicit loops in the user code.
|
||||
///
|
||||
/// This situation can arise when working with user-provided paths. On some platforms,
|
||||
/// `std::fs::File::open(path)` might return `Ok(fs)` even when `path` is a directory,
|
||||
/// but any later attempt to read from `fs` will return an error.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// This lint suggests replacing `filter_map()` or `flat_map()` applied to a `Lines`
|
||||
/// instance in all cases. There are two cases where the suggestion might not be
|
||||
/// appropriate or necessary:
|
||||
///
|
||||
/// - If the `Lines` instance can never produce any error, or if an error is produced
|
||||
/// only once just before terminating the iterator, using `map_while()` is not
|
||||
/// necessary but will not do any harm.
|
||||
/// - If the `Lines` instance can produce intermittent errors then recover and produce
|
||||
/// successful results, using `map_while()` would stop at the first error.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # use std::{fs::File, io::{self, BufRead, BufReader}};
|
||||
/// # let _ = || -> io::Result<()> {
|
||||
/// let mut lines = BufReader::new(File::open("some-path")?).lines().filter_map(Result::ok);
|
||||
/// // If "some-path" points to a directory, the next statement never terminates:
|
||||
/// let first_line: Option<String> = lines.next();
|
||||
/// # Ok(()) };
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # use std::{fs::File, io::{self, BufRead, BufReader}};
|
||||
/// # let _ = || -> io::Result<()> {
|
||||
/// let mut lines = BufReader::new(File::open("some-path")?).lines().map_while(Result::ok);
|
||||
/// let first_line: Option<String> = lines.next();
|
||||
/// # Ok(()) };
|
||||
/// ```
|
||||
#[clippy::version = "1.70.0"]
|
||||
pub LINES_FILTER_MAP_OK,
|
||||
suspicious,
|
||||
"filtering `std::io::Lines` with `filter_map()`, `flat_map()`, or `flatten()` might cause an infinite loop"
|
||||
}
|
||||
|
||||
impl_lint_pass!(LinesFilterMapOk => [LINES_FILTER_MAP_OK]);
|
||||
|
||||
impl LateLintPass<'_> for LinesFilterMapOk {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if let ExprKind::MethodCall(fm_method, fm_receiver, fm_args, fm_span) = expr.kind
|
||||
&& cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator)
|
||||
&& let fm_method_name = fm_method.ident.name
|
||||
&& matches!(fm_method_name, sym::filter_map | sym::flat_map | sym::flatten)
|
||||
&& cx
|
||||
.typeck_results()
|
||||
.expr_ty_adjusted(fm_receiver)
|
||||
.is_diag_item(cx, sym::IoLines)
|
||||
&& should_lint(cx, fm_args, fm_method_name)
|
||||
&& self.msrv.meets(cx, msrvs::MAP_WHILE)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LINES_FILTER_MAP_OK,
|
||||
fm_span,
|
||||
format!("`{fm_method_name}()` will run forever if the iterator repeatedly produces an `Err`",),
|
||||
|diag| {
|
||||
diag.span_note(
|
||||
fm_receiver.span,
|
||||
"this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error");
|
||||
diag.span_suggestion(
|
||||
fm_span,
|
||||
"replace with",
|
||||
"map_while(Result::ok)",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn should_lint(cx: &LateContext<'_>, args: &[Expr<'_>], method_name: Symbol) -> bool {
|
||||
match args {
|
||||
[] => method_name == sym::flatten,
|
||||
[fm_arg] => {
|
||||
match &fm_arg.kind {
|
||||
// Detect `Result::ok`
|
||||
ExprKind::Path(qpath) => cx
|
||||
.qpath_res(qpath, fm_arg.hir_id)
|
||||
.opt_def_id()
|
||||
.is_some_and(|did| cx.tcx.is_diagnostic_item(sym::result_ok_method, did)),
|
||||
// Detect `|x| x.ok()`
|
||||
ExprKind::Closure(Closure { body, .. }) => {
|
||||
if let Body {
|
||||
params: [param], value, ..
|
||||
} = cx.tcx.hir_body(*body)
|
||||
&& let ExprKind::MethodCall(method, receiver, [], _) = value.kind
|
||||
{
|
||||
method.ident.name == sym::ok
|
||||
&& receiver.res_local_id() == Some(param.pat.hir_id)
|
||||
&& cx
|
||||
.typeck_results()
|
||||
.type_dependent_def_id(value.hir_id)
|
||||
.opt_parent(cx)
|
||||
.opt_impl_ty(cx)
|
||||
.is_diag_item(cx, sym::Result)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
@ -157,10 +157,7 @@ pub(super) fn check<'tcx>(
|
|||
"consider using an iterator and enumerate()",
|
||||
vec![
|
||||
(pat.span, format!("({}, <item>)", ident.name)),
|
||||
(
|
||||
span,
|
||||
format!("{indexed}.{method}().enumerate(){method_1}{method_2}"),
|
||||
),
|
||||
(span, format!("{indexed}.{method}().enumerate(){method_1}{method_2}")),
|
||||
],
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use clippy_utils::source::snippet;
|
|||
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{
|
||||
Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Node, Pat, Stmt, StmtKind, StructTailExpr,
|
||||
Block, Destination, Expr, ExprKind, HirId, InlineAsm, InlineAsmOperand, Node, Pat, Stmt, StmtKind, StructTailExpr,
|
||||
};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{BytePos, Span, sym};
|
||||
|
|
@ -75,12 +75,19 @@ pub(super) fn check<'tcx>(
|
|||
fn contains_any_break_or_continue(block: &Block<'_>) -> bool {
|
||||
for_each_expr_without_closures(block, |e| match e.kind {
|
||||
ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),
|
||||
ExprKind::InlineAsm(asm) if contains_label(asm) => ControlFlow::Break(()),
|
||||
ExprKind::Loop(..) => ControlFlow::Continue(Descend::No),
|
||||
_ => ControlFlow::Continue(Descend::Yes),
|
||||
})
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn contains_label(asm: &InlineAsm<'_>) -> bool {
|
||||
asm.operands
|
||||
.iter()
|
||||
.any(|(op, _span)| matches!(op, InlineAsmOperand::Label { .. }))
|
||||
}
|
||||
|
||||
/// The `never_loop` analysis keeps track of three things:
|
||||
///
|
||||
/// * Has any (reachable) code path hit a `continue` of the main loop?
|
||||
|
|
@ -378,7 +385,15 @@ fn never_loop_expr<'tcx>(
|
|||
InlineAsmOperand::Const { .. } | InlineAsmOperand::SymFn { .. } | InlineAsmOperand::SymStatic { .. } => {
|
||||
NeverLoopResult::Normal
|
||||
},
|
||||
InlineAsmOperand::Label { block } => never_loop_block(cx, block, local_labels, main_loop_id),
|
||||
InlineAsmOperand::Label { block } =>
|
||||
// We do not know whether the label will be executed or not, so `Diverging` must be
|
||||
// downgraded to `Normal`.
|
||||
{
|
||||
match never_loop_block(cx, block, local_labels, main_loop_id) {
|
||||
NeverLoopResult::Diverging { .. } => NeverLoopResult::Normal,
|
||||
result => result,
|
||||
}
|
||||
},
|
||||
})),
|
||||
ExprKind::OffsetOf(_, _)
|
||||
| ExprKind::Yield(_, _)
|
||||
|
|
|
|||
|
|
@ -183,7 +183,13 @@ fn emit_manual_let_else(
|
|||
format!("{{ {sn_else} }}")
|
||||
};
|
||||
let sn_bl = replace_in_pattern(cx, span, ident_map, pat, &mut app, true);
|
||||
let sugg = format!("let {sn_bl} = {sn_expr} else {else_bl};");
|
||||
let sugg = if sn_expr.ends_with('}') {
|
||||
// let-else statement expressions are not allowed to end with `}`
|
||||
// https://rust-lang.github.io/rfcs/3137-let-else.html#let-pattern--if--else--else-
|
||||
format!("let {sn_bl} = ({sn_expr}) else {else_bl};")
|
||||
} else {
|
||||
format!("let {sn_bl} = {sn_expr} else {else_bl};")
|
||||
};
|
||||
diag.span_suggestion(span, "consider writing", sugg, app);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath};
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::{is_none_pattern, msrvs, peel_hir_expr_refs, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Arm, Expr, ExprKind, LangItem, Pat, PatKind, QPath, is_range_literal};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::{Span, Symbol};
|
||||
|
||||
|
|
@ -124,8 +124,7 @@ fn check_map(cx: &LateContext<'_>, map: &Expr<'_>, span: Span, msrv: Msrv) {
|
|||
fn check_as_ref(cx: &LateContext<'_>, expr: &Expr<'_>, span: Span, msrv: Msrv) {
|
||||
if let ExprKind::MethodCall(seg, callee, [], _) = expr.kind
|
||||
&& seg.ident.name == sym::as_ref
|
||||
&& let ty::Adt(adtdef, ..) = cx.typeck_results().expr_ty(callee).kind()
|
||||
&& cx.tcx.is_diagnostic_item(sym::Option, adtdef.did())
|
||||
&& cx.typeck_results().expr_ty(callee).is_diag_item(cx, sym::Option)
|
||||
&& msrv.meets(
|
||||
cx,
|
||||
if clippy_utils::is_in_const_context(cx) {
|
||||
|
|
@ -135,19 +134,22 @@ fn check_as_ref(cx: &LateContext<'_>, expr: &Expr<'_>, span: Span, msrv: Msrv) {
|
|||
},
|
||||
)
|
||||
{
|
||||
if let Some(snippet) = clippy_utils::source::snippet_opt(cx, callee.span) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_OPTION_AS_SLICE,
|
||||
span,
|
||||
"use `Option::as_slice`",
|
||||
"use",
|
||||
format!("{snippet}.as_slice()"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
span_lint(cx, MANUAL_OPTION_AS_SLICE, span, "use `Option_as_slice`");
|
||||
}
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MANUAL_OPTION_AS_SLICE,
|
||||
span,
|
||||
"manual implementation of `Option::as_slice`",
|
||||
|diag| {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let callee = snippet_with_context(cx, callee.span, expr.span.ctxt(), "_", &mut app).0;
|
||||
diag.span_suggestion_verbose(
|
||||
span,
|
||||
"use `Option::as_slice` directly",
|
||||
format!("{callee}.as_slice()"),
|
||||
app,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -214,6 +214,9 @@ fn lint_map_unit_fn(
|
|||
};
|
||||
let fn_arg = &map_args.1[0];
|
||||
|
||||
#[expect(clippy::items_after_statements, reason = "the const is only used below")]
|
||||
const SUGG_MSG: &str = "use `if let` instead";
|
||||
|
||||
if is_unit_function(cx, fn_arg) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let msg = suggestion_msg("function", map_type);
|
||||
|
|
@ -226,7 +229,7 @@ fn lint_map_unit_fn(
|
|||
);
|
||||
|
||||
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
|
||||
diag.span_suggestion(stmt.span, "try", suggestion, applicability);
|
||||
diag.span_suggestion_verbose(stmt.span, SUGG_MSG, suggestion, applicability);
|
||||
});
|
||||
} else if let Some((binding, closure_expr)) = unit_closure(cx, fn_arg) {
|
||||
let msg = suggestion_msg("closure", map_type);
|
||||
|
|
@ -242,7 +245,7 @@ fn lint_map_unit_fn(
|
|||
snippet_with_applicability(cx, var_arg.span, "_", &mut applicability),
|
||||
snippet_with_context(cx, reduced_expr_span, var_arg.span.ctxt(), "_", &mut applicability).0,
|
||||
);
|
||||
diag.span_suggestion(stmt.span, "try", suggestion, applicability);
|
||||
diag.span_suggestion_verbose(stmt.span, SUGG_MSG, suggestion, applicability);
|
||||
} else {
|
||||
let suggestion = format!(
|
||||
"if let {0}({1}) = {2} {{ ... }}",
|
||||
|
|
@ -250,7 +253,7 @@ fn lint_map_unit_fn(
|
|||
snippet(cx, binding.pat.span, "_"),
|
||||
snippet(cx, var_arg.span, "_"),
|
||||
);
|
||||
diag.span_suggestion(stmt.span, "try", suggestion, Applicability::HasPlaceholders);
|
||||
diag.span_suggestion_verbose(stmt.span, SUGG_MSG, suggestion, Applicability::HasPlaceholders);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::consts::ConstEvalCtxt;
|
||||
use clippy_utils::res::{MaybeDef, MaybeQPath};
|
||||
use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath};
|
||||
use clippy_utils::source::{SpanRangeExt as _, indent_of, reindent_multiline};
|
||||
use rustc_ast::{BindingMode, ByRef};
|
||||
use rustc_errors::Applicability;
|
||||
|
|
@ -11,7 +11,8 @@ use rustc_span::sym;
|
|||
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{expr_type_is_certain, implements_trait};
|
||||
use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_copy};
|
||||
use clippy_utils::usage::local_used_after_expr;
|
||||
use clippy_utils::{is_default_equivalent, is_lint_allowed, peel_blocks, span_contains_comment};
|
||||
|
||||
use super::{MANUAL_UNWRAP_OR, MANUAL_UNWRAP_OR_DEFAULT};
|
||||
|
|
@ -87,7 +88,9 @@ fn handle(
|
|||
binding_id: HirId,
|
||||
) {
|
||||
// Only deal with situations where both alternatives return the same non-adjusted type.
|
||||
if cx.typeck_results().expr_ty(body_some) != cx.typeck_results().expr_ty(body_none) {
|
||||
if cx.typeck_results().expr_ty(body_some) != cx.typeck_results().expr_ty(body_none)
|
||||
|| !safe_to_move_scrutinee(cx, expr, condition)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -185,6 +188,29 @@ fn find_type_name<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'static
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks whether it is safe to move scrutinee.
|
||||
/// It is not safe to move if:
|
||||
/// 1. `scrutinee` is a `Result` that doesn't implemenet `Copy`, mainly because the `Err`
|
||||
/// variant is not copyable.
|
||||
/// 2. `expr` is a local variable that is used after the if-let-else expression.
|
||||
/// ```rust,ignore
|
||||
/// let foo: Result<usize, String> = Ok(0);
|
||||
/// let v = if let Ok(v) = foo { v } else { 1 };
|
||||
/// let bar = foo;
|
||||
/// ```
|
||||
fn safe_to_move_scrutinee(cx: &LateContext<'_>, expr: &Expr<'_>, scrutinee: &Expr<'_>) -> bool {
|
||||
if let Some(hir_id) = scrutinee.res_local_id()
|
||||
&& let scrutinee_ty = cx.typeck_results().expr_ty(scrutinee)
|
||||
&& scrutinee_ty.is_diag_item(cx, sym::Result)
|
||||
&& !is_copy(cx, scrutinee_ty)
|
||||
&& local_used_after_expr(cx, hir_id, expr)
|
||||
{
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_match<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::res::{MaybeDef, MaybeQPath};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::option_arg_ty;
|
||||
use clippy_utils::{is_none_arm, peel_blocks};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, LangItem, Mutability, PatKind, QPath};
|
||||
|
|
@ -10,54 +11,77 @@ use rustc_middle::ty;
|
|||
use super::MATCH_AS_REF;
|
||||
|
||||
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
|
||||
if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
|
||||
let arm_ref_mut = if is_none_arm(cx, &arms[0]) {
|
||||
is_ref_some_arm(cx, &arms[1])
|
||||
} else if is_none_arm(cx, &arms[1]) {
|
||||
is_ref_some_arm(cx, &arms[0])
|
||||
if let [arm1, arm2] = arms
|
||||
&& arm1.guard.is_none()
|
||||
&& arm2.guard.is_none()
|
||||
&& let Some(arm_ref_mutbl) = if is_none_arm(cx, arm1) {
|
||||
as_ref_some_arm(cx, arm2)
|
||||
} else if is_none_arm(cx, arm2) {
|
||||
as_ref_some_arm(cx, arm1)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(rb) = arm_ref_mut {
|
||||
let suggestion = match rb {
|
||||
Mutability::Not => "as_ref",
|
||||
Mutability::Mut => "as_mut",
|
||||
};
|
||||
|
||||
let output_ty = cx.typeck_results().expr_ty(expr);
|
||||
let input_ty = cx.typeck_results().expr_ty(ex);
|
||||
|
||||
let cast = if let ty::Adt(_, args) = input_ty.kind()
|
||||
&& let input_ty = args.type_at(0)
|
||||
&& let ty::Adt(_, args) = output_ty.kind()
|
||||
&& let output_ty = args.type_at(0)
|
||||
&& let ty::Ref(_, output_ty, _) = *output_ty.kind()
|
||||
&& input_ty != output_ty
|
||||
{
|
||||
".map(|x| x as _)"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MATCH_AS_REF,
|
||||
expr.span,
|
||||
format!("use `{suggestion}()` instead"),
|
||||
"try",
|
||||
format!(
|
||||
"{}.{suggestion}(){cast}",
|
||||
snippet_with_applicability(cx, ex.span, "_", &mut applicability),
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
&& let output_ty = cx.typeck_results().expr_ty(expr)
|
||||
&& let input_ty = cx.typeck_results().expr_ty(ex)
|
||||
&& let Some(input_ty) = option_arg_ty(cx, input_ty)
|
||||
&& let Some(output_ty) = option_arg_ty(cx, output_ty)
|
||||
&& let ty::Ref(_, output_ty, output_mutbl) = *output_ty.kind()
|
||||
{
|
||||
let method = match arm_ref_mutbl {
|
||||
Mutability::Not => "as_ref",
|
||||
Mutability::Mut => "as_mut",
|
||||
};
|
||||
|
||||
// ```
|
||||
// let _: Option<&T> = match opt {
|
||||
// Some(ref mut t) => Some(t),
|
||||
// None => None,
|
||||
// };
|
||||
// ```
|
||||
// We need to suggest `t.as_ref()` in order downcast the reference from `&mut` to `&`.
|
||||
// We may or may not need to cast the type as well, for which we'd need `.map()`, and that could
|
||||
// theoretically take care of the reference downcasting as well, but we chose to keep these two
|
||||
// operations separate
|
||||
let need_as_ref = arm_ref_mutbl == Mutability::Mut && output_mutbl == Mutability::Not;
|
||||
|
||||
let cast = if input_ty == output_ty { "" } else { ".map(|x| x as _)" };
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MATCH_AS_REF,
|
||||
expr.span,
|
||||
format!("manual implementation of `Option::{method}`"),
|
||||
|diag| {
|
||||
if need_as_ref {
|
||||
diag.note("but the type is coerced to a non-mutable reference, and so `as_ref` can used instead");
|
||||
diag.span_suggestion_verbose(
|
||||
expr.span,
|
||||
"use `Option::as_ref()`",
|
||||
format!(
|
||||
"{}.as_ref(){cast}",
|
||||
Sugg::hir_with_applicability(cx, ex, "_", &mut applicability).maybe_paren(),
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
} else {
|
||||
diag.span_suggestion_verbose(
|
||||
expr.span,
|
||||
format!("use `Option::{method}()` directly"),
|
||||
format!(
|
||||
"{}.{method}(){cast}",
|
||||
Sugg::hir_with_applicability(cx, ex, "_", &mut applicability).maybe_paren(),
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`)
|
||||
fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<Mutability> {
|
||||
fn as_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<Mutability> {
|
||||
if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind
|
||||
&& cx
|
||||
.qpath_res(qpath, arm.pat.hir_id)
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
|
|||
},
|
||||
_,
|
||||
) => path_prefix.with_prefix(path.segments),
|
||||
_ => (),
|
||||
QPath::TypeRelative(..) => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -416,7 +416,7 @@ fn get_ident(path: &QPath<'_>) -> Option<rustc_span::symbol::Ident> {
|
|||
let name = path.segments[0].ident;
|
||||
Some(name)
|
||||
},
|
||||
_ => None,
|
||||
QPath::TypeRelative(..) => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
85
clippy_lints/src/methods/lines_filter_map_ok.rs
Normal file
85
clippy_lints/src/methods/lines_filter_map_ok.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes};
|
||||
use clippy_utils::sym;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Body, Closure, Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::Span;
|
||||
|
||||
use super::LINES_FILTER_MAP_OK;
|
||||
|
||||
pub(super) fn check_flatten(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, call_span: Span, msrv: Msrv) {
|
||||
if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator)
|
||||
&& cx
|
||||
.typeck_results()
|
||||
.expr_ty_adjusted(recv)
|
||||
.is_diag_item(cx, sym::IoLines)
|
||||
&& msrv.meets(cx, msrvs::MAP_WHILE)
|
||||
{
|
||||
emit(cx, recv, "flatten", call_span);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_filter_or_flat_map(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &Expr<'_>,
|
||||
recv: &Expr<'_>,
|
||||
method_name: &'static str,
|
||||
method_arg: &Expr<'_>,
|
||||
call_span: Span,
|
||||
msrv: Msrv,
|
||||
) {
|
||||
if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator)
|
||||
&& cx
|
||||
.typeck_results()
|
||||
.expr_ty_adjusted(recv)
|
||||
.is_diag_item(cx, sym::IoLines)
|
||||
&& match method_arg.kind {
|
||||
// Detect `Result::ok`
|
||||
ExprKind::Path(ref qpath) => cx
|
||||
.qpath_res(qpath, method_arg.hir_id)
|
||||
.is_diag_item(cx, sym::result_ok_method),
|
||||
// Detect `|x| x.ok()`
|
||||
ExprKind::Closure(&Closure { body, .. }) => {
|
||||
if let Body {
|
||||
params: [param], value, ..
|
||||
} = cx.tcx.hir_body(body)
|
||||
&& let ExprKind::MethodCall(method, receiver, [], _) = value.kind
|
||||
{
|
||||
method.ident.name == sym::ok
|
||||
&& receiver.res_local_id() == Some(param.pat.hir_id)
|
||||
&& cx.ty_based_def(*value).is_diag_item(cx, sym::result_ok_method)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
&& msrv.meets(cx, msrvs::MAP_WHILE)
|
||||
{
|
||||
emit(cx, recv, method_name, call_span);
|
||||
}
|
||||
}
|
||||
|
||||
fn emit(cx: &LateContext<'_>, recv: &Expr<'_>, method_name: &'static str, call_span: Span) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LINES_FILTER_MAP_OK,
|
||||
call_span,
|
||||
format!("`{method_name}()` will run forever if the iterator repeatedly produces an `Err`"),
|
||||
|diag| {
|
||||
diag.span_note(
|
||||
recv.span,
|
||||
"this expression returning a `std::io::Lines` may produce \
|
||||
an infinite number of `Err` in case of a read error",
|
||||
);
|
||||
diag.span_suggestion(
|
||||
call_span,
|
||||
"replace with",
|
||||
"map_while(Result::ok)",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -56,6 +56,7 @@ mod iter_with_drain;
|
|||
mod iterator_step_by_zero;
|
||||
mod join_absolute_paths;
|
||||
mod lib;
|
||||
mod lines_filter_map_ok;
|
||||
mod manual_c_str_literals;
|
||||
mod manual_contains;
|
||||
mod manual_inspect;
|
||||
|
|
@ -4665,6 +4666,55 @@ declare_clippy_lint! {
|
|||
"making no use of the \"map closure\" when calling `.map_or_else(|| 2 * k, |n| n)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `lines.filter_map(Result::ok)` or `lines.flat_map(Result::ok)`
|
||||
/// when `lines` has type `std::io::Lines`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `Lines` instances might produce a never-ending stream of `Err`, in which case
|
||||
/// `filter_map(Result::ok)` will enter an infinite loop while waiting for an
|
||||
/// `Ok` variant. Calling `next()` once is sufficient to enter the infinite loop,
|
||||
/// even in the absence of explicit loops in the user code.
|
||||
///
|
||||
/// This situation can arise when working with user-provided paths. On some platforms,
|
||||
/// `std::fs::File::open(path)` might return `Ok(fs)` even when `path` is a directory,
|
||||
/// but any later attempt to read from `fs` will return an error.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// This lint suggests replacing `filter_map()` or `flat_map()` applied to a `Lines`
|
||||
/// instance in all cases. There are two cases where the suggestion might not be
|
||||
/// appropriate or necessary:
|
||||
///
|
||||
/// - If the `Lines` instance can never produce any error, or if an error is produced
|
||||
/// only once just before terminating the iterator, using `map_while()` is not
|
||||
/// necessary but will not do any harm.
|
||||
/// - If the `Lines` instance can produce intermittent errors then recover and produce
|
||||
/// successful results, using `map_while()` would stop at the first error.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # use std::{fs::File, io::{self, BufRead, BufReader}};
|
||||
/// # let _ = || -> io::Result<()> {
|
||||
/// let mut lines = BufReader::new(File::open("some-path")?).lines().filter_map(Result::ok);
|
||||
/// // If "some-path" points to a directory, the next statement never terminates:
|
||||
/// let first_line: Option<String> = lines.next();
|
||||
/// # Ok(()) };
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # use std::{fs::File, io::{self, BufRead, BufReader}};
|
||||
/// # let _ = || -> io::Result<()> {
|
||||
/// let mut lines = BufReader::new(File::open("some-path")?).lines().map_while(Result::ok);
|
||||
/// let first_line: Option<String> = lines.next();
|
||||
/// # Ok(()) };
|
||||
/// ```
|
||||
#[clippy::version = "1.70.0"]
|
||||
pub LINES_FILTER_MAP_OK,
|
||||
suspicious,
|
||||
"filtering `std::io::Lines` with `filter_map()`, `flat_map()`, or `flatten()` might cause an infinite loop"
|
||||
}
|
||||
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
|
|
@ -4847,6 +4897,7 @@ impl_lint_pass!(Methods => [
|
|||
IP_CONSTANT,
|
||||
REDUNDANT_ITER_CLONED,
|
||||
UNNECESSARY_OPTION_MAP_OR_ELSE,
|
||||
LINES_FILTER_MAP_OK,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
|
|
@ -5050,7 +5101,11 @@ impl Methods {
|
|||
(sym::bytes, []) => unbuffered_bytes::check(cx, expr, recv),
|
||||
(sym::cloned, []) => {
|
||||
cloned_instead_of_copied::check(cx, expr, recv, span, self.msrv);
|
||||
option_as_ref_cloned::check(cx, recv, span);
|
||||
if let Some((method @ (sym::as_ref | sym::as_mut), as_ref_recv, [], as_ref_ident_span, _)) =
|
||||
method_call(recv)
|
||||
{
|
||||
option_as_ref_cloned::check(cx, span, method, as_ref_recv, as_ref_ident_span);
|
||||
}
|
||||
},
|
||||
(sym::collect, []) if cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) => {
|
||||
needless_collect::check(cx, span, expr, recv, call_span);
|
||||
|
|
@ -5162,32 +5217,47 @@ impl Methods {
|
|||
},
|
||||
(sym::filter_map, [arg]) => {
|
||||
unused_enumerate_index::check(cx, expr, recv, arg);
|
||||
unnecessary_filter_map::check(cx, expr, arg, name);
|
||||
unnecessary_filter_map::check(cx, expr, arg, call_span, unnecessary_filter_map::Kind::FilterMap);
|
||||
filter_map_bool_then::check(cx, expr, arg, call_span);
|
||||
filter_map_identity::check(cx, expr, arg, span);
|
||||
lines_filter_map_ok::check_filter_or_flat_map(
|
||||
cx,
|
||||
expr,
|
||||
recv,
|
||||
"filter_map",
|
||||
arg,
|
||||
call_span,
|
||||
self.msrv,
|
||||
);
|
||||
},
|
||||
(sym::find_map, [arg]) => {
|
||||
unused_enumerate_index::check(cx, expr, recv, arg);
|
||||
unnecessary_filter_map::check(cx, expr, arg, name);
|
||||
unnecessary_filter_map::check(cx, expr, arg, call_span, unnecessary_filter_map::Kind::FindMap);
|
||||
},
|
||||
(sym::flat_map, [arg]) => {
|
||||
unused_enumerate_index::check(cx, expr, recv, arg);
|
||||
flat_map_identity::check(cx, expr, arg, span);
|
||||
flat_map_option::check(cx, expr, arg, span);
|
||||
lines_filter_map_ok::check_filter_or_flat_map(
|
||||
cx, expr, recv, "flat_map", arg, call_span, self.msrv,
|
||||
);
|
||||
},
|
||||
(sym::flatten, []) => match method_call(recv) {
|
||||
Some((sym::map, recv, [map_arg], map_span, _)) => {
|
||||
map_flatten::check(cx, expr, recv, map_arg, map_span);
|
||||
},
|
||||
Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check(
|
||||
cx,
|
||||
expr,
|
||||
recv,
|
||||
recv2,
|
||||
iter_overeager_cloned::Op::LaterCloned,
|
||||
true,
|
||||
),
|
||||
_ => {},
|
||||
(sym::flatten, []) => {
|
||||
match method_call(recv) {
|
||||
Some((sym::map, recv, [map_arg], map_span, _)) => {
|
||||
map_flatten::check(cx, expr, recv, map_arg, map_span);
|
||||
},
|
||||
Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check(
|
||||
cx,
|
||||
expr,
|
||||
recv,
|
||||
recv2,
|
||||
iter_overeager_cloned::Op::LaterCloned,
|
||||
true,
|
||||
),
|
||||
_ => {},
|
||||
}
|
||||
lines_filter_map_ok::check_flatten(cx, expr, recv, call_span, self.msrv);
|
||||
},
|
||||
(sym::fold, [init, acc]) => {
|
||||
manual_try_fold::check(cx, expr, init, acc, call_span, self.msrv);
|
||||
|
|
|
|||
|
|
@ -4,24 +4,28 @@ use clippy_utils::sym;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::{Span, Symbol};
|
||||
|
||||
use super::{OPTION_AS_REF_CLONED, method_call};
|
||||
use super::OPTION_AS_REF_CLONED;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, cloned_recv: &Expr<'_>, cloned_ident_span: Span) {
|
||||
if let Some((method @ (sym::as_ref | sym::as_mut), as_ref_recv, [], as_ref_ident_span, _)) =
|
||||
method_call(cloned_recv)
|
||||
&& cx
|
||||
.typeck_results()
|
||||
.expr_ty(as_ref_recv)
|
||||
.peel_refs()
|
||||
.is_diag_item(cx, sym::Option)
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
cloned_ident_span: Span,
|
||||
as_ref_method: Symbol,
|
||||
as_ref_recv: &Expr<'_>,
|
||||
as_ref_ident_span: Span,
|
||||
) {
|
||||
if cx
|
||||
.typeck_results()
|
||||
.expr_ty(as_ref_recv)
|
||||
.peel_refs()
|
||||
.is_diag_item(cx, sym::Option)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
OPTION_AS_REF_CLONED,
|
||||
as_ref_ident_span.to(cloned_ident_span),
|
||||
format!("cloning an `Option<_>` using `.{method}().cloned()`"),
|
||||
format!("cloning an `Option<_>` using `.{as_ref_method}().cloned()`"),
|
||||
"this can be written more concisely by cloning the `Option<_>` directly",
|
||||
"clone".into(),
|
||||
Applicability::MachineApplicable,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use clippy_utils::sugg::deref_closure_args;
|
||||
|
|
@ -34,79 +34,67 @@ pub(super) fn check<'tcx>(
|
|||
{
|
||||
let msg = format!("called `{option_check_method}()` after searching an `Iterator` with `{search_method}`");
|
||||
let search_snippet = snippet(cx, search_arg.span, "..");
|
||||
if search_snippet.lines().count() <= 1 {
|
||||
// suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()`
|
||||
// suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()`
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let any_search_snippet = if search_method == sym::find
|
||||
&& let ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind
|
||||
&& let closure_body = cx.tcx.hir_body(body)
|
||||
&& let Some(closure_arg) = closure_body.params.first()
|
||||
{
|
||||
if let PatKind::Ref(..) = closure_arg.pat.kind {
|
||||
Some(search_snippet.replacen('&', "", 1))
|
||||
} else if let PatKind::Binding(..) = strip_pat_refs(closure_arg.pat).kind {
|
||||
// `find()` provides a reference to the item, but `any` does not,
|
||||
// so we should fix item usages for suggestion
|
||||
if let Some(closure_sugg) = deref_closure_args(cx, search_arg) {
|
||||
applicability = closure_sugg.applicability;
|
||||
Some(closure_sugg.suggestion)
|
||||
} else {
|
||||
Some(search_snippet.to_string())
|
||||
}
|
||||
// suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()`
|
||||
// suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()`
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let any_search_snippet = if search_method == sym::find
|
||||
&& let ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind
|
||||
&& let closure_body = cx.tcx.hir_body(body)
|
||||
&& let Some(closure_arg) = closure_body.params.first()
|
||||
{
|
||||
if let PatKind::Ref(..) = closure_arg.pat.kind {
|
||||
Some(search_snippet.replacen('&', "", 1))
|
||||
} else if let PatKind::Binding(..) = strip_pat_refs(closure_arg.pat).kind {
|
||||
// `find()` provides a reference to the item, but `any` does not,
|
||||
// so we should fix item usages for suggestion
|
||||
if let Some(closure_sugg) = deref_closure_args(cx, search_arg) {
|
||||
applicability = closure_sugg.applicability;
|
||||
Some(closure_sugg.suggestion)
|
||||
} else {
|
||||
None
|
||||
Some(search_snippet.to_string())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// add note if not multi-line
|
||||
if is_some {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SEARCH_IS_SOME,
|
||||
method_span.with_hi(expr.span.hi()),
|
||||
msg,
|
||||
"consider using",
|
||||
format!(
|
||||
"any({})",
|
||||
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
} else {
|
||||
let iter = snippet(cx, search_recv.span, "..");
|
||||
let sugg = if is_receiver_of_method_call(cx, expr) {
|
||||
format!(
|
||||
"(!{iter}.any({}))",
|
||||
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"!{iter}.any({})",
|
||||
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
|
||||
)
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SEARCH_IS_SOME,
|
||||
expr.span,
|
||||
msg,
|
||||
"consider using",
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let hint = format!(
|
||||
"this is more succinctly expressed by calling `any()`{}",
|
||||
if option_check_method == "is_none" {
|
||||
" with negation"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
None
|
||||
};
|
||||
// add note if not multi-line
|
||||
if is_some {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SEARCH_IS_SOME,
|
||||
method_span.with_hi(expr.span.hi()),
|
||||
msg,
|
||||
"consider using",
|
||||
format!(
|
||||
"any({})",
|
||||
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
} else {
|
||||
let iter = snippet(cx, search_recv.span, "..");
|
||||
let sugg = if is_receiver_of_method_call(cx, expr) {
|
||||
format!(
|
||||
"(!{iter}.any({}))",
|
||||
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"!{iter}.any({})",
|
||||
any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
|
||||
)
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SEARCH_IS_SOME,
|
||||
expr.span,
|
||||
msg,
|
||||
"consider using",
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, msg, None, hint);
|
||||
}
|
||||
}
|
||||
// lint if `find()` is called by `String` or `&str`
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@ use super::utils::clone_or_copy_needed;
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes};
|
||||
use clippy_utils::sym;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use clippy_utils::ty::{is_copy, option_arg_ty};
|
||||
use clippy_utils::usage::mutated_variables;
|
||||
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::LangItem::{OptionNone, OptionSome};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::Symbol;
|
||||
use rustc_span::Span;
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::{UNNECESSARY_FILTER_MAP, UNNECESSARY_FIND_MAP};
|
||||
|
||||
|
|
@ -18,7 +18,8 @@ pub(super) fn check<'tcx>(
|
|||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx hir::Expr<'tcx>,
|
||||
arg: &'tcx hir::Expr<'tcx>,
|
||||
name: Symbol,
|
||||
call_span: Span,
|
||||
kind: Kind,
|
||||
) {
|
||||
if !cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) {
|
||||
return;
|
||||
|
|
@ -45,61 +46,88 @@ pub(super) fn check<'tcx>(
|
|||
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
|
||||
let sugg = if !found_filtering {
|
||||
// Check if the closure is .filter_map(|x| Some(x))
|
||||
if name == sym::filter_map
|
||||
&& let hir::ExprKind::Call(expr, args) = body.value.kind
|
||||
if kind.is_filter_map()
|
||||
&& let hir::ExprKind::Call(expr, [arg]) = body.value.kind
|
||||
&& expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome)
|
||||
&& let hir::ExprKind::Path(_) = args[0].kind
|
||||
&& let hir::ExprKind::Path(_) = arg.kind
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
UNNECESSARY_FILTER_MAP,
|
||||
expr.span,
|
||||
call_span,
|
||||
String::from("this call to `.filter_map(..)` is unnecessary"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if name == sym::filter_map {
|
||||
"map(..)"
|
||||
} else {
|
||||
"map(..).next()"
|
||||
match kind {
|
||||
Kind::FilterMap => "map(..)",
|
||||
Kind::FindMap => "map(..).next()",
|
||||
}
|
||||
} else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) {
|
||||
match cx.typeck_results().expr_ty(body.value).kind() {
|
||||
ty::Adt(adt, subst)
|
||||
if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && in_ty == subst.type_at(0) =>
|
||||
{
|
||||
if name == sym::filter_map {
|
||||
"filter(..)"
|
||||
} else {
|
||||
"find(..)"
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
let ty = cx.typeck_results().expr_ty(body.value);
|
||||
if option_arg_ty(cx, ty).is_some_and(|t| t == in_ty) {
|
||||
match kind {
|
||||
Kind::FilterMap => "filter(..)",
|
||||
Kind::FindMap => "find(..)",
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
span_lint(
|
||||
cx,
|
||||
if name == sym::filter_map {
|
||||
UNNECESSARY_FILTER_MAP
|
||||
} else {
|
||||
UNNECESSARY_FIND_MAP
|
||||
match kind {
|
||||
Kind::FilterMap => UNNECESSARY_FILTER_MAP,
|
||||
Kind::FindMap => UNNECESSARY_FIND_MAP,
|
||||
},
|
||||
expr.span,
|
||||
format!("this `.{name}(..)` can be written more simply using `.{sugg}`"),
|
||||
call_span,
|
||||
format!("this `.{kind}(..)` can be written more simply using `.{sugg}`"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) enum Kind {
|
||||
FilterMap,
|
||||
FindMap,
|
||||
}
|
||||
|
||||
impl Kind {
|
||||
fn is_filter_map(self) -> bool {
|
||||
matches!(self, Self::FilterMap)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Kind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::FilterMap => f.write_str("filter_map"),
|
||||
Self::FindMap => f.write_str("find_map"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns (found_mapping, found_filtering)
|
||||
fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tcx hir::Expr<'_>) -> (bool, bool) {
|
||||
match expr.kind {
|
||||
hir::ExprKind::Path(ref path)
|
||||
if cx
|
||||
.qpath_res(path, expr.hir_id)
|
||||
.ctor_parent(cx)
|
||||
.is_lang_item(cx, OptionNone) =>
|
||||
{
|
||||
// None
|
||||
(false, true)
|
||||
},
|
||||
hir::ExprKind::Call(func, args) => {
|
||||
if func.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) {
|
||||
if args[0].res_local_id() == Some(arg_id) {
|
||||
// Some(arg_id)
|
||||
return (false, false);
|
||||
}
|
||||
// Some(not arg_id)
|
||||
return (true, false);
|
||||
}
|
||||
(true, true)
|
||||
|
|
@ -109,8 +137,10 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc
|
|||
&& cx.typeck_results().expr_ty(recv).is_bool()
|
||||
&& arg.res_local_id() == Some(arg_id)
|
||||
{
|
||||
// bool.then_some(arg_id)
|
||||
(false, true)
|
||||
} else {
|
||||
// bool.then_some(not arg_id)
|
||||
(true, true)
|
||||
}
|
||||
},
|
||||
|
|
@ -134,14 +164,6 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc
|
|||
let else_check = check_expression(cx, arg_id, else_arm);
|
||||
(if_check.0 | else_check.0, if_check.1 | else_check.1)
|
||||
},
|
||||
hir::ExprKind::Path(ref path)
|
||||
if cx
|
||||
.qpath_res(path, expr.hir_id)
|
||||
.ctor_parent(cx)
|
||||
.is_lang_item(cx, OptionNone) =>
|
||||
{
|
||||
(false, true)
|
||||
},
|
||||
_ => (true, true),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use super::UNNEEDED_WILDCARD_PATTERN;
|
|||
|
||||
pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) {
|
||||
if let PatKind::TupleStruct(_, _, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind
|
||||
&& let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest())
|
||||
&& let Some(rest_index) = patterns.iter().position(Pat::is_rest)
|
||||
{
|
||||
if let Some((left_index, left_pat)) = patterns[..rest_index]
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> {
|
|||
fn path_has_args(p: &QPath<'_>) -> bool {
|
||||
match *p {
|
||||
QPath::Resolved(_, Path { segments: [.., s], .. }) | QPath::TypeRelative(_, s) => s.args.is_some(),
|
||||
_ => false,
|
||||
QPath::Resolved(..) => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::higher::If;
|
||||
use clippy_utils::is_from_proc_macro;
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::source::{SpanRangeExt, walk_span_to_context};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{ExprKind, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
|
|
@ -29,13 +29,13 @@ declare_clippy_lint! {
|
|||
/// really_expensive_condition_with_side_effects(&mut i);
|
||||
/// ```
|
||||
#[clippy::version = "1.72.0"]
|
||||
pub NEEDLESS_IF,
|
||||
pub NEEDLESS_IFS,
|
||||
complexity,
|
||||
"checks for empty if branches"
|
||||
}
|
||||
declare_lint_pass!(NeedlessIf => [NEEDLESS_IF]);
|
||||
declare_lint_pass!(NeedlessIfs => [NEEDLESS_IFS]);
|
||||
|
||||
impl LateLintPass<'_> for NeedlessIf {
|
||||
impl LateLintPass<'_> for NeedlessIfs {
|
||||
fn check_stmt<'tcx>(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) {
|
||||
if let StmtKind::Expr(expr) = stmt.kind
|
||||
&& let Some(If {
|
||||
|
|
@ -56,12 +56,13 @@ impl LateLintPass<'_> for NeedlessIf {
|
|||
src.bytes()
|
||||
.all(|ch| matches!(ch, b'{' | b'}') || ch.is_ascii_whitespace())
|
||||
})
|
||||
&& let Some(cond_snippet) = cond.span.get_source_text(cx)
|
||||
&& let Some(cond_span) = walk_span_to_context(cond.span, expr.span.ctxt())
|
||||
&& let Some(cond_snippet) = cond_span.get_source_text(cx)
|
||||
&& !is_from_proc_macro(cx, expr)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_IF,
|
||||
NEEDLESS_IFS,
|
||||
stmt.span,
|
||||
"this `if` branch is empty",
|
||||
"you can remove it",
|
||||
|
|
@ -6,7 +6,9 @@ use rustc_data_structures::fx::FxHashMap;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::hir_id::HirIdMap;
|
||||
use rustc_hir::{Body, Expr, ExprKind, HirId, ImplItem, ImplItemImplKind, ImplItemKind, Node, PatKind, TraitItem, TraitItemKind};
|
||||
use rustc_hir::{
|
||||
Body, Expr, ExprKind, HirId, ImplItem, ImplItemImplKind, ImplItemKind, Node, PatKind, TraitItem, TraitItemKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, ConstKind, GenericArgKind, GenericArgsRef};
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
|
|
|||
|
|
@ -8,46 +8,52 @@ use rustc_span::Span;
|
|||
|
||||
use super::DOUBLE_COMPARISONS;
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) {
|
||||
let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) {
|
||||
(ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => {
|
||||
(lb.node, llhs, lrhs, rb.node, rlhs, rrhs)
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
if !(eq_expr_value(cx, llhs, rlhs) && eq_expr_value(cx, lrhs, rrhs)) {
|
||||
return;
|
||||
}
|
||||
macro_rules! lint_double_comparison {
|
||||
($op:tt) => {{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let lhs_str = snippet_with_applicability(cx, llhs.span, "", &mut applicability);
|
||||
let rhs_str = snippet_with_applicability(cx, lrhs.span, "", &mut applicability);
|
||||
let sugg = format!("{} {} {}", lhs_str, stringify!($op), rhs_str);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DOUBLE_COMPARISONS,
|
||||
span,
|
||||
"this binary expression can be simplified",
|
||||
"try",
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}};
|
||||
}
|
||||
match (op, lkind, rkind) {
|
||||
(BinOpKind::Or, BinOpKind::Eq, BinOpKind::Lt) | (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Eq) => {
|
||||
lint_double_comparison!(<=);
|
||||
},
|
||||
(BinOpKind::Or, BinOpKind::Eq, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Eq) => {
|
||||
lint_double_comparison!(>=);
|
||||
},
|
||||
(BinOpKind::Or, BinOpKind::Lt, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Lt) => {
|
||||
lint_double_comparison!(!=);
|
||||
},
|
||||
(BinOpKind::And, BinOpKind::Le, BinOpKind::Ge) | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => {
|
||||
lint_double_comparison!(==);
|
||||
},
|
||||
_ => (),
|
||||
pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>, span: Span) {
|
||||
if let ExprKind::Binary(lop, llhs, lrhs) = lhs.kind
|
||||
&& let ExprKind::Binary(rop, rlhs, rrhs) = rhs.kind
|
||||
&& eq_expr_value(cx, llhs, rlhs)
|
||||
&& eq_expr_value(cx, lrhs, rrhs)
|
||||
{
|
||||
let op = match (op, lop.node, rop.node) {
|
||||
// x == y || x < y => x <= y
|
||||
(BinOpKind::Or, BinOpKind::Eq, BinOpKind::Lt)
|
||||
// x < y || x == y => x <= y
|
||||
| (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Eq) => {
|
||||
"<="
|
||||
},
|
||||
// x == y || x > y => x >= y
|
||||
(BinOpKind::Or, BinOpKind::Eq, BinOpKind::Gt)
|
||||
// x > y || x == y => x >= y
|
||||
| (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Eq) => {
|
||||
">="
|
||||
},
|
||||
// x < y || x > y => x != y
|
||||
(BinOpKind::Or, BinOpKind::Lt, BinOpKind::Gt)
|
||||
// x > y || x < y => x != y
|
||||
| (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Lt) => {
|
||||
"!="
|
||||
},
|
||||
// x <= y && x >= y => x == y
|
||||
(BinOpKind::And, BinOpKind::Le, BinOpKind::Ge)
|
||||
// x >= y && x <= y => x == y
|
||||
| (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => {
|
||||
"=="
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let lhs_str = snippet_with_applicability(cx, llhs.span, "", &mut applicability);
|
||||
let rhs_str = snippet_with_applicability(cx, lrhs.span, "", &mut applicability);
|
||||
let sugg = format!("{lhs_str} {op} {rhs_str}");
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DOUBLE_COMPARISONS,
|
||||
span,
|
||||
"this binary expression can be simplified",
|
||||
"try",
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_ast::BinOpKind;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::Span;
|
||||
|
||||
use super::INTEGER_DIVISION_REMAINDER_USED;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>, span: Span) {
|
||||
if let BinOpKind::Div | BinOpKind::Rem = op
|
||||
&& let lhs_ty = cx.typeck_results().expr_ty(lhs)
|
||||
&& let rhs_ty = cx.typeck_results().expr_ty(rhs)
|
||||
&& let ty::Int(_) | ty::Uint(_) = lhs_ty.peel_refs().kind()
|
||||
&& let ty::Int(_) | ty::Uint(_) = rhs_ty.peel_refs().kind()
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
INTEGER_DIVISION_REMAINDER_USED,
|
||||
span.source_callsite(),
|
||||
format!("use of `{}` has been disallowed in this context", op.as_str()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::ty::{self, IntTy, UintTy};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
|
||||
use clippy_utils::comparisons;
|
||||
|
|
@ -12,29 +11,26 @@ use clippy_utils::consts::{ConstEvalCtxt, FullInt};
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for comparisons where the relation is always either
|
||||
/// true or false, but where one side has been upcast so that the comparison is
|
||||
/// necessary. Only integer types are checked.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// An expression like `let x : u8 = ...; (x as u32) > 300`
|
||||
/// will mistakenly imply that it is possible for `x` to be outside the range of
|
||||
/// `u8`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let x: u8 = 1;
|
||||
/// (x as u32) > 300;
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub INVALID_UPCAST_COMPARISONS,
|
||||
pedantic,
|
||||
"a comparison involving an upcast which is always true or false"
|
||||
}
|
||||
use super::INVALID_UPCAST_COMPARISONS;
|
||||
|
||||
declare_lint_pass!(InvalidUpcastComparisons => [INVALID_UPCAST_COMPARISONS]);
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
cmp: BinOpKind,
|
||||
lhs: &'tcx Expr<'_>,
|
||||
rhs: &'tcx Expr<'_>,
|
||||
span: Span,
|
||||
) {
|
||||
let normalized = comparisons::normalize_comparison(cmp, lhs, rhs);
|
||||
let Some((rel, normalized_lhs, normalized_rhs)) = normalized else {
|
||||
return;
|
||||
};
|
||||
|
||||
let lhs_bounds = numeric_cast_precast_bounds(cx, normalized_lhs);
|
||||
let rhs_bounds = numeric_cast_precast_bounds(cx, normalized_rhs);
|
||||
|
||||
upcast_comparison_bounds_err(cx, span, rel, lhs_bounds, normalized_lhs, normalized_rhs, false);
|
||||
upcast_comparison_bounds_err(cx, span, rel, rhs_bounds, normalized_rhs, normalized_lhs, true);
|
||||
}
|
||||
|
||||
fn numeric_cast_precast_bounds(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(FullInt, FullInt)> {
|
||||
if let ExprKind::Cast(cast_exp, _) = expr.kind {
|
||||
|
|
@ -68,6 +64,47 @@ fn numeric_cast_precast_bounds(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<
|
|||
}
|
||||
}
|
||||
|
||||
fn upcast_comparison_bounds_err<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
span: Span,
|
||||
rel: Rel,
|
||||
lhs_bounds: Option<(FullInt, FullInt)>,
|
||||
lhs: &'tcx Expr<'_>,
|
||||
rhs: &'tcx Expr<'_>,
|
||||
invert: bool,
|
||||
) {
|
||||
if let Some((lb, ub)) = lhs_bounds
|
||||
&& let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs, span.ctxt())
|
||||
{
|
||||
match rel {
|
||||
Rel::Eq => {
|
||||
if norm_rhs_val < lb || ub < norm_rhs_val {
|
||||
err_upcast_comparison(cx, span, lhs, false);
|
||||
}
|
||||
},
|
||||
Rel::Ne => {
|
||||
if norm_rhs_val < lb || ub < norm_rhs_val {
|
||||
err_upcast_comparison(cx, span, lhs, true);
|
||||
}
|
||||
},
|
||||
Rel::Lt => {
|
||||
if (invert && norm_rhs_val < lb) || (!invert && ub < norm_rhs_val) {
|
||||
err_upcast_comparison(cx, span, lhs, true);
|
||||
} else if (!invert && norm_rhs_val <= lb) || (invert && ub <= norm_rhs_val) {
|
||||
err_upcast_comparison(cx, span, lhs, false);
|
||||
}
|
||||
},
|
||||
Rel::Le => {
|
||||
if (invert && norm_rhs_val <= lb) || (!invert && ub <= norm_rhs_val) {
|
||||
err_upcast_comparison(cx, span, lhs, true);
|
||||
} else if (!invert && norm_rhs_val < lb) || (invert && ub < norm_rhs_val) {
|
||||
err_upcast_comparison(cx, span, lhs, false);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, always: bool) {
|
||||
if let ExprKind::Cast(cast_val, _) = expr.kind {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
|
@ -90,76 +127,3 @@ fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, alwa
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn upcast_comparison_bounds_err<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
span: Span,
|
||||
rel: Rel,
|
||||
lhs_bounds: Option<(FullInt, FullInt)>,
|
||||
lhs: &'tcx Expr<'_>,
|
||||
rhs: &'tcx Expr<'_>,
|
||||
invert: bool,
|
||||
) {
|
||||
if let Some((lb, ub)) = lhs_bounds
|
||||
&& let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs, span.ctxt())
|
||||
{
|
||||
if rel == Rel::Eq || rel == Rel::Ne {
|
||||
if norm_rhs_val < lb || norm_rhs_val > ub {
|
||||
err_upcast_comparison(cx, span, lhs, rel == Rel::Ne);
|
||||
}
|
||||
} else if match rel {
|
||||
Rel::Lt => {
|
||||
if invert {
|
||||
norm_rhs_val < lb
|
||||
} else {
|
||||
ub < norm_rhs_val
|
||||
}
|
||||
},
|
||||
Rel::Le => {
|
||||
if invert {
|
||||
norm_rhs_val <= lb
|
||||
} else {
|
||||
ub <= norm_rhs_val
|
||||
}
|
||||
},
|
||||
Rel::Eq | Rel::Ne => unreachable!(),
|
||||
} {
|
||||
err_upcast_comparison(cx, span, lhs, true);
|
||||
} else if match rel {
|
||||
Rel::Lt => {
|
||||
if invert {
|
||||
norm_rhs_val >= ub
|
||||
} else {
|
||||
lb >= norm_rhs_val
|
||||
}
|
||||
},
|
||||
Rel::Le => {
|
||||
if invert {
|
||||
norm_rhs_val > ub
|
||||
} else {
|
||||
lb > norm_rhs_val
|
||||
}
|
||||
},
|
||||
Rel::Eq | Rel::Ne => unreachable!(),
|
||||
} {
|
||||
err_upcast_comparison(cx, span, lhs, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for InvalidUpcastComparisons {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind {
|
||||
let normalized = comparisons::normalize_comparison(cmp.node, lhs, rhs);
|
||||
let Some((rel, normalized_lhs, normalized_rhs)) = normalized else {
|
||||
return;
|
||||
};
|
||||
|
||||
let lhs_bounds = numeric_cast_precast_bounds(cx, normalized_lhs);
|
||||
let rhs_bounds = numeric_cast_precast_bounds(cx, normalized_rhs);
|
||||
|
||||
upcast_comparison_bounds_err(cx, expr.span, rel, lhs_bounds, normalized_lhs, normalized_rhs, false);
|
||||
upcast_comparison_bounds_err(cx, expr.span, rel, rhs_bounds, normalized_rhs, normalized_lhs, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::sugg::{Sugg, has_enclosing_paren};
|
||||
|
|
@ -7,111 +6,69 @@ use rustc_ast::{BinOpKind, LitIntType, LitKind, UnOp};
|
|||
use rustc_data_structures::packed::Pu128;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::source_map::Spanned;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for an expression like `(x + (y - 1)) / y` which is a common manual reimplementation
|
||||
/// of `x.div_ceil(y)`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It's simpler, clearer and more readable.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let x: i32 = 7;
|
||||
/// let y: i32 = 4;
|
||||
/// let div = (x + (y - 1)) / y;
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #![feature(int_roundings)]
|
||||
/// let x: i32 = 7;
|
||||
/// let y: i32 = 4;
|
||||
/// let div = x.div_ceil(y);
|
||||
/// ```
|
||||
#[clippy::version = "1.83.0"]
|
||||
pub MANUAL_DIV_CEIL,
|
||||
complexity,
|
||||
"manually reimplementing `div_ceil`"
|
||||
}
|
||||
use super::MANUAL_DIV_CEIL;
|
||||
|
||||
pub struct ManualDivCeil {
|
||||
msrv: Msrv,
|
||||
}
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &Expr<'_>, msrv: Msrv) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
impl ManualDivCeil {
|
||||
#[must_use]
|
||||
pub fn new(conf: &'static Conf) -> Self {
|
||||
Self { msrv: conf.msrv }
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(ManualDivCeil => [MANUAL_DIV_CEIL]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ManualDivCeil {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
if let ExprKind::Binary(div_op, div_lhs, div_rhs) = expr.kind
|
||||
&& div_op.node == BinOpKind::Div
|
||||
&& check_int_ty_and_feature(cx, div_lhs)
|
||||
&& check_int_ty_and_feature(cx, div_rhs)
|
||||
&& let ExprKind::Binary(inner_op, inner_lhs, inner_rhs) = div_lhs.kind
|
||||
&& self.msrv.meets(cx, msrvs::DIV_CEIL)
|
||||
if op == BinOpKind::Div
|
||||
&& check_int_ty_and_feature(cx, lhs)
|
||||
&& check_int_ty_and_feature(cx, rhs)
|
||||
&& let ExprKind::Binary(inner_op, inner_lhs, inner_rhs) = lhs.kind
|
||||
&& msrv.meets(cx, msrvs::DIV_CEIL)
|
||||
{
|
||||
// (x + (y - 1)) / y
|
||||
if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_rhs.kind
|
||||
&& inner_op.node == BinOpKind::Add
|
||||
&& sub_op.node == BinOpKind::Sub
|
||||
&& check_literal(sub_rhs)
|
||||
&& check_eq_expr(cx, sub_lhs, rhs)
|
||||
{
|
||||
// (x + (y - 1)) / y
|
||||
if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_rhs.kind
|
||||
&& inner_op.node == BinOpKind::Add
|
||||
&& sub_op.node == BinOpKind::Sub
|
||||
&& check_literal(sub_rhs)
|
||||
&& check_eq_expr(cx, sub_lhs, div_rhs)
|
||||
{
|
||||
build_suggestion(cx, expr, inner_lhs, div_rhs, &mut applicability);
|
||||
return;
|
||||
}
|
||||
build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability);
|
||||
return;
|
||||
}
|
||||
|
||||
// ((y - 1) + x) / y
|
||||
if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_lhs.kind
|
||||
&& inner_op.node == BinOpKind::Add
|
||||
&& sub_op.node == BinOpKind::Sub
|
||||
&& check_literal(sub_rhs)
|
||||
&& check_eq_expr(cx, sub_lhs, div_rhs)
|
||||
{
|
||||
build_suggestion(cx, expr, inner_rhs, div_rhs, &mut applicability);
|
||||
return;
|
||||
}
|
||||
// ((y - 1) + x) / y
|
||||
if let ExprKind::Binary(sub_op, sub_lhs, sub_rhs) = inner_lhs.kind
|
||||
&& inner_op.node == BinOpKind::Add
|
||||
&& sub_op.node == BinOpKind::Sub
|
||||
&& check_literal(sub_rhs)
|
||||
&& check_eq_expr(cx, sub_lhs, rhs)
|
||||
{
|
||||
build_suggestion(cx, expr, inner_rhs, rhs, &mut applicability);
|
||||
return;
|
||||
}
|
||||
|
||||
// (x + y - 1) / y
|
||||
if let ExprKind::Binary(add_op, add_lhs, add_rhs) = inner_lhs.kind
|
||||
&& inner_op.node == BinOpKind::Sub
|
||||
&& add_op.node == BinOpKind::Add
|
||||
&& check_literal(inner_rhs)
|
||||
&& check_eq_expr(cx, add_rhs, div_rhs)
|
||||
{
|
||||
build_suggestion(cx, expr, add_lhs, div_rhs, &mut applicability);
|
||||
}
|
||||
// (x + y - 1) / y
|
||||
if let ExprKind::Binary(add_op, add_lhs, add_rhs) = inner_lhs.kind
|
||||
&& inner_op.node == BinOpKind::Sub
|
||||
&& add_op.node == BinOpKind::Add
|
||||
&& check_literal(inner_rhs)
|
||||
&& check_eq_expr(cx, add_rhs, rhs)
|
||||
{
|
||||
build_suggestion(cx, expr, add_lhs, rhs, &mut applicability);
|
||||
}
|
||||
|
||||
// (x + (Y - 1)) / Y
|
||||
if inner_op.node == BinOpKind::Add && differ_by_one(inner_rhs, div_rhs) {
|
||||
build_suggestion(cx, expr, inner_lhs, div_rhs, &mut applicability);
|
||||
}
|
||||
// (x + (Y - 1)) / Y
|
||||
if inner_op.node == BinOpKind::Add && differ_by_one(inner_rhs, rhs) {
|
||||
build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability);
|
||||
}
|
||||
|
||||
// ((Y - 1) + x) / Y
|
||||
if inner_op.node == BinOpKind::Add && differ_by_one(inner_lhs, div_rhs) {
|
||||
build_suggestion(cx, expr, inner_rhs, div_rhs, &mut applicability);
|
||||
}
|
||||
// ((Y - 1) + x) / Y
|
||||
if inner_op.node == BinOpKind::Add && differ_by_one(inner_lhs, rhs) {
|
||||
build_suggestion(cx, expr, inner_rhs, rhs, &mut applicability);
|
||||
}
|
||||
|
||||
// (x - (-Y - 1)) / Y
|
||||
if inner_op.node == BinOpKind::Sub
|
||||
&& let ExprKind::Unary(UnOp::Neg, abs_div_rhs) = div_rhs.kind
|
||||
&& differ_by_one(abs_div_rhs, inner_rhs)
|
||||
{
|
||||
build_suggestion(cx, expr, inner_lhs, div_rhs, &mut applicability);
|
||||
}
|
||||
// (x - (-Y - 1)) / Y
|
||||
if inner_op.node == BinOpKind::Sub
|
||||
&& let ExprKind::Unary(UnOp::Neg, abs_div_rhs) = rhs.kind
|
||||
&& differ_by_one(abs_div_rhs, inner_rhs)
|
||||
{
|
||||
build_suggestion(cx, expr, inner_lhs, rhs, &mut applicability);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,9 @@ mod float_cmp;
|
|||
mod float_equality_without_abs;
|
||||
mod identity_op;
|
||||
mod integer_division;
|
||||
mod integer_division_remainder_used;
|
||||
mod invalid_upcast_comparisons;
|
||||
mod manual_div_ceil;
|
||||
mod manual_is_multiple_of;
|
||||
mod manual_midpoint;
|
||||
mod misrefactored_assign_op;
|
||||
|
|
@ -463,7 +466,7 @@ declare_clippy_lint! {
|
|||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for statements of the form `(a - b) < f32::EPSILON` or
|
||||
/// `(a - b) < f64::EPSILON`. Notes the missing `.abs()`.
|
||||
/// `(a - b) < f64::EPSILON`. Note the missing `.abs()`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The code without `.abs()` is more likely to have a bug.
|
||||
|
|
@ -616,7 +619,7 @@ declare_clippy_lint! {
|
|||
/// println!("{within_tolerance}"); // true
|
||||
/// ```
|
||||
///
|
||||
/// NB! Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is
|
||||
/// NOTE: Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is
|
||||
/// a different use of the term that is not suitable for floating point equality comparison.
|
||||
/// Indeed, for the example above using `f64::EPSILON` as the allowed error would return `false`.
|
||||
///
|
||||
|
|
@ -679,7 +682,7 @@ declare_clippy_lint! {
|
|||
/// println!("{within_tolerance}"); // true
|
||||
/// ```
|
||||
///
|
||||
/// NB! Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is
|
||||
/// NOTE: Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is
|
||||
/// a different use of the term that is not suitable for floating point equality comparison.
|
||||
/// Indeed, for the example above using `f64::EPSILON` as the allowed error would return `false`.
|
||||
///
|
||||
|
|
@ -860,6 +863,78 @@ declare_clippy_lint! {
|
|||
"manual implementation of `.is_multiple_of()`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for an expression like `(x + (y - 1)) / y` which is a common manual reimplementation
|
||||
/// of `x.div_ceil(y)`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It's simpler, clearer and more readable.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let x: i32 = 7;
|
||||
/// let y: i32 = 4;
|
||||
/// let div = (x + (y - 1)) / y;
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #![feature(int_roundings)]
|
||||
/// let x: i32 = 7;
|
||||
/// let y: i32 = 4;
|
||||
/// let div = x.div_ceil(y);
|
||||
/// ```
|
||||
#[clippy::version = "1.83.0"]
|
||||
pub MANUAL_DIV_CEIL,
|
||||
complexity,
|
||||
"manually reimplementing `div_ceil`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for comparisons where the relation is always either
|
||||
/// true or false, but where one side has been upcast so that the comparison is
|
||||
/// necessary. Only integer types are checked.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// An expression like `let x : u8 = ...; (x as u32) > 300`
|
||||
/// will mistakenly imply that it is possible for `x` to be outside the range of
|
||||
/// `u8`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let x: u8 = 1;
|
||||
/// (x as u32) > 300;
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub INVALID_UPCAST_COMPARISONS,
|
||||
pedantic,
|
||||
"a comparison involving an upcast which is always true or false"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for the usage of division (`/`) and remainder (`%`) operations
|
||||
/// when performed on any integer types using the default `Div` and `Rem` trait implementations.
|
||||
///
|
||||
/// ### Why restrict this?
|
||||
/// In cryptographic contexts, division can result in timing sidechannel vulnerabilities,
|
||||
/// and needs to be replaced with constant-time code instead (e.g. Barrett reduction).
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let my_div = 10 / 2;
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let my_div = 10 >> 1;
|
||||
/// ```
|
||||
#[clippy::version = "1.79.0"]
|
||||
pub INTEGER_DIVISION_REMAINDER_USED,
|
||||
restriction,
|
||||
"use of disallowed default division and remainder operations"
|
||||
}
|
||||
|
||||
pub struct Operators {
|
||||
arithmetic_context: numeric_arithmetic::Context,
|
||||
verbose_bit_mask_threshold: u64,
|
||||
|
|
@ -897,6 +972,7 @@ impl_lint_pass!(Operators => [
|
|||
FLOAT_EQUALITY_WITHOUT_ABS,
|
||||
IDENTITY_OP,
|
||||
INTEGER_DIVISION,
|
||||
INTEGER_DIVISION_REMAINDER_USED,
|
||||
CMP_OWNED,
|
||||
FLOAT_CMP,
|
||||
FLOAT_CMP_CONST,
|
||||
|
|
@ -906,6 +982,8 @@ impl_lint_pass!(Operators => [
|
|||
SELF_ASSIGNMENT,
|
||||
MANUAL_MIDPOINT,
|
||||
MANUAL_IS_MULTIPLE_OF,
|
||||
MANUAL_DIV_CEIL,
|
||||
INVALID_UPCAST_COMPARISONS,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Operators {
|
||||
|
|
@ -921,6 +999,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
|
|||
}
|
||||
erasing_op::check(cx, e, op.node, lhs, rhs);
|
||||
identity_op::check(cx, e, op.node, lhs, rhs);
|
||||
invalid_upcast_comparisons::check(cx, op.node, lhs, rhs, e.span);
|
||||
needless_bitwise_bool::check(cx, e, op.node, lhs, rhs);
|
||||
manual_midpoint::check(cx, e, op.node, lhs, rhs, self.msrv);
|
||||
manual_is_multiple_of::check(cx, e, op.node, lhs, rhs, self.msrv);
|
||||
|
|
@ -933,6 +1012,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
|
|||
duration_subsec::check(cx, e, op.node, lhs, rhs);
|
||||
float_equality_without_abs::check(cx, e, op.node, lhs, rhs);
|
||||
integer_division::check(cx, e, op.node, lhs, rhs);
|
||||
integer_division_remainder_used::check(cx, op.node, lhs, rhs, e.span);
|
||||
cmp_owned::check(cx, op.node, lhs, rhs);
|
||||
float_cmp::check(cx, e, op.node, lhs, rhs);
|
||||
modulo_one::check(cx, e, op.node, rhs);
|
||||
|
|
@ -944,6 +1024,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
|
|||
rhs,
|
||||
self.modulo_arithmetic_allow_comparison_to_zero,
|
||||
);
|
||||
manual_div_ceil::check(cx, e, op.node, lhs, rhs, self.msrv);
|
||||
},
|
||||
ExprKind::AssignOp(op, lhs, rhs) => {
|
||||
let bin_op = op.node.into();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use rustc_ast::ast::BinOpKind::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub};
|
||||
use rustc_ast::ast::{BinOpKind, Expr, ExprKind};
|
||||
|
|
@ -10,7 +10,8 @@ use rustc_span::source_map::Spanned;
|
|||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for operations where precedence may be unclear and suggests to add parentheses.
|
||||
/// It catches a mixed usage of arithmetic and bit shifting/combining operators without parentheses
|
||||
/// It catches a mixed usage of arithmetic and bit shifting/combining operators,
|
||||
/// as well as method calls applied to closures.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Not everyone knows the precedence of those operators by
|
||||
|
|
@ -109,6 +110,19 @@ impl EarlyLintPass for Precedence {
|
|||
},
|
||||
_ => (),
|
||||
}
|
||||
} else if let ExprKind::MethodCall(method_call) = &expr.kind
|
||||
&& let ExprKind::Closure(closure) = &method_call.receiver.kind
|
||||
{
|
||||
span_lint_and_then(cx, PRECEDENCE, expr.span, "precedence might not be obvious", |diag| {
|
||||
diag.multipart_suggestion(
|
||||
"consider parenthesizing the closure",
|
||||
vec![
|
||||
(closure.fn_decl_span.shrink_to_lo(), String::from("(")),
|
||||
(closure.body.span.shrink_to_hi(), String::from(")")),
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,8 +86,7 @@ impl LateLintPass<'_> for SingleRangeInVecInit {
|
|||
return;
|
||||
};
|
||||
|
||||
let ExprKind::Struct(&qpath, [start, end], StructTailExpr::None) = inner_expr.kind
|
||||
else {
|
||||
let ExprKind::Struct(&qpath, [start, end], StructTailExpr::None) = inner_expr.kind else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::qpath_generic_tys;
|
||||
use clippy_utils::res::MaybeResPath;
|
||||
use clippy_utils::source::snippet;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{self as hir, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -13,12 +14,21 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
|||
&& let Some(arg) = qpath_generic_tys(qpath).next()
|
||||
&& arg.basic_res().opt_def_id() == Some(def_id)
|
||||
{
|
||||
span_lint(
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
OPTION_OPTION,
|
||||
hir_ty.span,
|
||||
"consider using `Option<T>` instead of `Option<Option<T>>` or a custom \
|
||||
enum if you need to distinguish all 3 cases",
|
||||
// use just `T` here, as the inner type is not what's problematic
|
||||
"use of `Option<Option<T>>`",
|
||||
|diag| {
|
||||
// but use the specific type here, as:
|
||||
// - this is kind of a suggestion
|
||||
// - it's printed right after the linted type
|
||||
let inner_opt = snippet(cx, arg.span, "_");
|
||||
diag.help(format!(
|
||||
"consider using `{inner_opt}`, or a custom enum if you need to distinguish all 3 cases"
|
||||
));
|
||||
},
|
||||
);
|
||||
true
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -454,6 +454,6 @@ fn extend_with_matching(
|
|||
fn eq_pre_post(ps1: &[Pat], ps2: &[Pat], idx: usize) -> bool {
|
||||
ps1.len() == ps2.len()
|
||||
&& ps1[idx].is_rest() == ps2[idx].is_rest() // Avoid `[x, ..] | [x, 0]` => `[x, .. | 0]`.
|
||||
&& over(&ps1[..idx], &ps2[..idx], |l, r| eq_pat(l, r))
|
||||
&& over(&ps1[idx + 1..], &ps2[idx + 1..], |l, r| eq_pat(l, r))
|
||||
&& over(&ps1[..idx], &ps2[..idx], eq_pat)
|
||||
&& over(&ps1[idx + 1..], &ps2[idx + 1..], eq_pat)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use rustc_ast::{
|
|||
FormatPlaceholder, FormatTrait,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, Impl, Item, ItemKind};
|
||||
use rustc_hir::{Expr, Impl, Item, ItemKind, OwnerId};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
|
@ -240,7 +240,8 @@ declare_clippy_lint! {
|
|||
|
||||
pub struct Write {
|
||||
format_args: FormatArgsStorage,
|
||||
in_debug_impl: bool,
|
||||
// The outermost `impl Debug` we're currently in. While we're in one, `USE_DEBUG` is deactivated
|
||||
outermost_debug_impl: Option<OwnerId>,
|
||||
allow_print_in_tests: bool,
|
||||
}
|
||||
|
||||
|
|
@ -248,10 +249,14 @@ impl Write {
|
|||
pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self {
|
||||
Self {
|
||||
format_args,
|
||||
in_debug_impl: false,
|
||||
outermost_debug_impl: None,
|
||||
allow_print_in_tests: conf.allow_print_in_tests,
|
||||
}
|
||||
}
|
||||
|
||||
fn in_debug_impl(&self) -> bool {
|
||||
self.outermost_debug_impl.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(Write => [
|
||||
|
|
@ -268,14 +273,16 @@ impl_lint_pass!(Write => [
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for Write {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
if is_debug_impl(cx, item) {
|
||||
self.in_debug_impl = true;
|
||||
// Only check for `impl Debug`s if we're not already in one
|
||||
if self.outermost_debug_impl.is_none() && is_debug_impl(cx, item) {
|
||||
self.outermost_debug_impl = Some(item.owner_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
if is_debug_impl(cx, item) {
|
||||
self.in_debug_impl = false;
|
||||
fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
// Only clear `self.outermost_debug_impl` if we're escaping the _outermost_ debug impl
|
||||
if self.outermost_debug_impl == Some(item.owner_id) {
|
||||
self.outermost_debug_impl = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -329,7 +336,7 @@ impl<'tcx> LateLintPass<'tcx> for Write {
|
|||
|
||||
check_literal(cx, format_args, name);
|
||||
|
||||
if !self.in_debug_impl {
|
||||
if !self.in_debug_impl() {
|
||||
for piece in &format_args.template {
|
||||
if let &FormatArgsPiece::Placeholder(FormatPlaceholder {
|
||||
span: Some(span),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use clippy_utils::sym;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty::{self, EarlyBinder, GenericArgKind};
|
||||
use rustc_middle::ty::{self, GenericArgKind};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
declare_tool_lint! {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_utils"
|
||||
version = "0.1.92"
|
||||
version = "0.1.93"
|
||||
edition = "2024"
|
||||
description = "Helpful tools for writing lints, provided as they are used in Clippy"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
|
|||
|
||||
<!-- begin autogenerated nightly -->
|
||||
```
|
||||
nightly-2025-10-16
|
||||
nightly-2025-10-31
|
||||
```
|
||||
<!-- end autogenerated nightly -->
|
||||
|
||||
|
|
|
|||
|
|
@ -48,12 +48,10 @@ pub fn eq_pat(l: &Pat, r: &Pat) -> bool {
|
|||
(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)),
|
||||
(Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, eq_pat),
|
||||
(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_deref(), rqself.as_deref())
|
||||
&& 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, eq_pat)
|
||||
},
|
||||
(Struct(lqself, lp, lfs, lr), Struct(rqself, rp, rfs, rr)) => {
|
||||
lr == rr
|
||||
|
|
@ -61,7 +59,7 @@ pub fn eq_pat(l: &Pat, r: &Pat) -> bool {
|
|||
&& eq_path(lp, rp)
|
||||
&& unordered_over(lfs, rfs, eq_field_pat)
|
||||
},
|
||||
(Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)),
|
||||
(Or(ls), Or(rs)) => unordered_over(ls, rs, eq_pat),
|
||||
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
|
||||
_ => false,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ pub struct Range<'a> {
|
|||
pub end: Option<&'a Expr<'a>>,
|
||||
/// Whether the interval is open or closed.
|
||||
pub limits: ast::RangeLimits,
|
||||
pub span: Span
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl<'a> Range<'a> {
|
||||
|
|
@ -236,14 +236,12 @@ impl<'a> Range<'a> {
|
|||
limits: ast::RangeLimits::HalfOpen,
|
||||
span,
|
||||
}),
|
||||
(hir::LangItem::RangeFrom, [field]) if field.ident.name == sym::start => {
|
||||
Some(Range {
|
||||
start: Some(field.expr),
|
||||
end: None,
|
||||
limits: ast::RangeLimits::HalfOpen,
|
||||
span,
|
||||
})
|
||||
},
|
||||
(hir::LangItem::RangeFrom, [field]) if field.ident.name == sym::start => Some(Range {
|
||||
start: Some(field.expr),
|
||||
end: None,
|
||||
limits: ast::RangeLimits::HalfOpen,
|
||||
span,
|
||||
}),
|
||||
(hir::LangItem::Range, [field1, field2]) => {
|
||||
let (start, end) = match (field1.ident.name, field2.ident.name) {
|
||||
(sym::start, sym::end) => (field1.expr, field2.expr),
|
||||
|
|
@ -257,14 +255,12 @@ impl<'a> Range<'a> {
|
|||
span,
|
||||
})
|
||||
},
|
||||
(hir::LangItem::RangeToInclusive, [field]) if field.ident.name == sym::end => {
|
||||
Some(Range {
|
||||
start: None,
|
||||
end: Some(field.expr),
|
||||
limits: ast::RangeLimits::Closed,
|
||||
span,
|
||||
})
|
||||
},
|
||||
(hir::LangItem::RangeToInclusive, [field]) if field.ident.name == sym::end => Some(Range {
|
||||
start: None,
|
||||
end: Some(field.expr),
|
||||
limits: ast::RangeLimits::Closed,
|
||||
span,
|
||||
}),
|
||||
(hir::LangItem::RangeTo, [field]) if field.ident.name == sym::end => Some(Range {
|
||||
start: None,
|
||||
end: Some(field.expr),
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@ pub fn is_ty_alias(qpath: &QPath<'_>) -> bool {
|
|||
match *qpath {
|
||||
QPath::Resolved(_, path) => matches!(path.res, Res::Def(DefKind::TyAlias | DefKind::AssocTy, ..)),
|
||||
QPath::TypeRelative(ty, _) if let TyKind::Path(qpath) = ty.kind => is_ty_alias(&qpath),
|
||||
_ => false,
|
||||
QPath::TypeRelative(..) => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,9 +107,7 @@ pub trait MaybeQPath<'a>: Copy {
|
|||
fn f(qpath: &QPath<'_>, id: HirId, typeck: &TypeckResults<'_>) -> Res {
|
||||
match *qpath {
|
||||
QPath::Resolved(_, p) => p.res,
|
||||
QPath::TypeRelative(..) if let Some((kind, id)) = typeck.ty_based_def(id) => {
|
||||
Res::Def(kind, id)
|
||||
},
|
||||
QPath::TypeRelative(..) if let Some((kind, id)) = typeck.ty_based_def(id) => Res::Def(kind, id),
|
||||
QPath::TypeRelative(..) => Res::Err,
|
||||
}
|
||||
}
|
||||
|
|
@ -403,7 +401,7 @@ impl<'a> MaybeResPath<'a> for &QPath<'a> {
|
|||
fn opt_res_path(self) -> OptResPath<'a> {
|
||||
match *self {
|
||||
QPath::Resolved(ty, path) => (ty, Some(path)),
|
||||
_ => (None, None),
|
||||
QPath::TypeRelative(..) => (None, None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use rustc_middle::ty::TyCtxt;
|
|||
use rustc_session::Session;
|
||||
use rustc_span::source_map::{SourceMap, original_sp};
|
||||
use rustc_span::{
|
||||
BytePos, DesugaringKind, DUMMY_SP, FileNameDisplayPreference, Pos, RelativeBytePos, SourceFile, SourceFileAndLine,
|
||||
BytePos, DUMMY_SP, DesugaringKind, FileNameDisplayPreference, Pos, RelativeBytePos, SourceFile, SourceFileAndLine,
|
||||
Span, SpanData, SyntaxContext, hygiene,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
|
@ -675,7 +675,7 @@ fn snippet_with_context_sess<'a>(
|
|||
return (
|
||||
snippet_with_applicability_sess(sess, span, default, applicability),
|
||||
false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else(
|
||||
|
|
|
|||
|
|
@ -127,7 +127,11 @@ impl<'a> Sugg<'a> {
|
|||
|
||||
/// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*`
|
||||
/// function variants of `Sugg`, since these use different snippet functions.
|
||||
fn hir_from_snippet(cx: &LateContext<'_>, expr: &hir::Expr<'_>, mut get_snippet: impl FnMut(Span) -> Cow<'a, str>) -> Self {
|
||||
fn hir_from_snippet(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
mut get_snippet: impl FnMut(Span) -> Cow<'a, str>,
|
||||
) -> Self {
|
||||
if let Some(range) = higher::Range::hir(cx, expr) {
|
||||
let op = AssocOp::Range(range.limits);
|
||||
let start = range.start.map_or("".into(), |expr| get_snippet(expr.span));
|
||||
|
|
@ -765,7 +769,7 @@ pub struct DerefClosure {
|
|||
/// such as explicit deref and borrowing cases.
|
||||
/// Returns `None` if no such use cases have been triggered in closure body
|
||||
///
|
||||
/// note: this only works on single line immutable closures with exactly one input parameter.
|
||||
/// note: This only works on immutable closures with exactly one input parameter.
|
||||
pub fn deref_closure_args(cx: &LateContext<'_>, closure: &hir::Expr<'_>) -> Option<DerefClosure> {
|
||||
if let ExprKind::Closure(&Closure {
|
||||
fn_decl, def_id, body, ..
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "declare_clippy_lint"
|
||||
version = "0.1.92"
|
||||
version = "0.1.93"
|
||||
edition = "2024"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[toolchain]
|
||||
# begin autogenerated nightly
|
||||
channel = "nightly-2025-10-16"
|
||||
channel = "nightly-2025-10-31"
|
||||
# end autogenerated nightly
|
||||
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
|
||||
profile = "minimal"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
clippy::no_effect,
|
||||
clippy::unnecessary_operation,
|
||||
clippy::never_loop,
|
||||
clippy::needless_if,
|
||||
clippy::needless_ifs,
|
||||
clippy::collapsible_if,
|
||||
clippy::blocks_in_conditions,
|
||||
clippy::single_match,
|
||||
|
|
|
|||
|
|
@ -57,3 +57,15 @@ macro_rules! bad_transmute {
|
|||
std::mem::transmute($e)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[rustfmt::skip]
|
||||
macro_rules! double_parens {
|
||||
($a:expr, $b:expr, $c:expr, $d:expr) => {{
|
||||
let a = ($a);
|
||||
let a = (());
|
||||
let b = ((5));
|
||||
let c = std::convert::identity((5));
|
||||
InterruptMask((($a.union($b).union($c).union($d)).into_bits()) as u32)
|
||||
}};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,3 +230,14 @@ pub fn allow_lint_same_span_derive(input: TokenStream) -> TokenStream {
|
|||
span_help(Group::new(Delimiter::Brace, TokenStream::new()).into()),
|
||||
])
|
||||
}
|
||||
|
||||
#[proc_macro_derive(DoubleParens)]
|
||||
pub fn derive_double_parens(_: TokenStream) -> TokenStream {
|
||||
quote! {
|
||||
fn foo() {
|
||||
let a = (());
|
||||
let b = ((5));
|
||||
let c = std::convert::identity((5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#![feature(proc_macro_span)]
|
||||
#![allow(clippy::needless_if, dead_code)]
|
||||
#![allow(clippy::needless_ifs, dead_code)]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#![allow(
|
||||
unused,
|
||||
unnecessary_transmutes,
|
||||
clippy::needless_if,
|
||||
clippy::needless_ifs,
|
||||
clippy::missing_transmute_annotations
|
||||
)]
|
||||
#![warn(clippy::nonminimal_bool)]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#![allow(
|
||||
unused,
|
||||
unnecessary_transmutes,
|
||||
clippy::needless_if,
|
||||
clippy::needless_ifs,
|
||||
clippy::missing_transmute_annotations
|
||||
)]
|
||||
#![warn(clippy::nonminimal_bool)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(non_local_definitions, clippy::needless_if)]
|
||||
#![allow(non_local_definitions, clippy::needless_ifs)]
|
||||
#![warn(clippy::bool_comparison)]
|
||||
#![allow(clippy::non_canonical_partial_ord_impl)]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(non_local_definitions, clippy::needless_if)]
|
||||
#![allow(non_local_definitions, clippy::needless_ifs)]
|
||||
#![warn(clippy::bool_comparison)]
|
||||
#![allow(clippy::non_canonical_partial_ord_impl)]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#![allow(
|
||||
unused,
|
||||
clippy::needless_if,
|
||||
clippy::needless_ifs,
|
||||
clippy::redundant_clone,
|
||||
clippy::derive_partial_eq_without_eq
|
||||
)] // See #5700
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#![allow(
|
||||
unused,
|
||||
clippy::needless_if,
|
||||
clippy::needless_ifs,
|
||||
clippy::redundant_clone,
|
||||
clippy::derive_partial_eq_without_eq
|
||||
)] // See #5700
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_if)]
|
||||
#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_ifs)]
|
||||
#![warn(clippy::collapsible_if, clippy::collapsible_else_if)]
|
||||
|
||||
#[rustfmt::skip]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_if)]
|
||||
#![allow(clippy::assertions_on_constants, clippy::equatable_if_let, clippy::needless_ifs)]
|
||||
#![warn(clippy::collapsible_if, clippy::collapsible_else_if)]
|
||||
|
||||
#[rustfmt::skip]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#![allow(
|
||||
clippy::assertions_on_constants,
|
||||
clippy::equatable_if_let,
|
||||
clippy::needless_if,
|
||||
clippy::needless_ifs,
|
||||
clippy::nonminimal_bool,
|
||||
clippy::eq_op,
|
||||
clippy::redundant_pattern_matching
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#![allow(
|
||||
clippy::assertions_on_constants,
|
||||
clippy::equatable_if_let,
|
||||
clippy::needless_if,
|
||||
clippy::needless_ifs,
|
||||
clippy::nonminimal_bool,
|
||||
clippy::eq_op,
|
||||
clippy::redundant_pattern_matching
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#![warn(clippy::comparison_to_empty)]
|
||||
#![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)]
|
||||
#![allow(clippy::borrow_deref_ref, clippy::needless_ifs, clippy::useless_vec)]
|
||||
|
||||
fn main() {
|
||||
// Disallow comparisons to empty
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#![warn(clippy::comparison_to_empty)]
|
||||
#![allow(clippy::borrow_deref_ref, clippy::needless_if, clippy::useless_vec)]
|
||||
#![allow(clippy::borrow_deref_ref, clippy::needless_ifs, clippy::useless_vec)]
|
||||
|
||||
fn main() {
|
||||
// Disallow comparisons to empty
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(clippy::needless_if)]
|
||||
#![allow(clippy::needless_ifs)]
|
||||
|
||||
#[derive(Default)]
|
||||
struct A<T> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(clippy::needless_if)]
|
||||
#![allow(clippy::needless_ifs)]
|
||||
|
||||
#[derive(Default)]
|
||||
struct A<T> {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//@aux-build:proc_macros.rs
|
||||
#![allow(
|
||||
dead_code,
|
||||
clippy::needless_if,
|
||||
clippy::needless_ifs,
|
||||
clippy::similar_names,
|
||||
clippy::single_match,
|
||||
clippy::toplevel_ref_arg,
|
||||
|
|
|
|||
|
|
@ -1,33 +1,14 @@
|
|||
#![warn(clippy::needless_doctest_main)]
|
||||
//! issue 10491:
|
||||
//! ```rust,no_test
|
||||
//! use std::collections::HashMap;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let mut m = HashMap::new();
|
||||
//! m.insert(1u32, 2u32);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
/// some description here
|
||||
/// ```rust,no_test
|
||||
/// fn main() {
|
||||
/// foo()
|
||||
/// }
|
||||
/// ```
|
||||
fn foo() {}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// Description
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
//~^ error: needless `fn main` in doctest
|
||||
//~^ needless_doctest_main
|
||||
/// let a = 0;
|
||||
/// }
|
||||
/// ```
|
||||
fn mulpipulpi() {}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// With a `#[no_main]`
|
||||
/// ```rust
|
||||
/// #[no_main]
|
||||
|
|
@ -45,7 +26,6 @@ fn pulpimulpi() {}
|
|||
/// ```
|
||||
fn plumilupi() {}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// Additional function, shouldn't trigger
|
||||
/// ```rust
|
||||
/// fn additional_function() {
|
||||
|
|
@ -58,7 +38,6 @@ fn plumilupi() {}
|
|||
/// ```
|
||||
fn mlupipupi() {}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// Additional function AFTER main, shouldn't trigger
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
|
|
@ -71,22 +50,19 @@ fn mlupipupi() {}
|
|||
/// ```
|
||||
fn lumpimupli() {}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// Ignore code block, should not lint at all
|
||||
/// ```rust, ignore
|
||||
/// fn main() {
|
||||
//~^ error: needless `fn main` in doctest
|
||||
/// // Hi!
|
||||
/// let _ = 0;
|
||||
/// }
|
||||
/// ```
|
||||
fn mpulpilumi() {}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// Spaces in weird positions (including an \u{A0} after `main`)
|
||||
/// ```rust
|
||||
/// fn main (){
|
||||
//~^ error: needless `fn main` in doctest
|
||||
//~^ needless_doctest_main
|
||||
/// let _ = 0;
|
||||
/// }
|
||||
/// ```
|
||||
|
|
|
|||
|
|
@ -1,36 +1,17 @@
|
|||
error: needless `fn main` in doctest
|
||||
--> tests/ui/doc/needless_doctest_main.rs:23:5
|
||||
--> tests/ui/doc/needless_doctest_main.rs:5:5
|
||||
|
|
||||
LL | /// fn main() {
|
||||
| _____^
|
||||
LL | |
|
||||
LL | | /// let a = 0;
|
||||
LL | | /// }
|
||||
| |_____^
|
||||
LL | /// fn main() {
|
||||
| ^^^^^^^
|
||||
|
|
||||
= note: `-D clippy::needless-doctest-main` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::needless_doctest_main)]`
|
||||
|
||||
error: needless `fn main` in doctest
|
||||
--> tests/ui/doc/needless_doctest_main.rs:77:5
|
||||
--> tests/ui/doc/needless_doctest_main.rs:64:5
|
||||
|
|
||||
LL | /// fn main() {
|
||||
| _____^
|
||||
LL | |
|
||||
LL | | /// // Hi!
|
||||
LL | | /// let _ = 0;
|
||||
LL | | /// }
|
||||
| |_____^
|
||||
LL | /// fn main (){
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error: needless `fn main` in doctest
|
||||
--> tests/ui/doc/needless_doctest_main.rs:88:5
|
||||
|
|
||||
LL | /// fn main (){
|
||||
| _____^
|
||||
LL | |
|
||||
LL | | /// let _ = 0;
|
||||
LL | | /// }
|
||||
| |_____^
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(clippy::needless_if)]
|
||||
#![allow(clippy::needless_ifs)]
|
||||
|
||||
fn main() {
|
||||
let x = 1;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(clippy::needless_if)]
|
||||
#![allow(clippy::needless_ifs)]
|
||||
|
||||
fn main() {
|
||||
let x = 1;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
//@aux-build:proc_macros.rs
|
||||
//@aux-build:proc_macro_derive.rs
|
||||
//@aux-build:macro_rules.rs
|
||||
#![warn(clippy::double_parens)]
|
||||
#![expect(clippy::eq_op, clippy::no_effect)]
|
||||
#![feature(custom_inner_attributes)]
|
||||
#![rustfmt::skip]
|
||||
|
||||
use proc_macros::{external, with_span};
|
||||
|
||||
fn dummy_fn<T>(_: T) {}
|
||||
|
||||
struct DummyStruct;
|
||||
|
|
@ -96,4 +101,64 @@ fn issue9000(x: DummyStruct) {
|
|||
//~^ double_parens
|
||||
}
|
||||
|
||||
fn issue15892() {
|
||||
use macro_rules::double_parens as double_parens_external;
|
||||
|
||||
macro_rules! double_parens{
|
||||
($a:expr, $b:expr, $c:expr, $d:expr) => {{
|
||||
let a = ($a);
|
||||
let a = ();
|
||||
//~^ double_parens
|
||||
let b = (5);
|
||||
//~^ double_parens
|
||||
let c = std::convert::identity(5);
|
||||
//~^ double_parens
|
||||
InterruptMask((($a.union($b).union($c).union($d)).into_bits()) as u32)
|
||||
}};
|
||||
}
|
||||
|
||||
// Don't lint: external macro
|
||||
(external!((5)));
|
||||
external!(((5)));
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct InterruptMask(u32);
|
||||
|
||||
impl InterruptMask {
|
||||
pub const OE: InterruptMask = InterruptMask(1 << 10);
|
||||
pub const BE: InterruptMask = InterruptMask(1 << 9);
|
||||
pub const PE: InterruptMask = InterruptMask(1 << 8);
|
||||
pub const FE: InterruptMask = InterruptMask(1 << 7);
|
||||
// Lint: internal macro
|
||||
pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE);
|
||||
// Don't lint: external macro
|
||||
pub const F: InterruptMask = double_parens_external!((Self::OE), Self::BE, Self::PE, Self::FE);
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
pub const G: InterruptMask = external!(
|
||||
InterruptMask((((Self::OE.union(Self::BE).union(Self::PE).union(Self::FE))).into_bits()) as u32)
|
||||
);
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
// Don't lint: external proc-macro
|
||||
pub const H: InterruptMask = with_span!(span
|
||||
InterruptMask((((Self::OE.union(Self::BE).union(Self::PE).union(Self::FE))).into_bits()) as u32)
|
||||
);
|
||||
pub const fn into_bits(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
#[must_use]
|
||||
pub const fn union(self, rhs: Self) -> Self {
|
||||
InterruptMask(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn issue15940() {
|
||||
use proc_macro_derive::DoubleParens;
|
||||
|
||||
#[derive(DoubleParens)]
|
||||
// Don't lint: external derive macro
|
||||
pub struct Person;
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
//@aux-build:proc_macros.rs
|
||||
//@aux-build:proc_macro_derive.rs
|
||||
//@aux-build:macro_rules.rs
|
||||
#![warn(clippy::double_parens)]
|
||||
#![expect(clippy::eq_op, clippy::no_effect)]
|
||||
#![feature(custom_inner_attributes)]
|
||||
#![rustfmt::skip]
|
||||
|
||||
use proc_macros::{external, with_span};
|
||||
|
||||
fn dummy_fn<T>(_: T) {}
|
||||
|
||||
struct DummyStruct;
|
||||
|
|
@ -96,4 +101,64 @@ fn issue9000(x: DummyStruct) {
|
|||
//~^ double_parens
|
||||
}
|
||||
|
||||
fn issue15892() {
|
||||
use macro_rules::double_parens as double_parens_external;
|
||||
|
||||
macro_rules! double_parens{
|
||||
($a:expr, $b:expr, $c:expr, $d:expr) => {{
|
||||
let a = ($a);
|
||||
let a = (());
|
||||
//~^ double_parens
|
||||
let b = ((5));
|
||||
//~^ double_parens
|
||||
let c = std::convert::identity((5));
|
||||
//~^ double_parens
|
||||
InterruptMask((($a.union($b).union($c).union($d)).into_bits()) as u32)
|
||||
}};
|
||||
}
|
||||
|
||||
// Don't lint: external macro
|
||||
(external!((5)));
|
||||
external!(((5)));
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct InterruptMask(u32);
|
||||
|
||||
impl InterruptMask {
|
||||
pub const OE: InterruptMask = InterruptMask(1 << 10);
|
||||
pub const BE: InterruptMask = InterruptMask(1 << 9);
|
||||
pub const PE: InterruptMask = InterruptMask(1 << 8);
|
||||
pub const FE: InterruptMask = InterruptMask(1 << 7);
|
||||
// Lint: internal macro
|
||||
pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE);
|
||||
// Don't lint: external macro
|
||||
pub const F: InterruptMask = double_parens_external!((Self::OE), Self::BE, Self::PE, Self::FE);
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
pub const G: InterruptMask = external!(
|
||||
InterruptMask((((Self::OE.union(Self::BE).union(Self::PE).union(Self::FE))).into_bits()) as u32)
|
||||
);
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
// Don't lint: external proc-macro
|
||||
pub const H: InterruptMask = with_span!(span
|
||||
InterruptMask((((Self::OE.union(Self::BE).union(Self::PE).union(Self::FE))).into_bits()) as u32)
|
||||
);
|
||||
pub const fn into_bits(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
#[must_use]
|
||||
pub const fn union(self, rhs: Self) -> Self {
|
||||
InterruptMask(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn issue15940() {
|
||||
use proc_macro_derive::DoubleParens;
|
||||
|
||||
#[derive(DoubleParens)]
|
||||
// Don't lint: external derive macro
|
||||
pub struct Person;
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
error: unnecessary parentheses
|
||||
--> tests/ui/double_parens.rs:15:5
|
||||
--> tests/ui/double_parens.rs:20:5
|
||||
|
|
||||
LL | ((0))
|
||||
| ^^^^^ help: remove them: `(0)`
|
||||
|
|
@ -8,37 +8,37 @@ LL | ((0))
|
|||
= help: to override `-D warnings` add `#[allow(clippy::double_parens)]`
|
||||
|
||||
error: unnecessary parentheses
|
||||
--> tests/ui/double_parens.rs:20:14
|
||||
--> tests/ui/double_parens.rs:25:14
|
||||
|
|
||||
LL | dummy_fn((0));
|
||||
| ^^^ help: remove them: `0`
|
||||
|
||||
error: unnecessary parentheses
|
||||
--> tests/ui/double_parens.rs:25:20
|
||||
--> tests/ui/double_parens.rs:30:20
|
||||
|
|
||||
LL | x.dummy_method((0));
|
||||
| ^^^ help: remove them: `0`
|
||||
|
||||
error: unnecessary parentheses
|
||||
--> tests/ui/double_parens.rs:30:5
|
||||
--> tests/ui/double_parens.rs:35:5
|
||||
|
|
||||
LL | ((1, 2))
|
||||
| ^^^^^^^^ help: remove them: `(1, 2)`
|
||||
|
||||
error: unnecessary parentheses
|
||||
--> tests/ui/double_parens.rs:36:5
|
||||
--> tests/ui/double_parens.rs:41:5
|
||||
|
|
||||
LL | (())
|
||||
| ^^^^ help: remove them: `()`
|
||||
|
||||
error: unnecessary parentheses
|
||||
--> tests/ui/double_parens.rs:59:16
|
||||
--> tests/ui/double_parens.rs:64:16
|
||||
|
|
||||
LL | assert_eq!(((1, 2)), (1, 2), "Error");
|
||||
| ^^^^^^^^ help: remove them: `(1, 2)`
|
||||
|
||||
error: unnecessary parentheses
|
||||
--> tests/ui/double_parens.rs:84:16
|
||||
--> tests/ui/double_parens.rs:89:16
|
||||
|
|
||||
LL | () => {((100))}
|
||||
| ^^^^^^^ help: remove them: `(100)`
|
||||
|
|
@ -49,22 +49,55 @@ LL | bar!();
|
|||
= note: this error originates in the macro `bar` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unnecessary parentheses
|
||||
--> tests/ui/double_parens.rs:91:5
|
||||
--> tests/ui/double_parens.rs:96:5
|
||||
|
|
||||
LL | ((vec![1, 2]));
|
||||
| ^^^^^^^^^^^^^^ help: remove them: `(vec![1, 2])`
|
||||
|
||||
error: unnecessary parentheses
|
||||
--> tests/ui/double_parens.rs:93:14
|
||||
--> tests/ui/double_parens.rs:98:14
|
||||
|
|
||||
LL | dummy_fn((vec![1, 2]));
|
||||
| ^^^^^^^^^^^^ help: remove them: `vec![1, 2]`
|
||||
|
||||
error: unnecessary parentheses
|
||||
--> tests/ui/double_parens.rs:95:20
|
||||
--> tests/ui/double_parens.rs:100:20
|
||||
|
|
||||
LL | x.dummy_method((vec![1, 2]));
|
||||
| ^^^^^^^^^^^^ help: remove them: `vec![1, 2]`
|
||||
|
||||
error: aborting due to 10 previous errors
|
||||
error: unnecessary parentheses
|
||||
--> tests/ui/double_parens.rs:110:21
|
||||
|
|
||||
LL | let a = (());
|
||||
| ^^^^ help: remove them: `()`
|
||||
...
|
||||
LL | pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE);
|
||||
| -------------------------------------------------------- in this macro invocation
|
||||
|
|
||||
= note: this error originates in the macro `double_parens` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unnecessary parentheses
|
||||
--> tests/ui/double_parens.rs:112:21
|
||||
|
|
||||
LL | let b = ((5));
|
||||
| ^^^^^ help: remove them: `(5)`
|
||||
...
|
||||
LL | pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE);
|
||||
| -------------------------------------------------------- in this macro invocation
|
||||
|
|
||||
= note: this error originates in the macro `double_parens` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unnecessary parentheses
|
||||
--> tests/ui/double_parens.rs:114:44
|
||||
|
|
||||
LL | let c = std::convert::identity((5));
|
||||
| ^^^ help: remove them: `5`
|
||||
...
|
||||
LL | pub const E: InterruptMask = double_parens!((Self::OE), Self::BE, Self::PE, Self::FE);
|
||||
| -------------------------------------------------------- in this macro invocation
|
||||
|
|
||||
= note: this error originates in the macro `double_parens` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: aborting due to 13 previous errors
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue