Merge commit '3e5a02b13b1244545454752c6629b767522a44b1' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2024-06-13 12:30:48 +02:00
parent 35f54fd439
commit 3bff119f63
207 changed files with 3098 additions and 1667 deletions

View File

@ -6,11 +6,69 @@ document.
## Unreleased / Beta / In Rust Nightly
[93f0a9a9...master](https://github.com/rust-lang/rust-clippy/compare/93f0a9a9...master)
[ca3b3937...master](https://github.com/rust-lang/rust-clippy/compare/ca3b3937...master)
## Rust 1.79
Current stable, released 2024-06-13
[View all 102 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2024-03-08T11%3A13%3A58Z..2024-04-18T15%3A50%3A50Z+base%3Amaster)
### New Lints
* Added [`legacy_numeric_constants`] to `style`
[#12312](https://github.com/rust-lang/rust-clippy/pull/12312)
* Added [`missing_transmute_annotations`] to `suspicious`
[#12239](https://github.com/rust-lang/rust-clippy/pull/12239)
* Added [`integer_division_remainder_used`] to `restriction`
[#12451](https://github.com/rust-lang/rust-clippy/pull/12451)
* Added [`duplicated_attributes`] to `suspicious`
[#12378](https://github.com/rust-lang/rust-clippy/pull/12378)
* Added [`manual_unwrap_or_default`] to `suspicious`
[#12440](https://github.com/rust-lang/rust-clippy/pull/12440)
* Added [`zero_repeat_side_effects`] to `suspicious`
[#12449](https://github.com/rust-lang/rust-clippy/pull/12449)
* Added [`const_is_empty`] to `suspicious`
[#12310](https://github.com/rust-lang/rust-clippy/pull/12310)
### Moves and Deprecations
* Moved [`box_default`] to `style` (From `perf`)
[#12601](https://github.com/rust-lang/rust-clippy/pull/12601)
* Moved [`manual_clamp`] to `complexity` (From `nursery` now warn-by-default)
[#12543](https://github.com/rust-lang/rust-clippy/pull/12543)
* Moved [`readonly_write_lock`] to `perf` (From `nursery` now warn-by-default)
[#12479](https://github.com/rust-lang/rust-clippy/pull/12479)
### Enhancements
* [`module_name_repetitions`]: Added the [`allowed-prefixes`] configuration to allow common prefixes.
[#12573](https://github.com/rust-lang/rust-clippy/pull/12573)
* [`cast_sign_loss`], [`cast_possible_truncation`], [`cast_lossless`]: Are now allowed in macros
[#12631](https://github.com/rust-lang/rust-clippy/pull/12631)
* [`manual_clamp`]: Now only lints on constant min and max values
[#12543](https://github.com/rust-lang/rust-clippy/pull/12543)
* [`assigning_clones`]: Now considers the [`msrv`] configuration
[#12511](https://github.com/rust-lang/rust-clippy/pull/12511)
* [`needless_return`], [`useless_let_if_seq`], [`mut_mut`], [`read_zero_byte_vec`], [`unused_io_amount`],
[`unused_peekable`]: Now respects `#[allow]` attributes on the affected statement instead
[#12446](https://github.com/rust-lang/rust-clippy/pull/12446)
### False Positive Fixes
* [`cast_lossless`]: No longer lints when casting to `u128`
[#12496](https://github.com/rust-lang/rust-clippy/pull/12496)
* [`std_instead_of_core`] No longer lints on modules that are only in `std`
[#12447](https://github.com/rust-lang/rust-clippy/pull/12447)
### ICE Fixes
* [`needless_return`]: No longer crashes on non-ascii characters
[#12493](https://github.com/rust-lang/rust-clippy/pull/12493)
## Rust 1.78
Current stable, released 2024-05-02
Released 2024-05-02
[View all 112 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2024-01-26T05%3A46%3A23Z..2024-03-07T16%3A25%3A52Z+base%3Amaster)
@ -5474,6 +5532,7 @@ Released 2018-09-13
[`manual_next_back`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_next_back
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
[`manual_pattern_char_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_pattern_char_comparison
[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
[`manual_range_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_patterns
[`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid
@ -5567,6 +5626,7 @@ Released 2018-09-13
[`needless_borrow`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
[`needless_borrowed_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrowed_reference
[`needless_borrows_for_generic_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrows_for_generic_args
[`needless_character_iteration`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_character_iteration
[`needless_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect
[`needless_continue`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_continue
[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main
@ -5576,6 +5636,7 @@ Released 2018-09-13
[`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
[`needless_maybe_sized`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_maybe_sized
[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
[`needless_option_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_take
[`needless_parens_on_range_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_parens_on_range_literals

View File

@ -1,6 +1,6 @@
[package]
name = "clippy"
version = "0.1.80"
version = "0.1.81"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View File

@ -172,7 +172,7 @@ You can add options to your code to `allow`/`warn`/`deny` Clippy lints:
Note: `allow` means to suppress the lint for your code. With `warn` the lint
will only emit a warning, while with `deny` the lint will emit an error, when
triggering for your code. An error causes clippy to exit with an error code, so
triggering for your code. An error causes Clippy to exit with an error code, so
is useful in scripts like CI/CD.
If you do not want to include your lint levels in your code, you can globally
@ -238,7 +238,7 @@ define the `CLIPPY_DISABLE_DOCS_LINKS` environment variable.
### Specifying the minimum supported Rust version
Projects that intend to support old versions of Rust can disable lints pertaining to newer features by
specifying the minimum supported Rust version (MSRV) in the clippy configuration file.
specifying the minimum supported Rust version (MSRV) in the Clippy configuration file.
```toml
msrv = "1.30.0"

View File

@ -99,7 +99,7 @@ For more details and options, refer to the Cargo documentation.
### Specifying the minimum supported Rust version
Projects that intend to support old versions of Rust can disable lints pertaining to newer features by specifying the
minimum supported Rust version (MSRV) in the clippy configuration file.
minimum supported Rust version (MSRV) in the Clippy configuration file.
```toml
msrv = "1.30.0"

View File

@ -107,7 +107,7 @@ More about [intellij] command usage and reasons.
## lintcheck
`cargo lintcheck` will build and run clippy on a fixed set of crates and
`cargo lintcheck` will build and run Clippy on a fixed set of crates and
generate a log of the results. You can `git diff` the updated log against its
previous version and see what impact your lint made on a small set of crates.
If you add a new lint, please audit the resulting warnings and make sure there

View File

@ -163,11 +163,11 @@ declare_clippy_lint! {
///
/// ### Example
/// ```rust
/// // example code where clippy issues a warning
/// // example code where Clippy issues a warning
/// ```
/// Use instead:
/// ```rust
/// // example code which does not raise clippy warning
/// // example code which does not raise Clippy warning
/// ```
#[clippy::version = "1.70.0"] // <- In which version was this implemented, keep it up to date!
pub LINT_NAME, // <- The lint name IN_ALL_CAPS

View File

@ -428,7 +428,7 @@ selection of possible matches is produced by the pattern syntax. In the second
stage, the named subpattern references can be used to do additional tests like
asserting that a node hasn't been created as part of a macro expansion.
## Implementing clippy lints using patterns
## Implementing Clippy lints using patterns
As a "real-world" example, I re-implemented the `collapsible_if` lint using
patterns. The code can be found
@ -572,7 +572,7 @@ The pattern syntax and the *PatternTree* are independent of specific syntax tree
implementations (rust ast / hir, syn, ...). When looking at the different
pattern examples in the previous sections, it can be seen that the patterns
don't contain any information specific to a certain syntax tree implementation.
In contrast, clippy lints currently match against ast / hir syntax tree nodes
In contrast, Clippy lints currently match against ast / hir syntax tree nodes
and therefore directly depend on their implementation.
The connection between the *PatternTree* and specific syntax tree
@ -690,7 +690,7 @@ change, only the `IsMatch` trait implementations need to be adapted and existing
lints can remain unchanged. This also means that if the `IsMatch` trait
implementations were integrated into the compiler, updating the `IsMatch`
implementations would be required for the compiler to compile successfully. This
could reduce the number of times clippy breaks because of changes in the
could reduce the number of times Clippy breaks because of changes in the
compiler. Another advantage of the pattern's independence is that converting an
`EarlyLintPass` lint into a `LatePassLint` wouldn't require rewriting the whole
pattern matching code. In fact, the pattern might work just fine without any
@ -777,7 +777,7 @@ complexity to solve a relatively minor problem.
The issue of users not knowing about the *PatternTree* structure could be solved
by a tool that, given a rust program, generates a pattern that matches only this
program (similar to the clippy author lint).
program (similar to the Clippy author lint).
For some simple cases (like the first example above), it might be possible to
successfully mix Rust and pattern syntax. This space could be further explored
@ -789,7 +789,7 @@ The pattern syntax is heavily inspired by regular expressions (repetitions,
alternatives, sequences, ...).
From what I've seen until now, other linters also implement lints that directly
work on syntax tree data structures, just like clippy does currently. I would
work on syntax tree data structures, just like Clippy does currently. I would
therefore consider the pattern syntax to be *new*, but please correct me if I'm
wrong.
@ -982,5 +982,5 @@ pattern!{
}
```
In the future, clippy could use this system to also provide lints for custom
In the future, Clippy could use this system to also provide lints for custom
syntaxes like those found in macros.

View File

@ -1,6 +1,6 @@
[package]
name = "clippy_config"
version = "0.1.80"
version = "0.1.81"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -1,5 +1,5 @@
This is a dummy crate to publish to crates.io. It primarily exists to ensure
that folks trying to install clippy from crates.io get redirected to the
that folks trying to install Clippy from crates.io get redirected to the
`rustup` technique.
Before publishing, be sure to rename `clippy_dummy` to `clippy` in `Cargo.toml`,

View File

@ -1,9 +1,9 @@
Installing clippy via crates.io is deprecated. Please use the following:
Installing Clippy via crates.io is deprecated. Please use the following:
```terminal
rustup component add clippy
```
on a Rust version 1.29 or later. You may need to run `rustup self update` if it complains about a missing clippy binary.
on a Rust version 1.29 or later. You may need to run `rustup self update` if it complains about a missing Clippy binary.
See [the homepage](https://github.com/rust-lang/rust-clippy/#clippy) for more information

View File

@ -1,6 +1,6 @@
[package]
name = "clippy_lints"
version = "0.1.80"
version = "0.1.81"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View File

@ -1,51 +0,0 @@
use super::{Attribute, MAYBE_MISUSED_CFG};
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_ast::{MetaItemKind, NestedMetaItem};
use rustc_errors::Applicability;
use rustc_lint::EarlyContext;
use rustc_span::sym;
pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute) {
if attr.has_name(sym::cfg)
&& let Some(items) = attr.meta_item_list()
{
check_nested_misused_cfg(cx, &items);
}
}
fn check_nested_misused_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
for item in items {
if let NestedMetaItem::MetaItem(meta) = item {
if let Some(ident) = meta.ident()
&& ident.name.as_str() == "features"
&& let Some(val) = meta.value_str()
{
span_lint_and_sugg(
cx,
MAYBE_MISUSED_CFG,
meta.span,
"'feature' may be misspelled as 'features'",
"did you mean",
format!("feature = \"{val}\""),
Applicability::MaybeIncorrect,
);
}
if let MetaItemKind::List(list) = &meta.kind {
check_nested_misused_cfg(cx, list);
// If this is not a list, then we check for `cfg(test)`.
} else if let Some(ident) = meta.ident()
&& matches!(ident.name.as_str(), "tests" | "Test")
{
span_lint_and_sugg(
cx,
MAYBE_MISUSED_CFG,
meta.span,
format!("'test' may be misspelled as '{}'", ident.name.as_str()),
"did you mean",
"test".to_string(),
Applicability::MaybeIncorrect,
);
}
}
}
}

View File

@ -1,90 +0,0 @@
use super::{Attribute, MISMATCHED_TARGET_OS};
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::{MetaItemKind, NestedMetaItem};
use rustc_errors::Applicability;
use rustc_lint::EarlyContext;
use rustc_span::{sym, Span};
static UNIX_SYSTEMS: &[&str] = &[
"android",
"dragonfly",
"emscripten",
"freebsd",
"fuchsia",
"haiku",
"illumos",
"ios",
"l4re",
"linux",
"macos",
"netbsd",
"openbsd",
"redox",
"solaris",
"vxworks",
];
// NOTE: windows is excluded from the list because it's also a valid target family.
static NON_UNIX_SYSTEMS: &[&str] = &["hermit", "none", "wasi"];
pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute) {
fn find_os(name: &str) -> Option<&'static str> {
UNIX_SYSTEMS
.iter()
.chain(NON_UNIX_SYSTEMS.iter())
.find(|&&os| os == name)
.copied()
}
fn is_unix(name: &str) -> bool {
UNIX_SYSTEMS.iter().any(|&os| os == name)
}
fn find_mismatched_target_os(items: &[NestedMetaItem]) -> Vec<(&str, Span)> {
let mut mismatched = Vec::new();
for item in items {
if let NestedMetaItem::MetaItem(meta) = item {
match &meta.kind {
MetaItemKind::List(list) => {
mismatched.extend(find_mismatched_target_os(list));
},
MetaItemKind::Word => {
if let Some(ident) = meta.ident()
&& let Some(os) = find_os(ident.name.as_str())
{
mismatched.push((os, ident.span));
}
},
MetaItemKind::NameValue(..) => {},
}
}
}
mismatched
}
if attr.has_name(sym::cfg)
&& let Some(list) = attr.meta_item_list()
&& let mismatched = find_mismatched_target_os(&list)
&& !mismatched.is_empty()
{
let mess = "operating system used in target family position";
span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
// Avoid showing the unix suggestion multiple times in case
// we have more than one mismatch for unix-like systems
let mut unix_suggested = false;
for (os, span) in mismatched {
let sugg = format!("target_os = \"{os}\"");
diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
if !unix_suggested && is_unix(os) {
diag.help("did you mean `unix`?");
unix_suggested = true;
}
}
});
}
}

View File

@ -7,8 +7,6 @@ mod deprecated_semver;
mod duplicated_attributes;
mod empty_line_after;
mod inline_always;
mod maybe_misused_cfg;
mod mismatched_target_os;
mod mixed_attributes_style;
mod non_minimal_cfg;
mod should_panic_without_expect;
@ -270,39 +268,6 @@ declare_clippy_lint! {
"usage of `cfg_attr(rustfmt)` instead of tool attributes"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for cfg attributes having operating systems used in target family position.
///
/// ### Why is this bad?
/// The configuration option will not be recognised and the related item will not be included
/// by the conditional compilation engine.
///
/// ### Example
/// ```no_run
/// #[cfg(linux)]
/// fn conditional() { }
/// ```
///
/// Use instead:
/// ```no_run
/// # mod hidden {
/// #[cfg(target_os = "linux")]
/// fn conditional() { }
/// # }
///
/// // or
///
/// #[cfg(unix)]
/// fn conditional() { }
/// ```
/// Check the [Rust Reference](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os) for more details.
#[clippy::version = "1.45.0"]
pub MISMATCHED_TARGET_OS,
correctness,
"usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for attributes that allow lints without a reason.
@ -391,38 +356,6 @@ declare_clippy_lint! {
"ensure that all `cfg(any())` and `cfg(all())` have more than one condition"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `#[cfg(features = "...")]` and suggests to replace it with
/// `#[cfg(feature = "...")]`.
///
/// It also checks if `cfg(test)` was misspelled.
///
/// ### Why is this bad?
/// Misspelling `feature` as `features` or `test` as `tests` can be sometimes hard to spot. It
/// may cause conditional compilation not work quietly.
///
/// ### Example
/// ```no_run
/// #[cfg(features = "some-feature")]
/// fn conditional() { }
/// #[cfg(tests)]
/// mod tests { }
/// ```
///
/// Use instead:
/// ```no_run
/// #[cfg(feature = "some-feature")]
/// fn conditional() { }
/// #[cfg(test)]
/// mod tests { }
/// ```
#[clippy::version = "1.69.0"]
pub MAYBE_MISUSED_CFG,
suspicious,
"prevent from misusing the wrong attr name"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `#[cfg_attr(feature = "cargo-clippy", ...)]` and for
@ -530,7 +463,7 @@ declare_clippy_lint! {
/// #[allow(dead_code)]
/// fn foo() {}
/// ```
#[clippy::version = "1.78.0"]
#[clippy::version = "1.79.0"]
pub DUPLICATED_ATTRIBUTES,
suspicious,
"duplicated attribute"
@ -612,11 +545,9 @@ pub struct EarlyAttributes {
impl_lint_pass!(EarlyAttributes => [
DEPRECATED_CFG_ATTR,
MISMATCHED_TARGET_OS,
EMPTY_LINE_AFTER_OUTER_ATTR,
EMPTY_LINE_AFTER_DOC_COMMENTS,
NON_MINIMAL_CFG,
MAYBE_MISUSED_CFG,
DEPRECATED_CLIPPY_CFG_ATTR,
UNNECESSARY_CLIPPY_CFG,
]);
@ -629,9 +560,7 @@ impl EarlyLintPass for EarlyAttributes {
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
deprecated_cfg_attr::check(cx, attr, &self.msrv);
deprecated_cfg_attr::check_clippy(cx, attr);
mismatched_target_os::check(cx, attr);
non_minimal_cfg::check(cx, attr);
maybe_misused_cfg::check(cx, attr);
}
extract_msrv_attr!(EarlyContext);

View File

@ -1,15 +1,11 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_block_with_applicability;
use clippy_utils::ty::implements_trait;
use clippy_utils::visitors::{for_each_expr, Descend};
use clippy_utils::{get_parent_expr, higher, is_from_proc_macro};
use core::ops::ControlFlow;
use clippy_utils::{higher, is_from_proc_macro};
use rustc_errors::Applicability;
use rustc_hir::{BlockCheckMode, Expr, ExprKind, MatchSource};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::declare_lint_pass;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
@ -124,30 +120,6 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInConditions {
);
}
}
} else {
let _: Option<!> = for_each_expr(cond, |e| {
if let ExprKind::Closure(closure) = e.kind {
// do not lint if the closure is called using an iterator (see #1141)
if let Some(parent) = get_parent_expr(cx, e)
&& let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind
&& let caller = cx.typeck_results().expr_ty(self_arg)
&& let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator)
&& implements_trait(cx, caller, iter_id, &[])
{
return ControlFlow::Continue(Descend::No);
}
let body = cx.tcx.hir().body(closure.body);
let ex = &body.value;
if let ExprKind::Block(block, _) = ex.kind {
if !body.value.span.from_expansion() && !block.stmts.is_empty() {
span_lint(cx, BLOCKS_IN_CONDITIONS, ex.span, complex_block_message.clone());
return ControlFlow::Continue(Descend::No);
}
}
}
ControlFlow::Continue(Descend::Yes)
});
}
}
}

View File

@ -174,6 +174,25 @@ fn check_inverted_bool_in_condition(
);
}
fn check_simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind
&& !expr.span.from_expansion()
&& !inner.span.from_expansion()
&& let Some(suggestion) = simplify_not(cx, inner)
&& cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow
{
span_lint_and_sugg(
cx,
NONMINIMAL_BOOL,
expr.span,
"this boolean expression can be simplified",
"try",
suggestion,
Applicability::MachineApplicable,
);
}
}
struct NonminimalBoolVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
}
@ -232,6 +251,11 @@ impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
_ => (),
}
}
if self.cx.typeck_results().expr_ty(e).is_never() {
return Err("contains never type".to_owned());
}
for (n, expr) in self.terminals.iter().enumerate() {
if eq_expr_value(self.cx, e, expr) {
#[expect(clippy::cast_possible_truncation)]
@ -542,8 +566,7 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
}
};
if improvements.is_empty() {
let mut visitor = NotSimplificationVisitor { cx: self.cx };
visitor.visit_expr(e);
check_simplify_not(self.cx, e);
} else {
nonminimal_bool_lint(
improvements
@ -586,30 +609,3 @@ fn implements_ord(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
.get_diagnostic_item(sym::Ord)
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
}
struct NotSimplificationVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
}
impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind
&& !expr.span.from_expansion()
&& !inner.span.from_expansion()
&& let Some(suggestion) = simplify_not(self.cx, inner)
&& self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow
{
span_lint_and_sugg(
self.cx,
NONMINIMAL_BOOL,
expr.span,
"this boolean expression can be simplified",
"try",
suggestion,
Applicability::MachineApplicable,
);
}
walk_expr(self, expr);
}
}

View File

@ -71,12 +71,6 @@ struct CargoToml {
workspace: Workspace,
}
#[derive(Default, Debug)]
struct LintsAndGroups {
lints: Vec<Spanned<String>>,
groups: Vec<(Spanned<String>, Spanned<LintConfig>)>,
}
fn toml_span(range: Range<usize>, file: &SourceFile) -> Span {
Span::new(
file.start_pos + BytePos::from_usize(range.start),
@ -86,27 +80,28 @@ fn toml_span(range: Range<usize>, file: &SourceFile) -> Span {
)
}
fn check_table(cx: &LateContext<'_>, table: LintTable, groups: &FxHashSet<&str>, file: &SourceFile) {
let mut by_priority = BTreeMap::<_, LintsAndGroups>::new();
fn check_table(cx: &LateContext<'_>, table: LintTable, known_groups: &FxHashSet<&str>, file: &SourceFile) {
let mut lints = Vec::new();
let mut groups = Vec::new();
for (name, config) in table {
let lints_and_groups = by_priority.entry(config.as_ref().priority()).or_default();
if groups.contains(name.get_ref().as_str()) {
lints_and_groups.groups.push((name, config));
if name.get_ref() == "warnings" {
continue;
}
if known_groups.contains(name.get_ref().as_str()) {
groups.push((name, config));
} else {
lints_and_groups.lints.push(name);
lints.push((name, config.into_inner()));
}
}
let low_priority = by_priority
.iter()
.find(|(_, lints_and_groups)| !lints_and_groups.lints.is_empty())
.map_or(-1, |(&lowest_lint_priority, _)| lowest_lint_priority - 1);
for (priority, LintsAndGroups { lints, groups }) in by_priority {
let Some(last_lint_alphabetically) = lints.last() else {
continue;
};
for (group, config) in groups {
for (group, group_config) in groups {
let priority = group_config.get_ref().priority();
let level = group_config.get_ref().level();
if let Some((conflict, _)) = lints
.iter()
.rfind(|(_, lint_config)| lint_config.priority() == priority && lint_config.level() != level)
{
span_lint_and_then(
cx,
LINT_GROUPS_PRIORITY,
@ -116,22 +111,23 @@ fn check_table(cx: &LateContext<'_>, table: LintTable, groups: &FxHashSet<&str>,
group.as_ref()
),
|diag| {
let config_span = toml_span(config.span(), file);
if config.as_ref().is_implicit() {
let config_span = toml_span(group_config.span(), file);
if group_config.as_ref().is_implicit() {
diag.span_label(config_span, "has an implicit priority of 0");
}
// add the label to next lint after this group that has the same priority
let lint = lints
.iter()
.filter(|lint| lint.span().start > group.span().start)
.min_by_key(|lint| lint.span().start)
.unwrap_or(last_lint_alphabetically);
diag.span_label(toml_span(lint.span(), file), "has the same priority as this lint");
diag.span_label(toml_span(conflict.span(), file), "has the same priority as this lint");
diag.note("the order of the lints in the table is ignored by Cargo");
let mut suggestion = String::new();
let low_priority = lints
.iter()
.map(|(_, config)| config.priority().saturating_sub(1))
.min()
.unwrap_or(-1);
Serialize::serialize(
&LintConfigTable {
level: config.as_ref().level().into(),
level: level.into(),
priority: Some(low_priority),
},
toml::ser::ValueSerializer::new(&mut suggestion),

View File

@ -3,7 +3,7 @@ use std::ops::ControlFlow;
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::visitors::{for_each_expr, Descend};
use clippy_utils::visitors::{for_each_expr_without_closures, Descend};
use clippy_utils::{method_chain_args, sext};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
@ -266,7 +266,7 @@ fn expr_add_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign {
fn exprs_with_muldiv_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
let mut res = vec![];
for_each_expr(expr, |sub_expr| -> ControlFlow<Infallible, Descend> {
for_each_expr_without_closures(expr, |sub_expr| -> ControlFlow<Infallible, Descend> {
// We don't check for mul/div/rem methods here, but we could.
if let ExprKind::Binary(op, lhs, _rhs) = sub_expr.kind {
if matches!(op.node, BinOpKind::Mul | BinOpKind::Div) {
@ -315,7 +315,7 @@ fn exprs_with_muldiv_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
fn exprs_with_add_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
let mut res = vec![];
for_each_expr(expr, |sub_expr| -> ControlFlow<Infallible, Descend> {
for_each_expr_without_closures(expr, |sub_expr| -> ControlFlow<Infallible, Descend> {
// We don't check for add methods here, but we could.
if let ExprKind::Binary(op, _lhs, _rhs) = sub_expr.kind {
if matches!(op.node, BinOpKind::Add) {

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal::NumericLiteral;
use clippy_utils::source::snippet_opt;
use clippy_utils::visitors::{for_each_expr, Visitable};
use clippy_utils::visitors::{for_each_expr_without_closures, Visitable};
use clippy_utils::{get_parent_expr, is_hir_ty_cfg_dependant, is_ty_alias, path_to_local};
use rustc_ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
@ -245,7 +245,7 @@ fn fp_ty_mantissa_nbits(typ: Ty<'_>) -> u32 {
/// TODO: Maybe we should move this to `clippy_utils` so others won't need to go down this dark,
/// dark path reimplementing this (or something similar).
fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx>, cast_from: Ty<'tcx>) -> bool {
for_each_expr(expr, |expr| {
for_each_expr_without_closures(expr, |expr| {
// Calls are a `Path`, and usage of locals are a `Path`. So, this checks
// - call() as i32
// - local as i32

View File

@ -3,7 +3,7 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{get_async_fn_body, is_async_fn, LimitStack};
use core::ops::ControlFlow;
use rustc_ast::ast::Attribute;
@ -65,7 +65,7 @@ impl CognitiveComplexity {
let mut cc = 1u64;
let mut returns = 0u64;
let _: Option<!> = for_each_expr(expr, |e| {
let _: Option<!> = for_each_expr_without_closures(expr, |e| {
match e.kind {
ExprKind::If(_, _, _) => {
cc += 1;

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::visitors::{for_each_expr_with_closures, Visitable};
use clippy_utils::visitors::{for_each_expr, Visitable};
use clippy_utils::{get_enclosing_block, path_to_local_id};
use core::ops::ControlFlow;
use rustc_hir::{Body, ExprKind, HirId, LangItem, LetStmt, Node, PatKind};
@ -82,7 +82,7 @@ fn has_no_read_access<'tcx, T: Visitable<'tcx>>(cx: &LateContext<'tcx>, id: HirI
let mut has_read_access = false;
// Inspect all expressions and sub-expressions in the block.
for_each_expr_with_closures(cx, block, |expr| {
for_each_expr(cx, block, |expr| {
// Ignore expressions that are not simply `id`.
if !path_to_local_id(expr, id) {
return ControlFlow::Continue(());

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt};
use clippy_utils::ty::{needs_ordered_drop, InteriorMut};
use clippy_utils::visitors::for_each_expr;
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{
capture_local_usage, eq_expr_value, find_binding_init, get_enclosing_block, hash_expr, hash_stmt, if_sequence,
is_else_clause, is_lint_allowed, path_to_local, search_same, ContainsName, HirEqInterExpr, SpanlessEq,
@ -362,7 +362,7 @@ fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool {
/// Checks if the statement modifies or moves any of the given locals.
fn modifies_any_local<'tcx>(cx: &LateContext<'tcx>, s: &'tcx Stmt<'_>, locals: &HirIdSet) -> bool {
for_each_expr(s, |e| {
for_each_expr_without_closures(s, |e| {
if let Some(id) = path_to_local(e)
&& locals.contains(&id)
&& !capture_local_usage(cx, e).is_imm_ref()
@ -413,7 +413,7 @@ fn scan_block_for_eq<'tcx>(
let mut cond_locals = HirIdSet::default();
for &cond in conds {
let _: Option<!> = for_each_expr(cond, |e| {
let _: Option<!> = for_each_expr_without_closures(cond, |e| {
if let Some(id) = path_to_local(e) {
cond_locals.insert(id);
}

View File

@ -58,8 +58,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
crate::attrs::INLINE_ALWAYS_INFO,
crate::attrs::MAYBE_MISUSED_CFG_INFO,
crate::attrs::MISMATCHED_TARGET_OS_INFO,
crate::attrs::MIXED_ATTRIBUTES_STYLE_INFO,
crate::attrs::NON_MINIMAL_CFG_INFO,
crate::attrs::SHOULD_PANIC_WITHOUT_EXPECT_INFO,
@ -419,6 +417,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::MAP_UNWRAP_OR_INFO,
crate::methods::MUT_MUTEX_LOCK_INFO,
crate::methods::NAIVE_BYTECOUNT_INFO,
crate::methods::NEEDLESS_CHARACTER_ITERATION_INFO,
crate::methods::NEEDLESS_COLLECT_INFO,
crate::methods::NEEDLESS_OPTION_AS_DEREF_INFO,
crate::methods::NEEDLESS_OPTION_TAKE_INFO,
@ -449,7 +448,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::SEEK_TO_START_INSTEAD_OF_REWIND_INFO,
crate::methods::SHOULD_IMPLEMENT_TRAIT_INFO,
crate::methods::SINGLE_CHAR_ADD_STR_INFO,
crate::methods::SINGLE_CHAR_PATTERN_INFO,
crate::methods::SKIP_WHILE_NEXT_INFO,
crate::methods::STABLE_SORT_PRIMITIVE_INFO,
crate::methods::STRING_EXTEND_CHARS_INFO,
@ -531,6 +529,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::needless_for_each::NEEDLESS_FOR_EACH_INFO,
crate::needless_if::NEEDLESS_IF_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,
crate::needless_pass_by_ref_mut::NEEDLESS_PASS_BY_REF_MUT_INFO,
crate::needless_pass_by_value::NEEDLESS_PASS_BY_VALUE_INFO,
@ -656,6 +655,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::std_instead_of_core::ALLOC_INSTEAD_OF_CORE_INFO,
crate::std_instead_of_core::STD_INSTEAD_OF_ALLOC_INFO,
crate::std_instead_of_core::STD_INSTEAD_OF_CORE_INFO,
crate::string_patterns::MANUAL_PATTERN_CHAR_COMPARISON_INFO,
crate::string_patterns::SINGLE_CHAR_PATTERN_INFO,
crate::strings::STRING_ADD_INFO,
crate::strings::STRING_ADD_ASSIGN_INFO,
crate::strings::STRING_FROM_UTF8_AS_BYTES_INFO,

View File

@ -215,3 +215,29 @@ declare_deprecated_lint! {
pub WRONG_PUB_SELF_CONVENTION,
"set the `avoid-breaking-exported-api` config option to `false` to enable the `wrong_self_convention` lint for public items"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// This lint has been superseded by rustc's own [`unexpected_cfgs`] lint that is able to detect the `#[cfg(features)]` and `#[cfg(tests)]` typos.
///
/// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs
#[clippy::version = "1.80.0"]
pub MAYBE_MISUSED_CFG,
"this lint has been replaced by `unexpected_cfgs`"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// This lint has been superseded by rustc's own [`unexpected_cfgs`] lint that is able to detect invalid `#[cfg(linux)]` attributes.
///
/// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs
#[clippy::version = "1.80.0"]
pub MISMATCHED_TARGET_OS,
"this lint has been replaced by `unexpected_cfgs`"
}

View File

@ -37,7 +37,7 @@ pub fn check(
cx,
MISSING_SAFETY_DOC,
span,
"unsafe function's docs miss `# Safety` section",
"unsafe function's docs are missing a `# Safety` section",
),
(true, Safety::Safe) => span_lint(
cx,

View File

@ -9,8 +9,8 @@ use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Saf
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{
self, Binder, ClosureArgs, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, RegionKind, Ty,
TypeVisitableExt, TypeckResults, TyCtxt,
self, Binder, ClosureArgs, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, RegionKind, Ty, TyCtxt,
TypeVisitableExt, TypeckResults,
};
use rustc_session::declare_lint_pass;
use rustc_span::symbol::sym;
@ -123,7 +123,8 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
ExprKind::Path(QPath::Resolved(..) | QPath::TypeRelative(..))
) =>
{
let callee_ty = typeck.expr_ty(callee).peel_refs();
let callee_ty_raw = typeck.expr_ty(callee);
let callee_ty = callee_ty_raw.peel_refs();
if matches!(type_diagnostic_name(cx, callee_ty), Some(sym::Arc | sym::Rc))
|| !check_inputs(typeck, body.params, None, args)
{
@ -170,15 +171,25 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
{
span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| {
if let Some(mut snippet) = snippet_opt(cx, callee.span) {
if let Ok((ClosureKind::FnMut, _)) = cx.tcx.infer_ctxt().build().type_implements_fn_trait(
cx.param_env,
Binder::bind_with_vars(callee_ty_adjusted, List::empty()),
ty::PredicatePolarity::Positive,
) && path_to_local(callee).map_or(false, |l| {
if path_to_local(callee).map_or(false, |l| {
// FIXME: Do we really need this `local_used_in` check?
// Isn't it checking something like... `callee(callee)`?
// If somehow this check is needed, add some test for it,
// 'cuz currently nothing changes after deleting this check.
local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr)
}) {
// Mutable closure is used after current expr; we cannot consume it.
snippet = format!("&mut {snippet}");
match cx.tcx.infer_ctxt().build().type_implements_fn_trait(
cx.param_env,
Binder::bind_with_vars(callee_ty_adjusted, List::empty()),
ty::PredicatePolarity::Positive,
) {
// Mutable closure is used after current expr; we cannot consume it.
Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"),
Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => {
snippet = format!("&{snippet}");
},
_ => (),
}
}
diag.span_suggestion(
expr.span,

View File

@ -103,7 +103,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
return;
}
if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') {
if is_whole && !sym_str.contains(['e', 'E']) {
// Normalize the literal by stripping the fractional portion
if sym_str.split('.').next().unwrap() != float_str {
// If the type suffix is missing the suggestion would be

View File

@ -2,7 +2,8 @@ use clippy_utils::consts::Constant::{Int, F32, F64};
use clippy_utils::consts::{constant, constant_simple, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{
eq_expr_value, get_parent_expr, higher, in_constant, is_no_std_crate, numeric_literal, peel_blocks, sugg,
eq_expr_value, get_parent_expr, higher, in_constant, is_inherent_method_call, is_no_std_crate, numeric_literal,
peel_blocks, sugg,
};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
@ -759,7 +760,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
if let ExprKind::MethodCall(path, receiver, args, _) = &expr.kind {
let recv_ty = cx.typeck_results().expr_ty(receiver);
if recv_ty.is_floating_point() && !is_no_std_crate(cx) {
if recv_ty.is_floating_point() && !is_no_std_crate(cx) && is_inherent_method_call(cx, expr) {
match path.ident.name.as_str() {
"ln" => check_ln1p(cx, expr, receiver),
"log" => check_log_base(cx, expr, receiver, args),

View File

@ -424,14 +424,14 @@ impl<'a, 'tcx> FormatArgsExpr<'a, 'tcx> {
count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter())
&& implements_trait(cx, target, display_trait_id, &[])
&& let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait()
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span.source_callsite())
{
let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
if n_needed_derefs == 0 && !needs_ref {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
to_string_span.with_lo(receiver.span.hi()),
to_string_span.with_lo(receiver.span.source_callsite().hi()),
format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
"remove this",
String::new(),

View File

@ -14,7 +14,7 @@ use clippy_utils::attrs::is_proc_macro;
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_must_use_ty;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{return_ty, trait_ref_of_method};
use core::ops::ControlFlow;
@ -226,7 +226,7 @@ fn is_mutated_static(e: &hir::Expr<'_>) -> bool {
}
fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool {
for_each_expr(body.value, |e| {
for_each_expr_without_closures(body.value, |e| {
use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall};
match e.kind {

View File

@ -5,7 +5,7 @@ use rustc_span::def_id::LocalDefId;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::type_is_unsafe_function;
use clippy_utils::visitors::for_each_expr_with_closures;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{iter_input_pats, path_to_local};
use core::ops::ControlFlow;
@ -49,7 +49,7 @@ fn check_raw_ptr<'tcx>(
if !raw_ptrs.is_empty() {
let typeck = cx.tcx.typeck_body(body.id());
let _: Option<!> = for_each_expr_with_closures(cx, body.value, |e| {
let _: Option<!> = for_each_expr(cx, body.value, |e| {
match e.kind {
hir::ExprKind::Call(f, args) if type_is_unsafe_function(cx, typeck.expr_ty(f)) => {
for arg in args {

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context};
use clippy_utils::visitors::for_each_expr;
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{get_async_fn_body, is_async_fn};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
@ -153,7 +153,7 @@ fn lint_implicit_returns(
ExprKind::Loop(block, ..) => {
let mut add_return = false;
let _: Option<!> = for_each_expr(block, |e| {
let _: Option<!> = for_each_expr_without_closures(block, |e| {
if let ExprKind::Break(dest, sub_expr) = e.kind {
if dest.target_id.ok() == Some(expr.hir_id) {
if call_site_span.is_none() && e.span.ctxt() == ctxt {

View File

@ -3,8 +3,8 @@ use clippy_utils::source::snippet;
use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir::def_id::DefId;
use rustc_hir::{
GenericArg, GenericBound, GenericBounds, ItemKind, PredicateOrigin, TraitBoundModifier, TyKind, AssocItemConstraint,
WherePredicate,
AssocItemConstraint, GenericArg, GenericBound, GenericBounds, ItemKind, PredicateOrigin, TraitBoundModifier,
TyKind, WherePredicate,
};
use rustc_hir_analysis::lower_ty;
use rustc_lint::{LateContext, LateLintPass};
@ -83,8 +83,8 @@ fn emit_lint(
let mut sugg = vec![(implied_span_extended, String::new())];
// We also might need to include associated item constraints that were specified in the implied bound,
// but omitted in the implied-by bound:
// We also might need to include associated item constraints that were specified in the implied
// bound, but omitted in the implied-by bound:
// `fn f() -> impl Deref<Target = u8> + DerefMut`
// If we're going to suggest removing `Deref<..>`, we'll need to put `<Target = u8>` on `DerefMut`
let omitted_constraints: Vec<_> = implied_constraints

View File

@ -2,12 +2,14 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::higher;
use clippy_utils::ty::{deref_chain, get_adt_inherent_method};
use clippy_utils::{higher, is_from_proc_macro};
use rustc_ast::ast::RangeLimits;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
@ -100,11 +102,21 @@ impl IndexingSlicing {
impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if self.suppress_restriction_lint_in_const && cx.tcx.hir().is_inside_const_context(expr.hir_id) {
if (self.suppress_restriction_lint_in_const && cx.tcx.hir().is_inside_const_context(expr.hir_id))
|| is_from_proc_macro(cx, expr)
{
return;
}
if let ExprKind::Index(array, index, _) = &expr.kind {
if let ExprKind::Index(array, index, _) = &expr.kind
&& let expr_ty = cx.typeck_results().expr_ty(array)
&& let mut deref = deref_chain(cx, expr_ty)
&& deref.any(|l| {
l.peel_refs().is_slice()
|| l.peel_refs().is_array()
|| ty_has_applicable_get_function(cx, l.peel_refs(), expr_ty, expr)
})
{
let note = "the suggestion might not be applicable in constant blocks";
let ty = cx.typeck_results().expr_ty(array).peel_refs();
if let Some(range) = higher::Range::hir(index) {
@ -231,3 +243,33 @@ fn to_const_range(cx: &LateContext<'_>, range: higher::Range<'_>, array_size: u1
(start, end)
}
/// Checks if the output Ty of the `get` method on this Ty (if any) matches the Ty returned by the
/// indexing operation (if any).
fn ty_has_applicable_get_function<'tcx>(
cx: &LateContext<'tcx>,
ty: Ty<'tcx>,
array_ty: Ty<'tcx>,
index_expr: &Expr<'_>,
) -> bool {
if let ty::Adt(_, _) = array_ty.kind()
&& let Some(get_output_ty) = get_adt_inherent_method(cx, ty, sym!(get)).map(|m| {
cx.tcx
.fn_sig(m.def_id)
.skip_binder()
.output()
.skip_binder()
})
&& let ty::Adt(def, args) = get_output_ty.kind()
&& cx.tcx.is_diagnostic_item(sym::Option, def.0.did)
&& let Some(option_generic_param) = args.first()
&& let generic_ty = option_generic_param.expect_ty().peel_refs()
// FIXME: ideally this would handle type params and projections properly, for now just assume it's the same type
&& (cx.typeck_results().expr_ty(index_expr).peel_refs() == generic_ty.peel_refs()
|| matches!(generic_ty.peel_refs().kind(), ty::Param(_) | ty::Alias(_, _)))
{
true
} else {
false
}
}

View File

@ -22,7 +22,7 @@ declare_clippy_lint! {
/// ```no_run
/// let my_div = 10 >> 1;
/// ```
#[clippy::version = "1.78.0"]
#[clippy::version = "1.79.0"]
pub INTEGER_DIVISION_REMAINDER_USED,
restriction,
"use of disallowed default division and remainder operations"

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::get_parent_as_impl;
use clippy_utils::source::snippet;
use clippy_utils::ty::{implements_trait, make_normalized_projection};
use clippy_utils::ty::{deref_chain, get_adt_inherent_method, implements_trait, make_normalized_projection};
use rustc_ast::Mutability;
use rustc_errors::Applicability;
use rustc_hir::{FnRetTy, ImplItemKind, ImplicitSelfKind, ItemKind, TyKind};
@ -9,8 +9,7 @@ use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, Ty};
use rustc_session::declare_lint_pass;
use rustc_span::{sym, Symbol};
use std::iter;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
@ -124,33 +123,6 @@ fn is_ty_exported(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
.is_some_and(|did| cx.effective_visibilities.is_exported(did))
}
/// Returns the deref chain of a type, starting with the type itself.
fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl Iterator<Item = Ty<'tcx>> + 'cx {
iter::successors(Some(ty), |&ty| {
if let Some(deref_did) = cx.tcx.lang_items().deref_trait()
&& implements_trait(cx, ty, deref_did, &[])
{
make_normalized_projection(cx.tcx, cx.param_env, deref_did, sym::Target, [ty])
} else {
None
}
})
}
fn adt_has_inherent_method(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
if let Some(ty_did) = ty.ty_adt_def().map(ty::AdtDef::did) {
cx.tcx.inherent_impls(ty_did).into_iter().flatten().any(|&did| {
cx.tcx
.associated_items(did)
.filter_by_name_unhygienic(method_name)
.next()
.is_some_and(|item| item.kind == ty::AssocKind::Fn)
})
} else {
false
}
}
impl LateLintPass<'_> for IterWithoutIntoIter {
fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
if !in_external_macro(cx.sess(), item.span)
@ -167,7 +139,7 @@ impl LateLintPass<'_> for IterWithoutIntoIter {
}
&& !deref_chain(cx, ty).any(|ty| {
// We can't check inherent impls for slices, but we know that they have an `iter(_mut)` method
ty.peel_refs().is_slice() || adt_has_inherent_method(cx, ty, expected_method_name)
ty.peel_refs().is_slice() || get_adt_inherent_method(cx, ty, expected_method_name).is_some()
})
&& let Some(iter_assoc_span) = imp.items.iter().find_map(|item| {
if item.ident.name == sym!(IntoIter) {

View File

@ -28,7 +28,7 @@ declare_clippy_lint! {
/// ```rust
/// let eps = f32::EPSILON;
/// ```
#[clippy::version = "1.72.0"]
#[clippy::version = "1.79.0"]
pub LEGACY_NUMERIC_CONSTANTS,
style,
"checks for usage of legacy std numeric constants and methods"

View File

@ -67,4 +67,12 @@
"clippy::wrong_pub_self_convention",
"set the `avoid-breaking-exported-api` config option to `false` to enable the `wrong_self_convention` lint for public items",
);
store.register_removed(
"clippy::maybe_misused_cfg",
"this lint has been replaced by `unexpected_cfgs`",
);
store.register_removed(
"clippy::mismatched_target_os",
"this lint has been replaced by `unexpected_cfgs`",
);
}

View File

@ -251,6 +251,7 @@ mod needless_else;
mod needless_for_each;
mod needless_if;
mod needless_late_init;
mod needless_maybe_sized;
mod needless_parens_on_range_literals;
mod needless_pass_by_ref_mut;
mod needless_pass_by_value;
@ -325,6 +326,7 @@ mod size_of_in_element_count;
mod size_of_ref;
mod slow_vector_initialization;
mod std_instead_of_core;
mod string_patterns;
mod strings;
mod strlen_on_c_strings;
mod suspicious_operation_groupings;
@ -1058,6 +1060,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi));
store.register_late_pass(|_| Box::new(collection_is_never_read::CollectionIsNeverRead));
store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage));
store.register_late_pass(|_| Box::new(needless_maybe_sized::NeedlessMaybeSized));
store.register_late_pass(|_| Box::new(redundant_async_block::RedundantAsyncBlock));
store.register_late_pass(|_| Box::new(let_with_type_underscore::UnderscoreTyped));
store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute));
@ -1165,6 +1168,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
..Default::default()
})
});
store.register_late_pass(|_| Box::new(string_patterns::StringPatterns));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View File

@ -43,7 +43,7 @@ declare_clippy_lint! {
/// let x: Option<Vec<String>> = Some(Vec::new());
/// let y: Vec<String> = x.unwrap_or_default();
/// ```
#[clippy::version = "1.78.0"]
#[clippy::version = "1.79.0"]
pub MANUAL_UNWRAP_OR_DEFAULT,
suspicious,
"check if a `match` or `if let` can be simplified with `unwrap_or_default`"
@ -57,7 +57,8 @@ fn get_some<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<HirId> {
// Since it comes from a pattern binding, we need to get the parent to actually match
// against it.
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
&& cx.tcx.lang_items().get(LangItem::OptionSome) == Some(def_id)
&& (cx.tcx.lang_items().get(LangItem::OptionSome) == Some(def_id)
|| cx.tcx.lang_items().get(LangItem::ResultOk) == Some(def_id))
{
let mut bindings = Vec::new();
pat.each_binding(|_, id, _, _| bindings.push(id));
@ -80,6 +81,14 @@ fn get_none<'tcx>(cx: &LateContext<'tcx>, arm: &Arm<'tcx>) -> Option<&'tcx Expr<
&& cx.tcx.lang_items().get(LangItem::OptionNone) == Some(def_id)
{
Some(arm.body)
} else if let PatKind::TupleStruct(QPath::Resolved(_, path), _, _)= arm.pat.kind
&& let Some(def_id) = path.res.opt_def_id()
// Since it comes from a pattern binding, we need to get the parent to actually match
// against it.
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
&& cx.tcx.lang_items().get(LangItem::ResultErr) == Some(def_id)
{
Some(arm.body)
} else if let PatKind::Wild = arm.pat.kind {
// We consider that the `Some` check will filter it out if it's not right.
Some(arm.body)

View File

@ -2,7 +2,7 @@ use clippy_config::msrvs::Msrv;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::matching_root_macro_call;
use clippy_utils::source::snippet;
use clippy_utils::visitors::{for_each_expr, is_local_used};
use clippy_utils::visitors::{for_each_expr_without_closures, is_local_used};
use clippy_utils::{in_constant, path_to_local};
use rustc_ast::{BorrowKind, LitKind};
use rustc_errors::Applicability;
@ -249,7 +249,7 @@ fn emit_redundant_guards<'tcx>(
/// an error in the future, and rustc already actively warns against this (see rust#41620),
/// so we don't consider those as usable within patterns for linting purposes.
fn expr_can_be_pat(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
for_each_expr(expr, |expr| {
for_each_expr_without_closures(expr, |expr| {
if match expr.kind {
ExprKind::ConstBlock(..) => cx.tcx.features().inline_const_pat,
ExprKind::Call(c, ..) if let ExprKind::Path(qpath) = c.kind => {

View File

@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, walk_span_to_context};
use clippy_utils::sugg::{make_unop, Sugg};
use clippy_utils::ty::{is_type_diagnostic_item, needs_ordered_drop};
use clippy_utils::visitors::{any_temporaries_need_ordered_drop, for_each_expr};
use clippy_utils::visitors::{any_temporaries_need_ordered_drop, for_each_expr_without_closures};
use clippy_utils::{higher, is_expn_of, is_trait_method};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
@ -283,7 +283,7 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op
// to see that there aren't any let chains anywhere in the guard, as that would break
// if we suggest `t.is_none() && (let X = y && z)` for:
// `match t { None if let X = y && z => true, _ => false }`
let has_nested_let_chain = for_each_expr(guard, |expr| {
let has_nested_let_chain = for_each_expr_without_closures(guard, |expr| {
if matches!(expr.kind, ExprKind::Let(..)) {
ControlFlow::Break(())
} else {

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{eq_expr_value, get_parent_expr};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
@ -46,7 +46,7 @@ fn collect_replace_calls<'tcx>(
let mut methods = VecDeque::new();
let mut from_args = VecDeque::new();
let _: Option<()> = for_each_expr(expr, |e| {
let _: Option<()> = for_each_expr_without_closures(expr, |e| {
if let Some(("replace", _, [from, to], _, _)) = method_call(e) {
if eq_expr_value(cx, to_arg, to) && cx.typeck_results().expr_ty(from).peel_refs().is_char() {
methods.push_front(e);

View File

@ -66,6 +66,7 @@ mod map_flatten;
mod map_identity;
mod map_unwrap_or;
mod mut_mutex_lock;
mod needless_character_iteration;
mod needless_collect;
mod needless_option_as_deref;
mod needless_option_take;
@ -93,7 +94,6 @@ mod seek_from_current;
mod seek_to_start_instead_of_rewind;
mod single_char_add_str;
mod single_char_insert_string;
mod single_char_pattern;
mod single_char_push_string;
mod skip_while_next;
mod stable_sort_primitive;
@ -1140,38 +1140,6 @@ declare_clippy_lint! {
"not returning type containing `Self` in a `new` method"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for string methods that receive a single-character
/// `str` as an argument, e.g., `_.split("x")`.
///
/// ### Why is this bad?
/// While this can make a perf difference on some systems,
/// benchmarks have proven inconclusive. But at least using a
/// char literal makes it clear that we are looking at a single
/// character.
///
/// ### Known problems
/// Does not catch multi-byte unicode characters. This is by
/// design, on many machines, splitting by a non-ascii char is
/// actually slower. Please do your own measurements instead of
/// relying solely on the results of this lint.
///
/// ### Example
/// ```rust,ignore
/// _.split("x");
/// ```
///
/// Use instead:
/// ```rust,ignore
/// _.split('x');
/// ```
#[clippy::version = "pre 1.29.0"]
pub SINGLE_CHAR_PATTERN,
pedantic,
"using a single-character str where a char could be used, e.g., `_.split(\"x\")`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for calling `.step_by(0)` on iterators which panics.
@ -4084,12 +4052,33 @@ declare_clippy_lint! {
/// ```no_run
/// println!("the string is empty");
/// ```
#[clippy::version = "1.78.0"]
#[clippy::version = "1.79.0"]
pub CONST_IS_EMPTY,
suspicious,
"is_empty() called on strings known at compile time"
}
declare_clippy_lint! {
/// ### What it does
/// Checks if an iterator is used to check if a string is ascii.
///
/// ### Why is this bad?
/// The `str` type already implements the `is_ascii` method.
///
/// ### Example
/// ```no_run
/// "foo".chars().all(|c| c.is_ascii());
/// ```
/// Use instead:
/// ```no_run
/// "foo".is_ascii();
/// ```
#[clippy::version = "1.80.0"]
pub NEEDLESS_CHARACTER_ITERATION,
suspicious,
"is_ascii() called on a char iterator"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
@ -4147,7 +4136,6 @@ impl_lint_pass!(Methods => [
FLAT_MAP_OPTION,
INEFFICIENT_TO_STRING,
NEW_RET_NO_SELF,
SINGLE_CHAR_PATTERN,
SINGLE_CHAR_ADD_STR,
SEARCH_IS_SOME,
FILTER_NEXT,
@ -4255,6 +4243,7 @@ impl_lint_pass!(Methods => [
UNNECESSARY_RESULT_MAP_OR_ELSE,
MANUAL_C_STR_LITERALS,
UNNECESSARY_GET_THEN_CHECK,
NEEDLESS_CHARACTER_ITERATION,
]);
/// Extracts a method call name, args, and `Span` of the method name.
@ -4301,7 +4290,6 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args);
single_char_add_str::check(cx, expr, receiver, args);
into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, receiver);
single_char_pattern::check(cx, expr, method_call.ident.name, receiver, args);
unnecessary_to_owned::check(cx, expr, method_call.ident.name, receiver, args, &self.msrv);
},
ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
@ -4462,6 +4450,7 @@ impl Methods {
},
("all", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
needless_character_iteration::check(cx, expr, recv, arg, true);
if let Some(("cloned", recv2, [], _, _)) = method_call(recv) {
iter_overeager_cloned::check(
cx,
@ -4482,6 +4471,7 @@ impl Methods {
},
("any", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
needless_character_iteration::check(cx, expr, recv, arg, false);
match method_call(recv) {
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
cx,

View File

@ -0,0 +1,124 @@
use rustc_errors::Applicability;
use rustc_hir::{Closure, Expr, ExprKind, HirId, StmtKind, UnOp};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::Span;
use super::utils::get_last_chain_binding_hir_id;
use super::NEEDLESS_CHARACTER_ITERATION;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use clippy_utils::{match_def_path, path_to_local_id, peel_blocks};
fn peels_expr_ref<'a, 'tcx>(mut expr: &'a Expr<'tcx>) -> &'a Expr<'tcx> {
while let ExprKind::AddrOf(_, _, e) = expr.kind {
expr = e;
}
expr
}
fn handle_expr(
cx: &LateContext<'_>,
expr: &Expr<'_>,
first_param: HirId,
span: Span,
before_chars: Span,
revert: bool,
is_all: bool,
) {
match expr.kind {
ExprKind::MethodCall(method, receiver, [], _) => {
// If we have `!is_ascii`, then only `.any()` should warn. And if the condition is
// `is_ascii`, then only `.all()` should warn.
if revert != is_all
&& method.ident.name.as_str() == "is_ascii"
&& path_to_local_id(receiver, first_param)
&& let char_arg_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs()
&& *char_arg_ty.kind() == ty::Char
&& let Some(snippet) = snippet_opt(cx, before_chars)
{
span_lint_and_sugg(
cx,
NEEDLESS_CHARACTER_ITERATION,
span,
"checking if a string is ascii using iterators",
"try",
format!("{}{snippet}.is_ascii()", if revert { "!" } else { "" }),
Applicability::MachineApplicable,
);
}
},
ExprKind::Block(block, _) => {
if block.stmts.iter().any(|stmt| !matches!(stmt.kind, StmtKind::Let(_))) {
// If there is something else than let bindings, then better not emit the lint.
return;
}
if let Some(block_expr) = block.expr
// First we ensure that this is a "binding chain" (each statement is a binding
// of the previous one) and that it is a binding of the closure argument.
&& let Some(last_chain_binding_id) =
get_last_chain_binding_hir_id(first_param, block.stmts)
{
handle_expr(
cx,
block_expr,
last_chain_binding_id,
span,
before_chars,
revert,
is_all,
);
}
},
ExprKind::Unary(UnOp::Not, expr) => handle_expr(cx, expr, first_param, span, before_chars, !revert, is_all),
ExprKind::Call(fn_path, [arg]) => {
// If we have `!is_ascii`, then only `.any()` should warn. And if the condition is
// `is_ascii`, then only `.all()` should warn.
if revert != is_all
&& let ExprKind::Path(path) = fn_path.kind
&& let Some(fn_def_id) = cx.qpath_res(&path, fn_path.hir_id).opt_def_id()
&& match_def_path(cx, fn_def_id, &["core", "char", "methods", "<impl char>", "is_ascii"])
&& path_to_local_id(peels_expr_ref(arg), first_param)
&& let Some(snippet) = snippet_opt(cx, before_chars)
{
span_lint_and_sugg(
cx,
NEEDLESS_CHARACTER_ITERATION,
span,
"checking if a string is ascii using iterators",
"try",
format!("{}{snippet}.is_ascii()", if revert { "!" } else { "" }),
Applicability::MachineApplicable,
);
}
},
_ => {},
}
}
pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>, is_all: bool) {
if let ExprKind::Closure(&Closure { body, .. }) = closure_arg.kind
&& let body = cx.tcx.hir().body(body)
&& let Some(first_param) = body.params.first()
&& let ExprKind::MethodCall(method, mut recv, [], _) = recv.kind
&& method.ident.name.as_str() == "chars"
&& let str_ty = cx.typeck_results().expr_ty_adjusted(recv).peel_refs()
&& *str_ty.kind() == ty::Str
{
let expr_start = recv.span;
while let ExprKind::MethodCall(_, new_recv, _, _) = recv.kind {
recv = new_recv;
}
let body_expr = peel_blocks(body.value);
handle_expr(
cx,
body_expr,
first_param.pat.hir_id,
recv.span.with_hi(call_expr.span.hi()),
recv.span.with_hi(expr_start.hi()),
false,
is_all,
);
}
}

View File

@ -60,7 +60,7 @@ pub(super) fn check<'tcx>(
let map = cx.tcx.hir();
let body = map.body_owned_by(map.enclosing_body_owner(expr.hir_id));
reference_visitor.visit_body(&body);
reference_visitor.visit_body(body);
if reference_visitor.found_reference {
return;

View File

@ -27,7 +27,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
lit.span,
"calling `push` with '/' or '\\' (file system root) will overwrite the previous path definition",
"try",
format!("\"{}\"", pushed_path_lit.trim_start_matches(|c| c == '/' || c == '\\')),
format!("\"{}\"", pushed_path_lit.trim_start_matches(['/', '\\'])),
Applicability::MachineApplicable,
);
}

View File

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::sugg::deref_closure_args;
use clippy_utils::ty::is_type_lang_item;
use clippy_utils::{get_parent_expr, is_trait_method, strip_pat_refs};
use clippy_utils::{is_receiver_of_method_call, is_trait_method, strip_pat_refs};
use hir::ExprKind;
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -156,13 +156,3 @@ pub(super) fn check<'tcx>(
}
}
}
fn is_receiver_of_method_call(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
if let Some(parent_expr) = get_parent_expr(cx, expr)
&& let ExprKind::MethodCall(_, receiver, ..) = parent_expr.kind
&& receiver.hir_id == expr.hir_id
{
return true;
}
false
}

View File

@ -1,8 +1,8 @@
use super::utils::get_hint_if_single_char_arg;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal};
use rustc_ast::BorrowKind;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{self as hir, ExprKind};
use rustc_lint::LateContext;
use super::SINGLE_CHAR_ADD_STR;
@ -10,7 +10,7 @@ use super::SINGLE_CHAR_ADD_STR;
/// lint for length-1 `str`s as argument for `insert_str`
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
let mut applicability = Applicability::MachineApplicable;
if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[1], &mut applicability, false) {
if let Some(extension_string) = str_literal_to_char_literal(cx, &args[1], &mut applicability, false) {
let base_string_snippet =
snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability);
let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability);
@ -25,4 +25,43 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::
applicability,
);
}
if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[1].kind
&& let ExprKind::MethodCall(path_segment, method_arg, _, _) = &arg.kind
&& path_segment.ident.name == rustc_span::sym::to_string
&& (is_ref_char(cx, method_arg) || is_char(cx, method_arg))
{
let base_string_snippet =
snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability);
let extension_string =
snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability);
let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability);
let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" };
let sugg = format!("{base_string_snippet}.insert({pos_arg}, {deref_string}{extension_string})");
span_lint_and_sugg(
cx,
SINGLE_CHAR_ADD_STR,
expr.span,
"calling `insert_str()` using a single-character converted to string",
"consider using `insert` without `to_string()`",
sugg,
applicability,
);
}
}
fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
if cx.typeck_results().expr_ty(expr).is_ref()
&& let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind()
&& ty.is_char()
{
return true;
}
false
}
fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
cx.typeck_results().expr_ty(expr).is_char()
}

View File

@ -1,64 +0,0 @@
use super::utils::get_hint_if_single_char_arg;
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::symbol::Symbol;
use super::SINGLE_CHAR_PATTERN;
const PATTERN_METHODS: [(&str, usize); 22] = [
("contains", 0),
("starts_with", 0),
("ends_with", 0),
("find", 0),
("rfind", 0),
("split", 0),
("split_inclusive", 0),
("rsplit", 0),
("split_terminator", 0),
("rsplit_terminator", 0),
("splitn", 1),
("rsplitn", 1),
("split_once", 0),
("rsplit_once", 0),
("matches", 0),
("rmatches", 0),
("match_indices", 0),
("rmatch_indices", 0),
("trim_start_matches", 0),
("trim_end_matches", 0),
("replace", 0),
("replacen", 0),
];
/// lint for length-1 `str`s for methods in `PATTERN_METHODS`
pub(super) fn check(
cx: &LateContext<'_>,
_expr: &hir::Expr<'_>,
method_name: Symbol,
receiver: &hir::Expr<'_>,
args: &[hir::Expr<'_>],
) {
for &(method, pos) in &PATTERN_METHODS {
if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(receiver).kind()
&& ty.is_str()
&& method_name.as_str() == method
&& args.len() > pos
&& let arg = &args[pos]
&& let mut applicability = Applicability::MachineApplicable
&& let Some(hint) = get_hint_if_single_char_arg(cx, arg, &mut applicability, true)
{
span_lint_and_sugg(
cx,
SINGLE_CHAR_PATTERN,
arg.span,
"single-character string constant used as pattern",
"consider using a `char`",
hint,
applicability,
);
}
}
}

View File

@ -1,8 +1,8 @@
use super::utils::get_hint_if_single_char_arg;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal};
use rustc_ast::BorrowKind;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{self as hir, ExprKind};
use rustc_lint::LateContext;
use super::SINGLE_CHAR_ADD_STR;
@ -10,7 +10,7 @@ use super::SINGLE_CHAR_ADD_STR;
/// lint for length-1 `str`s as argument for `push_str`
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
let mut applicability = Applicability::MachineApplicable;
if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[0], &mut applicability, false) {
if let Some(extension_string) = str_literal_to_char_literal(cx, &args[0], &mut applicability, false) {
let base_string_snippet =
snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability);
let sugg = format!("{base_string_snippet}.push({extension_string})");
@ -24,4 +24,42 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::
applicability,
);
}
if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[0].kind
&& let ExprKind::MethodCall(path_segment, method_arg, _, _) = &arg.kind
&& path_segment.ident.name == rustc_span::sym::to_string
&& (is_ref_char(cx, method_arg) || is_char(cx, method_arg))
{
let base_string_snippet =
snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability);
let extension_string =
snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability);
let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" };
let sugg = format!("{base_string_snippet}.push({deref_string}{extension_string})");
span_lint_and_sugg(
cx,
SINGLE_CHAR_ADD_STR,
expr.span,
"calling `push_str()` using a single-character converted to string",
"consider using `push` without `to_string()`",
sugg,
applicability,
);
}
}
fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
if cx.typeck_results().expr_ty(expr).is_ref()
&& let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind()
&& ty.is_char()
{
return true;
}
false
}
fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
cx.typeck_results().expr_ty(expr).is_char()
}

View File

@ -3,7 +3,7 @@ use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_with_context;
use clippy_utils::usage::local_used_after_expr;
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
use clippy_utils::visitors::{for_each_expr, Descend};
use clippy_utils::{is_diag_item_method, match_def_path, path_to_local_id, paths};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
@ -209,7 +209,7 @@ fn indirect_usage<'tcx>(
}) = stmt.kind
{
let mut path_to_binding = None;
let _: Option<!> = for_each_expr_with_closures(cx, init_expr, |e| {
let _: Option<!> = for_each_expr(cx, init_expr, |e| {
if path_to_local_id(e, binding) {
path_to_binding = Some(e);
}

View File

@ -2,7 +2,7 @@ use super::utils::clone_or_copy_needed;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::is_copy;
use clippy_utils::usage::mutated_variables;
use clippy_utils::visitors::{for_each_expr, Descend};
use clippy_utils::visitors::{for_each_expr_without_closures, Descend};
use clippy_utils::{is_res_lang_ctor, is_trait_method, path_res, path_to_local_id};
use core::ops::ControlFlow;
use rustc_hir as hir;
@ -26,7 +26,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a
let (mut found_mapping, mut found_filtering) = check_expression(cx, arg_id, body.value);
let _: Option<!> = for_each_expr(body.value, |e| {
let _: Option<!> = for_each_expr_without_closures(body.value, |e| {
if let hir::ExprKind::Ret(Some(e)) = &e.kind {
let (found_mapping_res, found_filtering_res) = check_expression(cx, arg_id, e);
found_mapping |= found_mapping_res;

View File

@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::ForLoop;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{get_iterator_item_ty, implements_trait};
use clippy_utils::visitors::for_each_expr;
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{can_mut_borrow_both, fn_def_id, get_parent_expr, path_to_local};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
@ -61,7 +61,7 @@ pub fn check_for_loop_iter(
fn is_caller_or_fields_change(cx: &LateContext<'_>, body: &Expr<'_>, caller: &Expr<'_>) -> bool {
let mut change = false;
if let ExprKind::Block(block, ..) = body.kind {
for_each_expr(block, |e| {
for_each_expr_without_closures(block, |e| {
match e.kind {
ExprKind::Assign(assignee, _, _) | ExprKind::AssignOp(_, assignee, _) => {
change |= !can_mut_borrow_both(cx, caller, assignee);

View File

@ -4,10 +4,11 @@ use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{Closure, Expr, ExprKind, HirId, QPath, Stmt, StmtKind};
use rustc_hir::{Closure, Expr, ExprKind, HirId, QPath};
use rustc_lint::LateContext;
use rustc_span::symbol::sym;
use super::utils::get_last_chain_binding_hir_id;
use super::UNNECESSARY_RESULT_MAP_OR_ELSE;
fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>) {
@ -25,22 +26,6 @@ fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &E
);
}
fn get_last_chain_binding_hir_id(mut hir_id: HirId, statements: &[Stmt<'_>]) -> Option<HirId> {
for stmt in statements {
if let StmtKind::Let(local) = stmt.kind
&& let Some(init) = local.init
&& let ExprKind::Path(QPath::Resolved(_, path)) = init.kind
&& let hir::def::Res::Local(local_hir_id) = path.res
&& local_hir_id == hir_id
{
hir_id = local.pat.hir_id;
} else {
return None;
}
}
Some(hir_id)
}
fn handle_qpath(
cx: &LateContext<'_>,
expr: &Expr<'_>,

View File

@ -1,10 +1,7 @@
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{get_parent_expr, path_to_local_id, usage};
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Pat};
use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, QPath, Stmt, StmtKind};
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{self, Ty};
@ -49,48 +46,6 @@ pub(super) fn derefs_to_slice<'tcx>(
}
}
pub(super) fn get_hint_if_single_char_arg(
cx: &LateContext<'_>,
arg: &Expr<'_>,
applicability: &mut Applicability,
ascii_only: bool,
) -> Option<String> {
if let ExprKind::Lit(lit) = &arg.kind
&& let ast::LitKind::Str(r, style) = lit.node
&& let string = r.as_str()
&& let len = if ascii_only {
string.len()
} else {
string.chars().count()
}
&& len == 1
{
let snip = snippet_with_applicability(cx, arg.span, string, applicability);
let ch = if let ast::StrStyle::Raw(nhash) = style {
let nhash = nhash as usize;
// for raw string: r##"a"##
&snip[(nhash + 2)..(snip.len() - 1 - nhash)]
} else {
// for regular string: "a"
&snip[1..(snip.len() - 1)]
};
let hint = format!(
"'{}'",
match ch {
"'" => "\\'",
r"\" => "\\\\",
"\\\"" => "\"", // no need to escape `"` in `'"'`
_ => ch,
}
);
Some(hint)
} else {
None
}
}
/// The core logic of `check_for_loop_iter` in `unnecessary_iter_cloned.rs`, this function wraps a
/// use of `CloneOrCopyVisitor`.
pub(super) fn clone_or_copy_needed<'tcx>(
@ -175,3 +130,19 @@ impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> {
.any(|hir_id| path_to_local_id(expr, *hir_id))
}
}
pub(super) fn get_last_chain_binding_hir_id(mut hir_id: HirId, statements: &[Stmt<'_>]) -> Option<HirId> {
for stmt in statements {
if let StmtKind::Let(local) = stmt.kind
&& let Some(init) = local.init
&& let ExprKind::Path(QPath::Resolved(_, path)) = init.kind
&& let rustc_hir::def::Res::Local(local_hir_id) = path.res
&& local_hir_id == hir_id
{
hir_id = local.pat.hir_id;
} else {
return None;
}
}
Some(hir_id)
}

View File

@ -6,7 +6,7 @@ use rustc_span::Span;
use super::ZERO_PREFIXED_LITERAL;
pub(super) fn check(cx: &EarlyContext<'_>, lit_span: Span, lit_snip: &str) {
let trimmed_lit_snip = lit_snip.trim_start_matches(|c| c == '_' || c == '0');
let trimmed_lit_snip = lit_snip.trim_start_matches(['_', '0']);
span_lint_and_then(
cx,
ZERO_PREFIXED_LITERAL,
@ -20,7 +20,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, lit_span: Span, lit_snip: &str) {
Applicability::MaybeIncorrect,
);
// do not advise to use octal form if the literal cannot be expressed in base 8.
if !lit_snip.contains(|c| c == '8' || c == '9') {
if !lit_snip.contains(['8', '9']) {
diag.span_suggestion(
lit_span,
"if you mean to use an octal constant, use `0o`",

View File

@ -4,7 +4,7 @@ use std::ops::ControlFlow;
use clippy_utils::comparisons::{normalize_comparison, Rel};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{eq_expr_value, hash_expr, higher};
use rustc_ast::{LitKind, RangeLimits};
use rustc_data_structures::packed::Pu128;
@ -405,7 +405,7 @@ impl LateLintPass<'_> for MissingAssertsForIndexing {
fn check_body(&mut self, cx: &LateContext<'_>, body: &Body<'_>) {
let mut map = UnhashMap::default();
for_each_expr(body.value, |expr| {
for_each_expr_without_closures(body.value, |expr| {
check_index(cx, expr, &mut map);
check_assert(cx, expr, &mut map);
ControlFlow::<!, ()>::Continue(())

View File

@ -1,7 +1,6 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::qualify_min_const_fn::is_min_const_fn;
use clippy_utils::ty::has_drop;
use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_macro, trait_ref_of_method};
use rustc_hir as hir;
use rustc_hir::def_id::CRATE_DEF_ID;
@ -121,10 +120,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
}
},
FnKind::Method(_, sig, ..) => {
if trait_ref_of_method(cx, def_id).is_some()
|| already_const(sig.header)
|| method_accepts_droppable(cx, def_id)
{
if trait_ref_of_method(cx, def_id).is_some() || already_const(sig.header) {
return;
}
},
@ -151,26 +147,13 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
let mir = cx.tcx.optimized_mir(def_id);
if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, &self.msrv) {
if cx.tcx.is_const_fn_raw(def_id.to_def_id()) {
cx.tcx.dcx().span_err(span, err);
}
} else {
if let Ok(()) = is_min_const_fn(cx.tcx, mir, &self.msrv) {
span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`");
}
}
extract_msrv_attr!(LateContext);
}
/// Returns true if any of the method parameters is a type that implements `Drop`. The method
/// can't be made const then, because `drop` can't be const-evaluated.
fn method_accepts_droppable(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
// If any of the params are droppable, return true
sig.inputs().iter().any(|&ty| has_drop(cx, ty))
}
// We don't have to lint on something that's already `const`
#[must_use]
fn already_const(header: hir::FnHeader) -> bool {

View File

@ -110,7 +110,7 @@ fn should_lint<'tcx>(
// Is there a call to `DebugStruct::debug_struct`? Do lint if there is.
let mut has_debug_struct = false;
for_each_expr(block, |expr| {
for_each_expr(cx, block, |expr| {
if let ExprKind::MethodCall(path, recv, ..) = &expr.kind {
let recv_ty = typeck_results.expr_ty(recv).peel_refs();
@ -165,7 +165,7 @@ fn check_struct<'tcx>(
let mut has_direct_field_access = false;
let mut field_accesses = FxHashSet::default();
for_each_expr(block, |expr| {
for_each_expr(cx, block, |expr| {
if let ExprKind::Field(target, ident) = expr.kind
&& let target_ty = typeck_results.expr_ty_adjusted(target).peel_refs()
&& target_ty == self_ty

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::visitors::{for_each_expr_with_closures, Descend, Visitable};
use clippy_utils::visitors::{for_each_expr, Descend, Visitable};
use core::ops::ControlFlow::Continue;
use hir::def::{DefKind, Res};
use hir::{BlockCheckMode, ExprKind, QPath, Safety, UnOp};
@ -96,7 +96,7 @@ fn collect_unsafe_exprs<'tcx>(
node: impl Visitable<'tcx>,
unsafe_ops: &mut Vec<(&'static str, Span)>,
) {
for_each_expr_with_closures(cx, node, |expr| {
for_each_expr(cx, node, |expr| {
match expr.kind {
ExprKind::InlineAsm(_) => unsafe_ops.push(("inline assembly used here", expr.span)),

View File

@ -6,8 +6,8 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::{
higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt,
span_extract_comment, SpanlessEq,
get_parent_expr, higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt, is_receiver_of_method_call,
peel_blocks, peel_blocks_with_stmt, span_extract_comment, SpanlessEq,
};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
@ -154,7 +154,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
snip = snip.blockify();
}
if condition_needs_parentheses(cond) && is_parent_stmt(cx, e.hir_id) {
if (condition_needs_parentheses(cond) && is_parent_stmt(cx, e.hir_id))
|| is_receiver_of_method_call(cx, e)
|| is_as_argument(cx, e)
{
snip = snip.maybe_par();
}
@ -442,3 +445,7 @@ fn fetch_assign<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, bool)
None
}
}
fn is_as_argument(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
matches!(get_parent_expr(cx, e).map(|e| e.kind), Some(ExprKind::Cast(_, _)))
}

View File

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::path_to_local;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::needs_ordered_drop;
use clippy_utils::visitors::{for_each_expr, for_each_expr_with_closures, is_local_used};
use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures, is_local_used};
use core::ops::ControlFlow;
use rustc_errors::{Applicability, MultiSpan};
use rustc_hir::{
@ -63,7 +63,7 @@ declare_clippy_lint! {
declare_lint_pass!(NeedlessLateInit => [NEEDLESS_LATE_INIT]);
fn contains_assign_expr<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> bool {
for_each_expr_with_closures(cx, stmt, |e| {
for_each_expr(cx, stmt, |e| {
if matches!(e.kind, ExprKind::Assign(..)) {
ControlFlow::Break(())
} else {
@ -74,7 +74,7 @@ fn contains_assign_expr<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) ->
}
fn contains_let(cond: &Expr<'_>) -> bool {
for_each_expr(cond, |e| {
for_each_expr_without_closures(cond, |e| {
if matches!(e.kind, ExprKind::Let(_)) {
ControlFlow::Break(())
} else {

View File

@ -0,0 +1,164 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_errors::Applicability;
use rustc_hir::def_id::{DefId, DefIdMap};
use rustc_hir::{GenericBound, Generics, PolyTraitRef, TraitBoundModifier, WherePredicate};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{ClauseKind, PredicatePolarity};
use rustc_session::declare_lint_pass;
use rustc_span::symbol::Ident;
declare_clippy_lint! {
/// ### What it does
/// Lints `?Sized` bounds applied to type parameters that cannot be unsized
///
/// ### Why is this bad?
/// The `?Sized` bound is misleading because it cannot be satisfied by an
/// unsized type
///
/// ### Example
/// ```rust
/// // `T` cannot be unsized because `Clone` requires it to be `Sized`
/// fn f<T: Clone + ?Sized>(t: &T) {}
/// ```
/// Use instead:
/// ```rust
/// fn f<T: Clone>(t: &T) {}
///
/// // or choose alternative bounds for `T` so that it can be unsized
/// ```
#[clippy::version = "1.79.0"]
pub NEEDLESS_MAYBE_SIZED,
suspicious,
"a `?Sized` bound that is unusable due to a `Sized` requirement"
}
declare_lint_pass!(NeedlessMaybeSized => [NEEDLESS_MAYBE_SIZED]);
#[allow(clippy::struct_field_names)]
struct Bound<'tcx> {
/// The [`DefId`] of the type parameter the bound refers to
param: DefId,
ident: Ident,
trait_bound: &'tcx PolyTraitRef<'tcx>,
modifier: TraitBoundModifier,
predicate_pos: usize,
bound_pos: usize,
}
/// Finds all of the [`Bound`]s that refer to a type parameter and are not from a macro expansion
fn type_param_bounds<'tcx>(generics: &'tcx Generics<'tcx>) -> impl Iterator<Item = Bound<'tcx>> {
generics
.predicates
.iter()
.enumerate()
.filter_map(|(predicate_pos, predicate)| {
let WherePredicate::BoundPredicate(bound_predicate) = predicate else {
return None;
};
let (param, ident) = bound_predicate.bounded_ty.as_generic_param()?;
Some(
bound_predicate
.bounds
.iter()
.enumerate()
.filter_map(move |(bound_pos, bound)| match bound {
&GenericBound::Trait(ref trait_bound, modifier) => Some(Bound {
param,
ident,
trait_bound,
modifier,
predicate_pos,
bound_pos,
}),
GenericBound::Outlives(_) => None,
})
.filter(|bound| !bound.trait_bound.span.from_expansion()),
)
})
.flatten()
}
/// Searches the supertraits of the trait referred to by `trait_bound` recursively, returning the
/// path taken to find a `Sized` bound if one is found
fn path_to_sized_bound(cx: &LateContext<'_>, trait_bound: &PolyTraitRef<'_>) -> Option<Vec<DefId>> {
fn search(cx: &LateContext<'_>, path: &mut Vec<DefId>) -> bool {
let trait_def_id = *path.last().unwrap();
if Some(trait_def_id) == cx.tcx.lang_items().sized_trait() {
return true;
}
for &(predicate, _) in cx.tcx.super_predicates_of(trait_def_id).predicates {
if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()
&& trait_predicate.polarity == PredicatePolarity::Positive
&& !path.contains(&trait_predicate.def_id())
{
path.push(trait_predicate.def_id());
if search(cx, path) {
return true;
}
path.pop();
}
}
false
}
let mut path = vec![trait_bound.trait_ref.trait_def_id()?];
search(cx, &mut path).then_some(path)
}
impl LateLintPass<'_> for NeedlessMaybeSized {
fn check_generics(&mut self, cx: &LateContext<'_>, generics: &Generics<'_>) {
let Some(sized_trait) = cx.tcx.lang_items().sized_trait() else {
return;
};
let maybe_sized_params: DefIdMap<_> = type_param_bounds(generics)
.filter(|bound| {
bound.trait_bound.trait_ref.trait_def_id() == Some(sized_trait)
&& bound.modifier == TraitBoundModifier::Maybe
})
.map(|bound| (bound.param, bound))
.collect();
for bound in type_param_bounds(generics) {
if bound.modifier == TraitBoundModifier::None
&& let Some(sized_bound) = maybe_sized_params.get(&bound.param)
&& let Some(path) = path_to_sized_bound(cx, bound.trait_bound)
{
span_lint_and_then(
cx,
NEEDLESS_MAYBE_SIZED,
sized_bound.trait_bound.span,
"`?Sized` bound is ignored because of a `Sized` requirement",
|diag| {
let ty_param = sized_bound.ident;
diag.span_note(
bound.trait_bound.span,
format!("`{ty_param}` cannot be unsized because of the bound"),
);
for &[current_id, next_id] in path.array_windows() {
let current = cx.tcx.item_name(current_id);
let next = cx.tcx.item_name(next_id);
diag.note(format!("...because `{current}` has the bound `{next}`"));
}
diag.span_suggestion_verbose(
generics.span_for_bound_removal(sized_bound.predicate_pos, sized_bound.bound_pos),
"change the bounds that require `Sized`, or remove the `?Sized` bound",
"",
Applicability::MaybeIncorrect,
);
},
);
return;
}
}
}
}

View File

@ -1,7 +1,7 @@
use super::needless_pass_by_value::requires_exact_signature;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet;
use clippy_utils::visitors::for_each_expr_with_closures;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{inherits_cfg, is_from_proc_macro, is_self};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_errors::Applicability;
@ -205,7 +205,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
// We retrieve all the closures declared in the function because they will not be found
// by `euv::Delegate`.
let mut closures: FxHashSet<LocalDefId> = FxHashSet::default();
for_each_expr_with_closures(cx, body, |expr| {
for_each_expr(cx, body, |expr| {
if let ExprKind::Closure(closure) = expr.kind {
closures.insert(closure.def_id);
}

View File

@ -1,10 +1,11 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::ty::implements_trait;
use clippy_utils::{is_res_lang_ctor, last_path_segment, path_res, std_or_core};
use clippy_utils::{is_from_proc_macro, is_res_lang_ctor, last_path_segment, path_res, std_or_core};
use rustc_errors::Applicability;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, LangItem, Node, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::EarlyBinder;
use rustc_session::declare_lint_pass;
use rustc_span::sym;
@ -111,7 +112,7 @@ declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL
impl LateLintPass<'_> for NonCanonicalImpls {
#[expect(clippy::too_many_lines)]
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
fn check_impl_item<'tcx>(&mut self, cx: &LateContext<'tcx>, impl_item: &ImplItem<'tcx>) {
let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id()) else {
return;
};
@ -128,6 +129,9 @@ impl LateLintPass<'_> for NonCanonicalImpls {
let ExprKind::Block(block, ..) = body.value.kind else {
return;
};
if in_external_macro(cx.sess(), block.span) || is_from_proc_macro(cx, impl_item) {
return;
}
if cx.tcx.is_diagnostic_item(sym::Clone, trait_impl.def_id)
&& let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy)

View File

@ -7,7 +7,7 @@ use std::ptr;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::in_constant;
use clippy_utils::macros::macro_backtrace;
use clippy_utils::ty::InteriorMut;
use clippy_utils::ty::{implements_trait, InteriorMut};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{
@ -18,7 +18,7 @@ use rustc_middle::mir::interpret::{ErrorHandled, EvalToValTreeResult, GlobalId};
use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_session::impl_lint_pass;
use rustc_span::{sym, InnerSpan, Span, DUMMY_SP};
use rustc_span::{sym, Span, DUMMY_SP};
use rustc_target::abi::VariantIdx;
// FIXME: this is a correctness problem but there's no suitable
@ -127,19 +127,19 @@ declare_clippy_lint! {
}
#[derive(Copy, Clone)]
enum Source {
Item { item: Span },
enum Source<'tcx> {
Item { item: Span, ty: Ty<'tcx> },
Assoc { item: Span },
Expr { expr: Span },
}
impl Source {
impl Source<'_> {
#[must_use]
fn lint(&self) -> (&'static Lint, &'static str, Span) {
match self {
Self::Item { item } | Self::Assoc { item, .. } => (
Self::Item { item, .. } | Self::Assoc { item, .. } => (
DECLARE_INTERIOR_MUTABLE_CONST,
"a `const` item should never be interior mutable",
"a `const` item should not be interior mutable",
*item,
),
Self::Expr { expr } => (
@ -151,16 +151,24 @@ impl Source {
}
}
fn lint(cx: &LateContext<'_>, source: Source) {
fn lint<'tcx>(cx: &LateContext<'tcx>, source: Source<'tcx>) {
let (lint, msg, span) = source.lint();
span_lint_and_then(cx, lint, span, msg, |diag| {
if span.from_expansion() {
return; // Don't give suggestions into macros.
}
match source {
Source::Item { .. } => {
let const_kw_span = span.from_inner(InnerSpan::new(0, 5));
diag.span_label(const_kw_span, "make this a static item (maybe with lazy_static)");
Source::Item { ty, .. } => {
let Some(sync_trait) = cx.tcx.lang_items().sync_trait() else {
return;
};
if implements_trait(cx, ty, sync_trait, &[]) {
diag.help("consider making this a static item");
} else {
diag.help(
"consider making this `Sync` so that it can go in a static item or using a `thread_local`",
);
}
},
Source::Assoc { .. } => (),
Source::Expr { .. } => {
@ -311,7 +319,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> {
&& self.interior_mut.is_interior_mut_ty(cx, ty)
&& Self::is_value_unfrozen_poly(cx, body_id, ty)
{
lint(cx, Source::Item { item: it.span });
lint(cx, Source::Item { item: it.span, ty });
}
}
}

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::implements_trait;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{binop_traits, eq_expr_value, trait_ref_of_method};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
@ -62,7 +62,7 @@ pub(super) fn check<'tcx>(
};
let mut found = false;
let found_multiple = for_each_expr(e, |e| {
let found_multiple = for_each_expr_without_closures(e, |e| {
if eq_expr_value(cx, assignee, e) {
if found {
return ControlFlow::Break(());

View File

@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicInResultFn {
fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir::Body<'tcx>) {
let mut panics = Vec::new();
let _: Option<!> = for_each_expr(body.value, |e| {
let _: Option<!> = for_each_expr(cx, body.value, |e| {
let Some(macro_call) = root_macro_call_first_node(cx, e) else {
return ControlFlow::Continue(Descend::Yes);
};

View File

@ -4,7 +4,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::peel_blocks;
use clippy_utils::source::{snippet, walk_span_to_context};
use clippy_utils::ty::implements_trait;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::visitors::for_each_expr_without_closures;
use rustc_errors::Applicability;
use rustc_hir::{
Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, MatchSource,
@ -107,7 +107,7 @@ fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if let ExprKind::Match(match_value, _, MatchSource::AwaitDesugar) = expr.kind
&& let ExprKind::Call(_, [into_future_arg]) = match_value.kind
&& let ctxt = expr.span.ctxt()
&& for_each_expr(into_future_arg, |e| {
&& for_each_expr_without_closures(into_future_arg, |e| {
walk_span_to_context(e.span, ctxt).map_or(ControlFlow::Break(()), |_| ControlFlow::Continue(()))
})
.is_none()

View File

@ -48,6 +48,9 @@ impl EarlyLintPass for DerefAddrOf {
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind
&& let ExprKind::AddrOf(_, ref mutability, ref addrof_target) = without_parens(deref_target).kind
// NOTE(tesuji): `*&` forces rustc to const-promote the array to `.rodata` section.
// See #12854 for details.
&& !matches!(addrof_target.kind, ExprKind::Array(_))
&& deref_target.span.eq_ctxt(e.span)
&& !addrof_target.span.from_expansion()
{

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet_opt, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
use clippy_utils::visitors::{for_each_expr, Descend};
use clippy_utils::{
binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res,
path_to_local_id, span_contains_cfg, span_find_starting_semi,
@ -436,7 +436,7 @@ fn emit_return_lint(
}
fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
for_each_expr_with_closures(cx, expr, |e| {
for_each_expr(cx, expr, |e| {
if let Some(def_id) = fn_def_id(cx, e)
&& cx
.tcx

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{indent_of, snippet};
use clippy_utils::{expr_or_init, get_attr, path_to_local, peel_hir_expr_unary};
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{walk_expr, Visitor};
@ -12,6 +12,7 @@ use rustc_session::impl_lint_pass;
use rustc_span::symbol::Ident;
use rustc_span::{sym, Span, DUMMY_SP};
use std::borrow::Cow;
use std::collections::hash_map::Entry;
declare_clippy_lint! {
/// ### What it does
@ -57,7 +58,6 @@ impl_lint_pass!(SignificantDropTightening<'_> => [SIGNIFICANT_DROP_TIGHTENING]);
pub struct SignificantDropTightening<'tcx> {
apas: FxIndexMap<HirId, AuxParamsAttr>,
/// Auxiliary structure used to avoid having to verify the same type multiple times.
seen_types: FxHashSet<Ty<'tcx>>,
type_cache: FxHashMap<Ty<'tcx>, bool>,
}
@ -74,7 +74,7 @@ impl<'tcx> LateLintPass<'tcx> for SignificantDropTightening<'tcx> {
self.apas.clear();
let initial_dummy_stmt = dummy_stmt_expr(body.value);
let mut ap = AuxParams::new(&mut self.apas, &initial_dummy_stmt);
StmtsChecker::new(&mut ap, cx, &mut self.seen_types, &mut self.type_cache).visit_body(body);
StmtsChecker::new(&mut ap, cx, &mut self.type_cache).visit_body(body);
for apa in ap.apas.values() {
if apa.counter <= 1 || !apa.has_expensive_expr_after_last_attr {
continue;
@ -142,28 +142,25 @@ impl<'tcx> LateLintPass<'tcx> for SignificantDropTightening<'tcx> {
/// Checks the existence of the `#[has_significant_drop]` attribute.
struct AttrChecker<'cx, 'others, 'tcx> {
cx: &'cx LateContext<'tcx>,
seen_types: &'others mut FxHashSet<Ty<'tcx>>,
type_cache: &'others mut FxHashMap<Ty<'tcx>, bool>,
}
impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> {
pub(crate) fn new(
cx: &'cx LateContext<'tcx>,
seen_types: &'others mut FxHashSet<Ty<'tcx>>,
type_cache: &'others mut FxHashMap<Ty<'tcx>, bool>,
) -> Self {
seen_types.clear();
Self {
cx,
seen_types,
type_cache,
}
pub(crate) fn new(cx: &'cx LateContext<'tcx>, type_cache: &'others mut FxHashMap<Ty<'tcx>, bool>) -> Self {
Self { cx, type_cache }
}
fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>) -> bool {
// The borrow checker prevents us from using something fancier like or_insert_with.
if let Some(ty) = self.type_cache.get(&ty) {
return *ty;
let ty = self
.cx
.tcx
.try_normalize_erasing_regions(self.cx.param_env, ty)
.unwrap_or(ty);
match self.type_cache.entry(ty) {
Entry::Occupied(e) => return *e.get(),
Entry::Vacant(e) => {
e.insert(false);
},
}
let value = self.has_sig_drop_attr_uncached(ty);
self.type_cache.insert(ty, value);
@ -185,7 +182,7 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> {
rustc_middle::ty::Adt(a, b) => {
for f in a.all_fields() {
let ty = f.ty(self.cx.tcx, b);
if !self.has_seen_ty(ty) && self.has_sig_drop_attr(ty) {
if self.has_sig_drop_attr(ty) {
return true;
}
}
@ -205,16 +202,11 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> {
_ => false,
}
}
fn has_seen_ty(&mut self, ty: Ty<'tcx>) -> bool {
!self.seen_types.insert(ty)
}
}
struct StmtsChecker<'ap, 'lc, 'others, 'stmt, 'tcx> {
ap: &'ap mut AuxParams<'others, 'stmt, 'tcx>,
cx: &'lc LateContext<'tcx>,
seen_types: &'others mut FxHashSet<Ty<'tcx>>,
type_cache: &'others mut FxHashMap<Ty<'tcx>, bool>,
}
@ -222,15 +214,9 @@ impl<'ap, 'lc, 'others, 'stmt, 'tcx> StmtsChecker<'ap, 'lc, 'others, 'stmt, 'tcx
fn new(
ap: &'ap mut AuxParams<'others, 'stmt, 'tcx>,
cx: &'lc LateContext<'tcx>,
seen_types: &'others mut FxHashSet<Ty<'tcx>>,
type_cache: &'others mut FxHashMap<Ty<'tcx>, bool>,
) -> Self {
Self {
ap,
cx,
seen_types,
type_cache,
}
Self { ap, cx, type_cache }
}
fn manage_has_expensive_expr_after_last_attr(&mut self) {
@ -288,7 +274,7 @@ impl<'ap, 'lc, 'others, 'stmt, 'tcx> Visitor<'tcx> for StmtsChecker<'ap, 'lc, 'o
apa.counter = apa.counter.wrapping_add(1);
apa.has_expensive_expr_after_last_attr = false;
};
let mut ac = AttrChecker::new(self.cx, self.seen_types, self.type_cache);
let mut ac = AttrChecker::new(self.cx, self.type_cache);
if ac.has_sig_drop_attr(self.cx.typeck_results().expr_ty(expr)) {
if let hir::StmtKind::Let(local) = self.ap.curr_stmt.kind
&& let hir::PatKind::Binding(_, hir_id, ident, _) = local.pat.kind

View File

@ -0,0 +1,227 @@
use std::ops::ControlFlow;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::macros::matching_root_macro_call;
use clippy_utils::path_to_local_id;
use clippy_utils::source::{snippet, str_literal_to_char_literal};
use clippy_utils::visitors::{for_each_expr, Descend};
use itertools::Itertools;
use rustc_ast::{BinOpKind, LitKind};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, PatKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use rustc_span::{sym, Span};
declare_clippy_lint! {
/// ### What it does
/// Checks for manual `char` comparison in string patterns
///
/// ### Why is this bad?
/// This can be written more concisely using a `char` or an array of `char`.
/// This is more readable and more optimized when comparing to only one `char`.
///
/// ### Example
/// ```no_run
/// "Hello World!".trim_end_matches(|c| c == '.' || c == ',' || c == '!' || c == '?');
/// ```
/// Use instead:
/// ```no_run
/// "Hello World!".trim_end_matches(['.', ',', '!', '?']);
/// ```
#[clippy::version = "1.80.0"]
pub MANUAL_PATTERN_CHAR_COMPARISON,
style,
"manual char comparison in string patterns"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for string methods that receive a single-character
/// `str` as an argument, e.g., `_.split("x")`.
///
/// ### Why is this bad?
/// While this can make a perf difference on some systems,
/// benchmarks have proven inconclusive. But at least using a
/// char literal makes it clear that we are looking at a single
/// character.
///
/// ### Known problems
/// Does not catch multi-byte unicode characters. This is by
/// design, on many machines, splitting by a non-ascii char is
/// actually slower. Please do your own measurements instead of
/// relying solely on the results of this lint.
///
/// ### Example
/// ```rust,ignore
/// _.split("x");
/// ```
///
/// Use instead:
/// ```rust,ignore
/// _.split('x');
/// ```
#[clippy::version = "pre 1.29.0"]
pub SINGLE_CHAR_PATTERN,
pedantic,
"using a single-character str where a char could be used, e.g., `_.split(\"x\")`"
}
declare_lint_pass!(StringPatterns => [MANUAL_PATTERN_CHAR_COMPARISON, SINGLE_CHAR_PATTERN]);
const PATTERN_METHODS: [(&str, usize); 22] = [
("contains", 0),
("starts_with", 0),
("ends_with", 0),
("find", 0),
("rfind", 0),
("split", 0),
("split_inclusive", 0),
("rsplit", 0),
("split_terminator", 0),
("rsplit_terminator", 0),
("splitn", 1),
("rsplitn", 1),
("split_once", 0),
("rsplit_once", 0),
("matches", 0),
("rmatches", 0),
("match_indices", 0),
("rmatch_indices", 0),
("trim_start_matches", 0),
("trim_end_matches", 0),
("replace", 0),
("replacen", 0),
];
fn check_single_char_pattern_lint(cx: &LateContext<'_>, arg: &Expr<'_>) {
let mut applicability = Applicability::MachineApplicable;
if let Some(hint) = str_literal_to_char_literal(cx, arg, &mut applicability, true) {
span_lint_and_sugg(
cx,
SINGLE_CHAR_PATTERN,
arg.span,
"single-character string constant used as pattern",
"consider using a `char`",
hint,
applicability,
);
}
}
fn get_char_span<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Span> {
if cx.typeck_results().expr_ty_adjusted(expr).is_char()
&& !expr.span.from_expansion()
&& switch_to_eager_eval(cx, expr)
{
Some(expr.span)
} else {
None
}
}
fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<'_>) {
if let ExprKind::Closure(closure) = method_arg.kind
&& let body = cx.tcx.hir().body(closure.body)
&& let Some(PatKind::Binding(_, binding, ..)) = body.params.first().map(|p| p.pat.kind)
{
let mut set_char_spans: Vec<Span> = Vec::new();
// We want to retrieve all the comparisons done.
// They are ordered in a nested way and so we need to traverse the AST to collect them all.
if for_each_expr(cx, body.value, |sub_expr| -> ControlFlow<(), Descend> {
match sub_expr.kind {
ExprKind::Binary(op, left, right) if op.node == BinOpKind::Eq => {
if path_to_local_id(left, binding)
&& let Some(span) = get_char_span(cx, right)
{
set_char_spans.push(span);
ControlFlow::Continue(Descend::No)
} else if path_to_local_id(right, binding)
&& let Some(span) = get_char_span(cx, left)
{
set_char_spans.push(span);
ControlFlow::Continue(Descend::No)
} else {
ControlFlow::Break(())
}
},
ExprKind::Binary(op, _, _) if op.node == BinOpKind::Or => ControlFlow::Continue(Descend::Yes),
ExprKind::Match(match_value, [arm, _], _) => {
if matching_root_macro_call(cx, sub_expr.span, sym::matches_macro).is_none()
|| arm.guard.is_some()
|| !path_to_local_id(match_value, binding)
{
return ControlFlow::Break(());
}
if arm.pat.walk_short(|pat| match pat.kind {
PatKind::Lit(expr) if let ExprKind::Lit(lit) = expr.kind => {
if let LitKind::Char(_) = lit.node {
set_char_spans.push(lit.span);
}
true
},
PatKind::Or(_) => true,
_ => false,
}) {
ControlFlow::Continue(Descend::No)
} else {
ControlFlow::Break(())
}
},
_ => ControlFlow::Break(()),
}
})
.is_some()
{
return;
}
span_lint_and_then(
cx,
MANUAL_PATTERN_CHAR_COMPARISON,
method_arg.span,
"this manual char comparison can be written more succinctly",
|diag| {
if let [set_char_span] = set_char_spans[..] {
diag.span_suggestion(
method_arg.span,
"consider using a `char`",
snippet(cx, set_char_span, "c"),
Applicability::MachineApplicable,
);
} else {
diag.span_suggestion(
method_arg.span,
"consider using an array of `char`",
format!(
"[{}]",
set_char_spans.into_iter().map(|span| snippet(cx, span, "c")).join(", ")
),
Applicability::MachineApplicable,
);
}
},
);
}
}
impl<'tcx> LateLintPass<'tcx> for StringPatterns {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !expr.span.from_expansion()
&& let ExprKind::MethodCall(method, receiver, args, _) = expr.kind
&& let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(receiver).kind()
&& ty.is_str()
&& let method_name = method.ident.name.as_str()
&& let Some(&(_, pos)) = PATTERN_METHODS
.iter()
.find(|(array_method_name, _)| *array_method_name == method_name)
&& let Some(arg) = args.get(pos)
{
check_single_char_pattern_lint(cx, arg);
check_manual_pattern_char_comparison(cx, arg);
}
}
}

View File

@ -399,13 +399,17 @@ impl<'tcx> LateLintPass<'tcx> for StrToString {
&& let ty::Ref(_, ty, ..) = ty.kind()
&& ty.is_str()
{
span_lint_and_help(
let mut applicability = Applicability::MachineApplicable;
let snippet = snippet_with_applicability(cx, self_arg.span, "..", &mut applicability);
span_lint_and_sugg(
cx,
STR_TO_STRING,
expr.span,
"`to_string()` called on a `&str`",
None,
"consider using `.to_owned()`",
"try",
format!("{snippet}.to_owned()"),
applicability,
);
}
}

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{binop_traits, trait_ref_of_method, BINOP_TRAITS, OP_ASSIGN_TRAITS};
use core::ops::ControlFlow;
use rustc_hir as hir;
@ -95,7 +95,7 @@ impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl {
fn count_binops(expr: &hir::Expr<'_>) -> u32 {
let mut count = 0u32;
let _: Option<!> = for_each_expr(expr, |e| {
let _: Option<!> = for_each_expr_without_closures(expr, |e| {
if matches!(
e.kind,
hir::ExprKind::Binary(..)

View File

@ -31,7 +31,6 @@ fn get_parent_local_binding_ty<'tcx>(cx: &LateContext<'tcx>, expr_hir_id: HirId)
fn is_function_block(cx: &LateContext<'_>, expr_hir_id: HirId) -> bool {
let def_id = cx.tcx.hir().enclosing_body_owner(expr_hir_id);
if let Some(body) = cx.tcx.hir().maybe_body_owned_by(def_id) {
let body = cx.tcx.hir().body(body.id());
return body.value.peel_blocks().hir_id == expr_hir_id;
}
false

View File

@ -546,7 +546,7 @@ declare_clippy_lint! {
/// let x = std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]);
/// # }
/// ```
#[clippy::version = "1.77.0"]
#[clippy::version = "1.79.0"]
pub MISSING_TRANSMUTE_ANNOTATIONS,
suspicious,
"warns if a transmute call doesn't have all generics specified"

View File

@ -3,7 +3,7 @@ use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_lint_allowed;
use clippy_utils::source::walk_span_to_context;
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
use clippy_utils::visitors::{for_each_expr, Descend};
use hir::HirId;
use rustc_data_structures::sync::Lrc;
use rustc_hir as hir;
@ -296,7 +296,7 @@ fn expr_has_unnecessary_safety_comment<'tcx>(
}
// this should roughly be the reverse of `block_parents_have_safety_comment`
if for_each_expr_with_closures(cx, expr, |expr| match expr.kind {
if for_each_expr(cx, expr, |expr| match expr.kind {
hir::ExprKind::Block(
Block {
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),

View File

@ -59,7 +59,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf {
let parent_item = cx.tcx.hir().expect_item(parent);
let assoc_item = cx.tcx.associated_item(impl_item.owner_id);
let contains_todo = |cx, body: &'_ Body<'_>| -> bool {
clippy_utils::visitors::for_each_expr(body.value, |e| {
clippy_utils::visitors::for_each_expr_without_closures(body.value, |e| {
if let Some(macro_call) = root_macro_call_first_node(cx, e) {
if cx.tcx.item_name(macro_call.def_id).as_str() == "todo" {
ControlFlow::Break(())

View File

@ -77,7 +77,7 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tc
let body = cx.tcx.hir().body(body_id);
let typeck = cx.tcx.typeck(impl_item.owner_id.def_id);
let mut result = Vec::new();
let _: Option<!> = for_each_expr(body.value, |e| {
let _: Option<!> = for_each_expr(cx, body.value, |e| {
// check for `expect`
if let Some(arglists) = method_chain_args(e, &["expect"]) {
let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs();

View File

@ -733,7 +733,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
match stmt.value.kind {
StmtKind::Let(local) => {
bind!(self, local);
kind!("Local({local})");
kind!("Let({local})");
self.option(field!(local.init), "init", |init| {
self.expr(init);
});

View File

@ -213,7 +213,7 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
output: &mut self.registered_lints,
cx,
};
let body_id = cx.tcx.hir().body_owned_by(
let body = cx.tcx.hir().body_owned_by(
impl_item_refs
.iter()
.find(|iiref| iiref.ident.as_str() == "get_lints")
@ -222,7 +222,7 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
.owner_id
.def_id,
);
collector.visit_expr(cx.tcx.hir().body(body_id).value);
collector.visit_expr(body.value);
}
}
}

View File

@ -719,7 +719,7 @@ fn get_lint_group_and_level_or_lint(
Some(sym::clippy),
&std::iter::once(Ident::with_dummy_span(sym::clippy)).collect(),
);
if let CheckLintNameResult::Tool(Ok(lint_lst)) = result {
if let CheckLintNameResult::Tool(lint_lst, None) = result {
if let Some(group) = get_lint_group(cx, lint_lst[0]) {
if EXCLUDED_LINT_GROUPS.contains(&group.as_str()) {
return None;

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::VecArgs;
use clippy_utils::source::snippet;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::visitors::for_each_expr_without_closures;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{ExprKind, Node};
@ -36,7 +36,7 @@ declare_clippy_lint! {
/// side_effect();
/// let a: [i32; 0] = [];
/// ```
#[clippy::version = "1.75.0"]
#[clippy::version = "1.79.0"]
pub ZERO_REPEAT_SIDE_EFFECTS,
suspicious,
"usage of zero-sized initializations of arrays or vecs causing side effects"
@ -65,7 +65,7 @@ impl LateLintPass<'_> for ZeroRepeatSideEffects {
fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr: &'_ rustc_hir::Expr<'_>, is_vec: bool) {
// check if expr is a call or has a call inside it
if for_each_expr(inner_expr, |x| {
if for_each_expr_without_closures(inner_expr, |x| {
if let ExprKind::Call(_, _) | ExprKind::MethodCall(_, _, _, _) = x.kind {
std::ops::ControlFlow::Break(())
} else {

View File

@ -1,6 +1,6 @@
[package]
name = "clippy_utils"
version = "0.1.80"
version = "0.1.81"
edition = "2021"
publish = false

View File

@ -412,7 +412,8 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
/// Simple constant folding: Insert an expression, get a constant or none.
pub fn expr(&mut self, e: &Expr<'_>) -> Option<Constant<'tcx>> {
match e.kind {
ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.lcx.tcx.hir().body(body).value), ExprKind::DropTemps(e) => self.expr(e),
ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.lcx.tcx.hir().body(body).value),
ExprKind::DropTemps(e) => self.expr(e),
ExprKind::Path(ref qpath) => {
self.fetch_path_and_apply(qpath, e.hir_id, self.typeck_results.expr_ty(e), |this, result| {
let result = mir_to_const(this.lcx, result)?;
@ -490,7 +491,8 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
/// leaves the local crate.
pub fn expr_is_empty(&mut self, e: &Expr<'_>) -> Option<bool> {
match e.kind {
ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr_is_empty(self.lcx.tcx.hir().body(body).value), ExprKind::DropTemps(e) => self.expr_is_empty(e),
ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr_is_empty(self.lcx.tcx.hir().body(body).value),
ExprKind::DropTemps(e) => self.expr_is_empty(e),
ExprKind::Path(ref qpath) => {
if !self
.typeck_results
@ -811,12 +813,8 @@ pub fn mir_to_const<'tcx>(lcx: &LateContext<'tcx>, result: mir::Const<'tcx>) ->
ty::Adt(adt_def, _) if adt_def.is_struct() => Some(Constant::Adt(result)),
ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)),
ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.to_bits(int.size()))),
ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits(
int.try_into().expect("invalid f32 bit representation"),
))),
ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits(
int.try_into().expect("invalid f64 bit representation"),
))),
ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits(int.into()))),
ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits(int.into()))),
ty::RawPtr(_, _) => Some(Constant::RawPtr(int.to_bits(int.size()))),
_ => None,
},

View File

@ -7,9 +7,9 @@ use rustc_data_structures::fx::FxHasher;
use rustc_hir::def::Res;
use rustc_hir::MatchSource::TryDesugar;
use rustc_hir::{
ArrayLen, BinOpKind, BindingMode, Block, BodyId, Closure, Expr, ExprField, ExprKind, FnRetTy, GenericArg,
GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime, LifetimeName, Pat, PatField, PatKind, Path,
PathSegment, PrimTy, QPath, Stmt, StmtKind, Ty, TyKind, AssocItemConstraint,
ArrayLen, AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, Closure, Expr, ExprField, ExprKind, FnRetTy,
GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime, LifetimeName, Pat, PatField,
PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, Ty, TyKind,
};
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::LateContext;
@ -519,7 +519,11 @@ impl HirEqInterExpr<'_, '_, '_> {
}
fn eq_assoc_type_binding(&mut self, left: &AssocItemConstraint<'_>, right: &AssocItemConstraint<'_>) -> bool {
left.ident.name == right.ident.name && self.eq_ty(left.ty().expect("expected assoc type binding"), right.ty().expect("expected assoc type binding"))
left.ident.name == right.ident.name
&& self.eq_ty(
left.ty().expect("expected assoc type binding"),
right.ty().expect("expected assoc type binding"),
)
}
fn check_ctxt(&mut self, left: SyntaxContext, right: SyntaxContext) -> bool {

View File

@ -126,7 +126,7 @@ use visitors::Visitable;
use crate::consts::{constant, mir_to_const, Constant};
use crate::higher::Range;
use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type};
use crate::visitors::for_each_expr;
use crate::visitors::for_each_expr_without_closures;
use rustc_middle::hir::nested_filter;
@ -321,6 +321,15 @@ pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str])
.map_or(false, |trt_id| match_def_path(cx, trt_id, path))
}
/// Checks if the given method call expression calls an inherent method.
pub fn is_inherent_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
cx.tcx.trait_of_item(method_id).is_none()
} else {
false
}
}
/// Checks if a method is defined in an impl of a diagnostic item
pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
@ -1313,7 +1322,7 @@ pub fn contains_name<'tcx>(name: Symbol, expr: &'tcx Expr<'_>, cx: &LateContext<
/// Returns `true` if `expr` contains a return expression
pub fn contains_return<'tcx>(expr: impl Visitable<'tcx>) -> bool {
for_each_expr(expr, |e| {
for_each_expr_without_closures(expr, |e| {
if matches!(e.kind, ExprKind::Ret(..)) {
ControlFlow::Break(())
} else {
@ -3392,3 +3401,14 @@ pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool {
contains_block(expr, false)
}
/// Returns true if the specified expression is in a receiver position.
pub fn is_receiver_of_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let Some(parent_expr) = get_parent_expr(cx, expr)
&& let ExprKind::MethodCall(_, receiver, ..) = parent_expr.kind
&& receiver.hir_id == expr.hir_id
{
return true;
}
false
}

View File

@ -1,6 +1,6 @@
#![allow(clippy::similar_names)] // `expr` and `expn`
use crate::visitors::{for_each_expr, Descend};
use crate::visitors::{for_each_expr_without_closures, Descend};
use arrayvec::ArrayVec;
use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
@ -323,7 +323,7 @@ fn find_assert_args_inner<'a, const N: usize>(
Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?,
};
let mut args = ArrayVec::new();
let panic_expn = for_each_expr(expr, |e| {
let panic_expn = for_each_expr_without_closures(expr, |e| {
if args.is_full() {
match PanicExpn::parse(e) {
Some(expn) => ControlFlow::Break(expn),
@ -349,7 +349,7 @@ fn find_assert_within_debug_assert<'a>(
expn: ExpnId,
assert_name: Symbol,
) -> Option<(&'a Expr<'a>, ExpnId)> {
for_each_expr(expr, |e| {
for_each_expr_without_closures(expr, |e| {
if !e.span.from_expansion() {
return ControlFlow::Continue(Descend::No);
}
@ -397,7 +397,7 @@ impl FormatArgsStorage {
///
/// See also [`find_format_arg_expr`]
pub fn get(&self, cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId) -> Option<&FormatArgs> {
let format_args_expr = for_each_expr(start, |expr| {
let format_args_expr = for_each_expr_without_closures(start, |expr| {
let ctxt = expr.span.ctxt();
if ctxt.outer_expn().is_descendant_of(expn_id) {
if macro_backtrace(expr.span)
@ -439,7 +439,7 @@ pub fn find_format_arg_expr<'hir, 'ast>(
parent: _,
} = target.expr.span.data();
for_each_expr(start, |expr| {
for_each_expr_without_closures(start, |expr| {
// When incremental compilation is enabled spans gain a parent during AST to HIR lowering,
// since we're comparing an AST span to a HIR one we need to ignore the parent field
let data = expr.span.data();

View File

@ -1,5 +1,5 @@
use crate::source::snippet;
use crate::visitors::{for_each_expr, Descend};
use crate::visitors::{for_each_expr_without_closures, Descend};
use crate::{path_to_local_id, strip_pat_refs};
use core::ops::ControlFlow;
use rustc_hir::{Body, BodyId, ExprKind, HirId, PatKind};
@ -31,7 +31,7 @@ fn extract_clone_suggestions<'tcx>(
body: &'tcx Body<'_>,
) -> Option<Vec<(Span, Cow<'static, str>)>> {
let mut spans = Vec::new();
for_each_expr(body, |e| {
for_each_expr_without_closures(body, |e| {
if let ExprKind::MethodCall(seg, recv, [], _) = e.kind
&& path_to_local_id(recv, id)
{

View File

@ -40,9 +40,13 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv)
)?;
for bb in &*body.basic_blocks {
check_terminator(tcx, body, bb.terminator(), msrv)?;
for stmt in &bb.statements {
check_statement(tcx, body, def_id, stmt, msrv)?;
// Cleanup blocks are ignored entirely by const eval, so we can too:
// https://github.com/rust-lang/rust/blob/1dea922ea6e74f99a0e97de5cdb8174e4dea0444/compiler/rustc_const_eval/src/transform/check_consts/check.rs#L382
if !bb.is_cleanup {
check_terminator(tcx, body, bb.terminator(), msrv)?;
for stmt in &bb.statements {
check_statement(tcx, body, def_id, stmt, msrv)?;
}
}
}
Ok(())

View File

@ -2,6 +2,7 @@
#![allow(clippy::module_name_repetitions)]
use rustc_ast::{LitKind, StrStyle};
use rustc_data_structures::sync::Lrc;
use rustc_errors::Applicability;
use rustc_hir::{BlockCheckMode, Expr, ExprKind, UnsafeSource};
@ -500,6 +501,50 @@ pub fn expand_past_previous_comma(cx: &LateContext<'_>, span: Span) -> Span {
extended.with_lo(extended.lo() - BytePos(1))
}
/// Converts `expr` to a `char` literal if it's a `str` literal containing a single
/// character (or a single byte with `ascii_only`)
pub fn str_literal_to_char_literal(
cx: &LateContext<'_>,
expr: &Expr<'_>,
applicability: &mut Applicability,
ascii_only: bool,
) -> Option<String> {
if let ExprKind::Lit(lit) = &expr.kind
&& let LitKind::Str(r, style) = lit.node
&& let string = r.as_str()
&& let len = if ascii_only {
string.len()
} else {
string.chars().count()
}
&& len == 1
{
let snip = snippet_with_applicability(cx, expr.span, string, applicability);
let ch = if let StrStyle::Raw(nhash) = style {
let nhash = nhash as usize;
// for raw string: r##"a"##
&snip[(nhash + 2)..(snip.len() - 1 - nhash)]
} else {
// for regular string: "a"
&snip[1..(snip.len() - 1)]
};
let hint = format!(
"'{}'",
match ch {
"'" => "\\'",
r"\" => "\\\\",
"\\\"" => "\"", // no need to escape `"` in `'"'`
_ => ch,
}
);
Some(hint)
} else {
None
}
}
#[cfg(test)]
mod test {
use super::{reindent_multiline, without_block_comments};

View File

@ -17,9 +17,9 @@ use rustc_middle::mir::ConstValue;
use rustc_middle::traits::EvaluationResult;
use rustc_middle::ty::layout::ValidityRequirement;
use rustc_middle::ty::{
self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable,
TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr,
self, AdtDef, AliasTy, AssocItem, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind,
GenericArgsRef, GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable,
TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr,
};
use rustc_span::symbol::Ident;
use rustc_span::{sym, Span, Symbol, DUMMY_SP};
@ -861,7 +861,6 @@ impl core::ops::Add<u32> for EnumValue {
}
/// Attempts to read the given constant as though it were an enum value.
#[expect(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
pub fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option<EnumValue> {
if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) {
match tcx.type_of(id).instantiate_identity().kind() {
@ -1314,3 +1313,39 @@ pub fn normalize_with_regions<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>
pub fn is_manually_drop(ty: Ty<'_>) -> bool {
ty.ty_adt_def().map_or(false, AdtDef::is_manually_drop)
}
/// Returns the deref chain of a type, starting with the type itself.
pub fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl Iterator<Item = Ty<'tcx>> + 'cx {
iter::successors(Some(ty), |&ty| {
if let Some(deref_did) = cx.tcx.lang_items().deref_trait()
&& implements_trait(cx, ty, deref_did, &[])
{
make_normalized_projection(cx.tcx, cx.param_env, deref_did, sym::Target, [ty])
} else {
None
}
})
}
/// Checks if a Ty<'_> has some inherent method Symbol.
/// This does not look for impls in the type's `Deref::Target` type.
/// If you need this, you should wrap this call in `clippy_utils::ty::deref_chain().any(...)`.
pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<&'a AssocItem> {
if let Some(ty_did) = ty.ty_adt_def().map(AdtDef::did) {
cx.tcx
.inherent_impls(ty_did)
.into_iter()
.flatten()
.map(|&did| {
cx.tcx
.associated_items(did)
.filter_by_name_unhygienic(method_name)
.next()
.filter(|item| item.kind == AssocKind::Fn)
})
.next()
.flatten()
} else {
None
}
}

View File

@ -206,8 +206,18 @@ fn path_segment_certainty(
// Checking `res_generics_def_id(..)` before calling `generics_of` avoids an ICE.
if cx.tcx.res_generics_def_id(path_segment.res).is_some() {
let generics = cx.tcx.generics_of(def_id);
let count = generics.own_params.len() - usize::from(generics.host_effect_index.is_some());
let lhs = if (parent_certainty.is_certain() || generics.parent_count == 0) && count == 0 {
let own_count = generics.own_params.len()
- usize::from(generics.host_effect_index.is_some_and(|index| {
// Check that the host index actually belongs to this resolution.
// E.g. for `Add::add`, host_effect_index is `Some(2)`, but it's part of the parent `Add`
// trait's generics.
// Add params: [Self#0, Rhs#1, host#2] parent_count=0, count=3
// Add::add params: [] parent_count=3, count=3
// (3..3).contains(&host_effect_index) => false
(generics.parent_count..generics.count()).contains(&index)
}));
let lhs = if (parent_certainty.is_certain() || generics.parent_count == 0) && own_count == 0 {
Certainty::Certain(None)
} else {
Certainty::Uncertain

View File

@ -1,4 +1,4 @@
use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend, Visitable};
use crate::visitors::{for_each_expr, for_each_expr_without_closures, Descend, Visitable};
use crate::{self as utils, get_enclosing_loop_or_multi_call_closure};
use core::ops::ControlFlow;
use hir::def::Res;
@ -145,7 +145,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
}
pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
for_each_expr(expression, |e| {
for_each_expr_without_closures(expression, |e| {
match e.kind {
ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),
// Something special could be done here to handle while or for loop
@ -159,7 +159,7 @@ pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
}
pub fn local_used_in<'tcx>(cx: &LateContext<'tcx>, local_id: HirId, v: impl Visitable<'tcx>) -> bool {
for_each_expr_with_closures(cx, v, |e| {
for_each_expr(cx, v, |e| {
if utils::path_to_local_id(e, local_id) {
ControlFlow::Break(())
} else {
@ -184,7 +184,7 @@ pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr
let loop_start = get_enclosing_loop_or_multi_call_closure(cx, after).map(|e| e.hir_id);
let mut past_expr = false;
for_each_expr_with_closures(cx, block, |e| {
for_each_expr(cx, block, |e| {
if past_expr {
if utils::path_to_local_id(e, local_id) {
ControlFlow::Break(())

View File

@ -100,7 +100,7 @@ visitable_ref!(Stmt, visit_stmt);
/// Calls the given function once for each expression contained. This does not enter any bodies or
/// nested items.
pub fn for_each_expr<'tcx, B, C: Continue>(
pub fn for_each_expr_without_closures<'tcx, B, C: Continue>(
node: impl Visitable<'tcx>,
f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>,
) -> Option<B> {
@ -134,7 +134,7 @@ pub fn for_each_expr<'tcx, B, C: Continue>(
/// Calls the given function once for each expression contained. This will enter bodies, but not
/// nested items.
pub fn for_each_expr_with_closures<'tcx, B, C: Continue>(
pub fn for_each_expr<'tcx, B, C: Continue>(
cx: &LateContext<'tcx>,
node: impl Visitable<'tcx>,
f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>,
@ -181,7 +181,7 @@ pub fn for_each_expr_with_closures<'tcx, B, C: Continue>(
/// returns `true` if expr contains match expr desugared from try
fn contains_try(expr: &Expr<'_>) -> bool {
for_each_expr(expr, |e| {
for_each_expr_without_closures(expr, |e| {
if matches!(e.kind, ExprKind::Match(_, _, hir::MatchSource::TryDesugar(_))) {
ControlFlow::Break(())
} else {
@ -286,7 +286,7 @@ where
/// Checks if the given resolved path is used in the given body.
pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
for_each_expr_with_closures(cx, cx.tcx.hir().body(body).value, |e| {
for_each_expr(cx, cx.tcx.hir().body(body).value, |e| {
if let ExprKind::Path(p) = &e.kind {
if cx.qpath_res(p, e.hir_id) == res {
return ControlFlow::Break(());
@ -299,7 +299,7 @@ pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
/// Checks if the given local is used.
pub fn is_local_used<'tcx>(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
for_each_expr_with_closures(cx, visitable, |e| {
for_each_expr(cx, visitable, |e| {
if path_to_local_id(e, id) {
ControlFlow::Break(())
} else {
@ -757,7 +757,7 @@ pub fn for_each_local_assignment<'tcx, B>(
}
pub fn contains_break_or_continue(expr: &Expr<'_>) -> bool {
for_each_expr(expr, |e| {
for_each_expr_without_closures(expr, |e| {
if matches!(e.kind, ExprKind::Break(..) | ExprKind::Continue(..)) {
ControlFlow::Break(())
} else {
@ -776,7 +776,7 @@ pub fn local_used_once<'tcx>(
) -> Option<&'tcx Expr<'tcx>> {
let mut expr = None;
let cf = for_each_expr_with_closures(cx, visitable, |e| {
let cf = for_each_expr(cx, visitable, |e| {
if path_to_local_id(e, id) && expr.replace(e).is_some() {
ControlFlow::Break(())
} else {

View File

@ -1,6 +1,6 @@
[package]
name = "declare_clippy_lint"
version = "0.1.80"
version = "0.1.81"
edition = "2021"
publish = false

View File

@ -1,6 +1,6 @@
## `cargo lintcheck`
Runs clippy on a fixed set of crates read from
Runs Clippy on a fixed set of crates read from
`lintcheck/lintcheck_crates.toml` and saves logs of the lint warnings into the
repo. We can then check the diff and spot new or disappearing warnings.
@ -84,7 +84,7 @@ This lets us spot bad suggestions or false positives automatically in some cases
> Note: Fix mode implies `--all-targets`, so it can fix as much code as it can.
Please note that the target dir should be cleaned afterwards since clippy will modify
Please note that the target dir should be cleaned afterwards since Clippy will modify
the downloaded sources which can lead to unexpected results when running lintcheck again afterwards.
### Recursive mode

View File

@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2024-05-30"
channel = "nightly-2024-06-13"
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]

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