Merge commit '10136170fe9ed01e46aeb4f4479175b79eb0e3c7' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2024-02-27 15:25:18 +01:00
commit ad7513e874
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
1096 changed files with 14195 additions and 10884 deletions

View file

@ -45,7 +45,7 @@ unset CARGO_MANIFEST_DIR
# Run a lint and make sure it produces the expected output. It's also expected to exit with code 1 # Run a lint and make sure it produces the expected output. It's also expected to exit with code 1
# FIXME: How to match the clippy invocation in compile-test.rs? # FIXME: How to match the clippy invocation in compile-test.rs?
./target/debug/clippy-driver -Dwarnings -Aunused -Zui-testing --emit metadata --crate-type bin tests/ui/double_neg.rs 2>double_neg.stderr && exit 1 ./target/debug/clippy-driver -Dwarnings -Aunused -Zui-testing --emit metadata --crate-type bin tests/ui/double_neg.rs 2>double_neg.stderr && exit 1
sed -e "s,tests/ui,\$DIR," -e "/= help: for/d" double_neg.stderr > normalized.stderr sed -e "/= help: for/d" double_neg.stderr > normalized.stderr
diff -u normalized.stderr tests/ui/double_neg.stderr diff -u normalized.stderr tests/ui/double_neg.stderr
# make sure "clippy-driver --rustc --arg" and "rustc --arg" behave the same # make sure "clippy-driver --rustc --arg" and "rustc --arg" behave the same

View file

@ -5125,6 +5125,7 @@ Released 2018-09-13
[`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access [`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access
[`default_union_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_union_representation [`default_union_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_union_representation
[`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr [`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr
[`deprecated_clippy_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_clippy_cfg_attr
[`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver [`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver
[`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof [`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof
[`deref_by_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_by_slicing [`deref_by_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_by_slicing
@ -5157,6 +5158,7 @@ Released 2018-09-13
[`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec [`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec
[`eager_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#eager_transmute [`eager_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#eager_transmute
[`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else [`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else
[`empty_docs`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_docs
[`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop [`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop
[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum [`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum
[`empty_enum_variants_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum_variants_with_brackets [`empty_enum_variants_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum_variants_with_brackets
@ -5429,6 +5431,7 @@ Released 2018-09-13
[`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic [`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic
[`modulo_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_one [`modulo_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_one
[`multi_assignments`]: https://rust-lang.github.io/rust-clippy/master/index.html#multi_assignments [`multi_assignments`]: https://rust-lang.github.io/rust-clippy/master/index.html#multi_assignments
[`multiple_bound_locations`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_bound_locations
[`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions [`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions
[`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl [`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl
[`multiple_unsafe_ops_per_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_unsafe_ops_per_block [`multiple_unsafe_ops_per_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_unsafe_ops_per_block
@ -5725,10 +5728,12 @@ Released 2018-09-13
[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints [`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints
[`unnecessary_box_returns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns [`unnecessary_box_returns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast [`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
[`unnecessary_clippy_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_clippy_cfg
[`unnecessary_fallible_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fallible_conversions [`unnecessary_fallible_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fallible_conversions
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map [`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map [`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold [`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
[`unnecessary_get_then_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_get_then_check
[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join [`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations [`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
[`unnecessary_literal_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_literal_unwrap [`unnecessary_literal_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_literal_unwrap

View file

@ -27,17 +27,17 @@ rustc_tools_util = "0.3.0"
tempfile = { version = "3.2", optional = true } tempfile = { version = "3.2", optional = true }
termize = "0.1" termize = "0.1"
color-print = "0.3.4" color-print = "0.3.4"
anstream = "0.5.0" anstream = "0.6.0"
[dev-dependencies] [dev-dependencies]
ui_test = "0.21.2" ui_test = "0.22.2"
tester = "0.9" tester = "0.9"
regex = "1.5" regex = "1.5"
toml = "0.7.3" toml = "0.7.3"
walkdir = "2.3" walkdir = "2.3"
# This is used by the `collect-metadata` alias. # This is used by the `collect-metadata` alias.
filetime = "0.2" filetime = "0.2"
itertools = "0.11" itertools = "0.12"
# UI test dependencies # UI test dependencies
clippy_utils = { path = "clippy_utils" } clippy_utils = { path = "clippy_utils" }

View file

@ -33,26 +33,29 @@ disallowed-names = ["bar", ".."] # -> ["bar", "foo", "baz", "quux"]
To deactivate the "for further information visit *lint-link*" message you can define the `CLIPPY_DISABLE_DOCS_LINKS` To deactivate the "for further information visit *lint-link*" message you can define the `CLIPPY_DISABLE_DOCS_LINKS`
environment variable. environment variable.
### Allowing/denying lints ### Allowing/Denying Lints
You can add options to your code to `allow`/`warn`/`deny` Clippy lints: #### Attributes in Code
* the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`) You can add attributes to your code to `allow`/`warn`/`deny` Clippy lints:
* all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`, * the whole set of `warn`-by-default lints using the `clippy` lint group (`#![allow(clippy::all)]`)
`#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive lints prone to false
positives. * all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![warn(clippy::all, clippy::pedantic)]`. Note
that `clippy::pedantic` contains some very aggressive lints prone to false positives.
* only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.) * only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.)
* `allow`/`warn`/`deny` can be limited to a single function or module using `#[allow(...)]`, etc. * `allow`/`warn`/`deny` can be limited to a single function or module using `#[allow(...)]`, etc.
Note: `allow` means to suppress the lint for your code. With `warn` the lint will only emit a warning, while with `deny` 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 is the lint will emit an error, when triggering for your code. An error causes Clippy to exit with an error code, so is
useful in scripts like CI/CD. most useful in scripts used in CI/CD.
If you do not want to include your lint levels in your code, you can globally enable/disable lints by passing extra #### Command Line Flags
flags to Clippy during the run:
If you do not want to include your lint levels in the code, you can globally enable/disable lints by passing extra flags
to Clippy during the run:
To allow `lint_name`, run To allow `lint_name`, run
@ -66,19 +69,33 @@ And to warn on `lint_name`, run
cargo clippy -- -W clippy::lint_name cargo clippy -- -W clippy::lint_name
``` ```
This also works with lint groups. For example, you can run Clippy with warnings for all lints enabled: This also works with lint groups. For example, you can run Clippy with warnings for all pedantic lints enabled:
```terminal ```terminal
cargo clippy -- -W clippy::pedantic cargo clippy -- -W clippy::pedantic
``` ```
If you care only about a single lint, you can allow all others and then explicitly warn on the lint(s) you are If you care only about a certain lints, you can allow all others and then explicitly warn on the lints you are
interested in: interested in:
```terminal ```terminal
cargo clippy -- -A clippy::all -W clippy::useless_format -W clippy::... cargo clippy -- -A clippy::all -W clippy::useless_format -W clippy::...
``` ```
#### Lints Section in `Cargo.toml`
Finally, lints can be allowed/denied using [the lints
section](https://doc.rust-lang.org/nightly/cargo/reference/manifest.html#the-lints-section)) in the `Cargo.toml` file:
To deny `clippy::enum_glob_use`, put the following in the `Cargo.toml`:
```toml
[lints.clippy]
enum_glob_use = "deny"
```
For more details and options, refer to the Cargo documentation.
### Specifying the minimum supported Rust version ### 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 Projects that intend to support old versions of Rust can disable lints pertaining to newer features by specifying the
@ -113,17 +130,14 @@ found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv)
Very rarely, you may wish to prevent Clippy from evaluating certain sections of code entirely. You can do this with Very rarely, you may wish to prevent Clippy from evaluating certain sections of code entirely. You can do this with
[conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html) by checking that the [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html) by checking that the
`cargo-clippy` feature is not set. You may need to provide a stub so that the code compiles: `clippy` cfg is not set. You may need to provide a stub so that the code compiles:
```rust ```rust
#[cfg(not(feature = "cargo-clippy"))] #[cfg(not(clippy)]
include!(concat!(env!("OUT_DIR"), "/my_big_function-generated.rs")); include!(concat!(env!("OUT_DIR"), "/my_big_function-generated.rs"));
#[cfg(feature = "cargo-clippy")] #[cfg(clippy)]
fn my_big_function(_input: &str) -> Option<MyStruct> { fn my_big_function(_input: &str) -> Option<MyStruct> {
None None
} }
``` ```
This feature is not actually part of your crate, so specifying `--all-features` to other tools, e.g. `cargo test
--all-features`, will not disable it.

View file

@ -82,7 +82,7 @@ The output looks something like this (from the example earlier):
```text ```text
error: an inclusive range would be more readable error: an inclusive range would be more readable
--> $DIR/range_plus_minus_one.rs:37:14 --> tests/ui/range_plus_minus_one.rs:37:14
| |
LL | for _ in 1..1 + 1 {} LL | for _ in 1..1 + 1 {}
| ^^^^^^^^ help: use: `1..=1` | ^^^^^^^^ help: use: `1..=1`
@ -135,14 +135,14 @@ Examples:
```text ```text
error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing.
--> $DIR/drop_forget_ref.rs:10:5 --> tests/ui/drop_forget_ref.rs:10:5
| |
10 | forget(&SomeStruct); 10 | forget(&SomeStruct);
| ^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^
| |
= note: `-D clippy::forget-ref` implied by `-D warnings` = note: `-D clippy::forget-ref` implied by `-D warnings`
note: argument has type &SomeStruct note: argument has type &SomeStruct
--> $DIR/drop_forget_ref.rs:10:12 --> tests/ui/drop_forget_ref.rs:10:12
| |
10 | forget(&SomeStruct); 10 | forget(&SomeStruct);
| ^^^^^^^^^^^ | ^^^^^^^^^^^
@ -158,7 +158,7 @@ Example:
```text ```text
error: constant division of 0.0 with 0.0 will always result in NaN error: constant division of 0.0 with 0.0 will always result in NaN
--> $DIR/zero_div_zero.rs:6:25 --> tests/ui/zero_div_zero.rs:6:25
| |
6 | let other_f64_nan = 0.0f64 / 0.0; 6 | let other_f64_nan = 0.0f64 / 0.0;
| ^^^^^^^^^^^^ | ^^^^^^^^^^^^
@ -176,7 +176,7 @@ Example:
```text ```text
error: This `.fold` can be more succinctly expressed as `.any` error: This `.fold` can be more succinctly expressed as `.any`
--> $DIR/methods.rs:390:13 --> tests/ui/methods.rs:390:13
| |
390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2); 390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`

View file

@ -97,19 +97,19 @@ failures:
---- compile_test stdout ---- ---- compile_test stdout ----
normalized stderr: normalized stderr:
error: function called "foo" error: function called "foo"
--> $DIR/foo_functions.rs:6:12 --> tests/ui/foo_functions.rs:6:12
| |
LL | pub fn foo(&self) {} LL | pub fn foo(&self) {}
| ^^^ | ^^^
| |
= note: `-D clippy::foo-functions` implied by `-D warnings` = note: `-D clippy::foo-functions` implied by `-D warnings`
error: function called "foo" error: function called "foo"
--> $DIR/foo_functions.rs:13:8 --> tests/ui/foo_functions.rs:13:8
| |
LL | fn foo(&self) {} LL | fn foo(&self) {}
| ^^^ | ^^^
error: function called "foo" error: function called "foo"
--> $DIR/foo_functions.rs:19:4 --> tests/ui/foo_functions.rs:19:4
| |
LL | fn foo() {} LL | fn foo() {}
| ^^^ | ^^^

View file

@ -278,7 +278,7 @@ The minimum number of struct fields for the lints about field names to trigger
--- ---
**Affected lints:** **Affected lints:**
* [`struct_variant_names`](https://rust-lang.github.io/rust-clippy/master/index.html#struct_variant_names) * [`struct_field_names`](https://rust-lang.github.io/rust-clippy/master/index.html#struct_field_names)
## `enum-variant-size-threshold` ## `enum-variant-size-threshold`

View file

@ -325,7 +325,7 @@ define_Conf! {
/// ///
/// The minimum number of enum variants for the lints about variant names to trigger /// The minimum number of enum variants for the lints about variant names to trigger
(enum_variant_name_threshold: u64 = 3), (enum_variant_name_threshold: u64 = 3),
/// Lint: STRUCT_VARIANT_NAMES. /// Lint: STRUCT_FIELD_NAMES.
/// ///
/// The minimum number of struct fields for the lints about field names to trigger /// The minimum number of struct fields for the lints about field names to trigger
(struct_field_name_threshold: u64 = 3), (struct_field_name_threshold: u64 = 3),
@ -648,7 +648,7 @@ fn deserialize(file: &SourceFile) -> TryConf {
extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS); extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS);
extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES); extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES);
// TODO: THIS SHOULD BE TESTED, this comment will be gone soon // TODO: THIS SHOULD BE TESTED, this comment will be gone soon
if conf.conf.allowed_idents_below_min_chars.contains(&"..".to_owned()) { if conf.conf.allowed_idents_below_min_chars.contains("..") {
conf.conf conf.conf
.allowed_idents_below_min_chars .allowed_idents_below_min_chars
.extend(DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string)); .extend(DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string));

View file

@ -6,7 +6,7 @@
clippy::missing_panics_doc, clippy::missing_panics_doc,
rustc::diagnostic_outside_of_impl, rustc::diagnostic_outside_of_impl,
rustc::untranslatable_diagnostic, rustc::untranslatable_diagnostic,
rustc::untranslatable_diagnostic_trivial, rustc::untranslatable_diagnostic_trivial
)] )]
extern crate rustc_ast; extern crate rustc_ast;

View file

@ -7,7 +7,7 @@ edition = "2021"
aho-corasick = "1.0" aho-corasick = "1.0"
clap = "4.1.4" clap = "4.1.4"
indoc = "1.0" indoc = "1.0"
itertools = "0.11" itertools = "0.12"
opener = "0.6" opener = "0.6"
shell-escape = "0.1" shell-escape = "0.1"
walkdir = "2.3" walkdir = "2.3"

View file

@ -14,7 +14,7 @@ cargo_metadata = "0.18"
clippy_config = { path = "../clippy_config" } clippy_config = { path = "../clippy_config" }
clippy_utils = { path = "../clippy_utils" } clippy_utils = { path = "../clippy_utils" }
declare_clippy_lint = { path = "../declare_clippy_lint" } declare_clippy_lint = { path = "../declare_clippy_lint" }
itertools = "0.11" itertools = "0.12"
quine-mc_cluskey = "0.2" quine-mc_cluskey = "0.2"
regex-syntax = "0.8" regex-syntax = "0.8"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View file

@ -2,8 +2,11 @@ use std::fmt;
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use rustc_ast::ast::{Expr, ExprKind, InlineAsmOptions}; use rustc_ast::ast::{Expr, ExprKind, InlineAsmOptions};
use rustc_lint::{EarlyContext, EarlyLintPass, Lint}; use rustc_ast::{InlineAsm, Item, ItemKind};
use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::Span;
use rustc_target::asm::InlineAsmArch;
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
enum AsmStyle { enum AsmStyle {
@ -31,8 +34,14 @@ impl std::ops::Not for AsmStyle {
} }
} }
fn check_expr_asm_syntax(lint: &'static Lint, cx: &EarlyContext<'_>, expr: &Expr, check_for: AsmStyle) { fn check_asm_syntax(
if let ExprKind::InlineAsm(ref inline_asm) = expr.kind { lint: &'static Lint,
cx: &EarlyContext<'_>,
inline_asm: &InlineAsm,
span: Span,
check_for: AsmStyle,
) {
if matches!(cx.sess().asm_arch, Some(InlineAsmArch::X86 | InlineAsmArch::X86_64)) {
let style = if inline_asm.options.contains(InlineAsmOptions::ATT_SYNTAX) { let style = if inline_asm.options.contains(InlineAsmOptions::ATT_SYNTAX) {
AsmStyle::Att AsmStyle::Att
} else { } else {
@ -43,7 +52,7 @@ fn check_expr_asm_syntax(lint: &'static Lint, cx: &EarlyContext<'_>, expr: &Expr
span_lint_and_help( span_lint_and_help(
cx, cx,
lint, lint,
expr.span, span,
&format!("{style} x86 assembly syntax used"), &format!("{style} x86 assembly syntax used"),
None, None,
&format!("use {} x86 assembly syntax", !style), &format!("use {} x86 assembly syntax", !style),
@ -89,7 +98,15 @@ declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]);
impl EarlyLintPass for InlineAsmX86IntelSyntax { impl EarlyLintPass for InlineAsmX86IntelSyntax {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
check_expr_asm_syntax(Self::get_lints()[0], cx, expr, AsmStyle::Intel); if let ExprKind::InlineAsm(inline_asm) = &expr.kind {
check_asm_syntax(Self::get_lints()[0], cx, inline_asm, expr.span, AsmStyle::Intel);
}
}
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if let ItemKind::GlobalAsm(inline_asm) = &item.kind {
check_asm_syntax(Self::get_lints()[0], cx, inline_asm, item.span, AsmStyle::Intel);
}
} }
} }
@ -130,6 +147,14 @@ declare_lint_pass!(InlineAsmX86AttSyntax => [INLINE_ASM_X86_ATT_SYNTAX]);
impl EarlyLintPass for InlineAsmX86AttSyntax { impl EarlyLintPass for InlineAsmX86AttSyntax {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
check_expr_asm_syntax(Self::get_lints()[0], cx, expr, AsmStyle::Att); if let ExprKind::InlineAsm(inline_asm) = &expr.kind {
check_asm_syntax(Self::get_lints()[0], cx, inline_asm, expr.span, AsmStyle::Att);
}
}
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if let ItemKind::GlobalAsm(inline_asm) = &item.kind {
check_asm_syntax(Self::get_lints()[0], cx, inline_asm, item.span, AsmStyle::Att);
}
} }
} }

View file

@ -1,7 +1,9 @@
//! checks for attributes //! checks for attributes
use clippy_config::msrvs::{self, Msrv}; use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::diagnostics::{
span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
};
use clippy_utils::is_from_proc_macro; use clippy_utils::is_from_proc_macro;
use clippy_utils::macros::{is_panic, macro_backtrace}; use clippy_utils::macros::{is_panic, macro_backtrace};
use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments}; use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
@ -433,6 +435,56 @@ declare_clippy_lint! {
"prevent from misusing the wrong attr name" "prevent from misusing the wrong attr name"
} }
declare_clippy_lint! {
/// ### What it does
/// Checks for `#[cfg_attr(feature = "cargo-clippy", ...)]` and for
/// `#[cfg(feature = "cargo-clippy")]` and suggests to replace it with
/// `#[cfg_attr(clippy, ...)]` or `#[cfg(clippy)]`.
///
/// ### Why is this bad?
/// This feature has been deprecated for years and shouldn't be used anymore.
///
/// ### Example
/// ```no_run
/// #[cfg(feature = "cargo-clippy")]
/// struct Bar;
/// ```
///
/// Use instead:
/// ```no_run
/// #[cfg(clippy)]
/// struct Bar;
/// ```
#[clippy::version = "1.78.0"]
pub DEPRECATED_CLIPPY_CFG_ATTR,
suspicious,
"usage of `cfg(feature = \"cargo-clippy\")` instead of `cfg(clippy)`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `#[cfg_attr(clippy, allow(clippy::lint))]`
/// and suggests to replace it with `#[allow(clippy::lint)]`.
///
/// ### Why is this bad?
/// There is no reason to put clippy attributes behind a clippy `cfg` as they are not
/// run by anything else than clippy.
///
/// ### Example
/// ```no_run
/// #![cfg_attr(clippy, allow(clippy::deprecated_cfg_attr))]
/// ```
///
/// Use instead:
/// ```no_run
/// #![allow(clippy::deprecated_cfg_attr)]
/// ```
#[clippy::version = "1.78.0"]
pub UNNECESSARY_CLIPPY_CFG,
suspicious,
"usage of `cfg_attr(clippy, allow(clippy::lint))` instead of `allow(clippy::lint)`"
}
declare_lint_pass!(Attributes => [ declare_lint_pass!(Attributes => [
ALLOW_ATTRIBUTES_WITHOUT_REASON, ALLOW_ATTRIBUTES_WITHOUT_REASON,
INLINE_ALWAYS, INLINE_ALWAYS,
@ -512,6 +564,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|| is_word(lint, sym::deprecated) || is_word(lint, sym::deprecated)
|| is_word(lint, sym!(unreachable_pub)) || is_word(lint, sym!(unreachable_pub))
|| is_word(lint, sym!(unused)) || is_word(lint, sym!(unused))
|| is_word(lint, sym!(unused_import_braces))
|| extract_clippy_lint(lint).map_or(false, |s| { || extract_clippy_lint(lint).map_or(false, |s| {
matches!( matches!(
s.as_str(), s.as_str(),
@ -794,6 +847,8 @@ impl_lint_pass!(EarlyAttributes => [
EMPTY_LINE_AFTER_DOC_COMMENTS, EMPTY_LINE_AFTER_DOC_COMMENTS,
NON_MINIMAL_CFG, NON_MINIMAL_CFG,
MAYBE_MISUSED_CFG, MAYBE_MISUSED_CFG,
DEPRECATED_CLIPPY_CFG_ATTR,
UNNECESSARY_CLIPPY_CFG,
]); ]);
impl EarlyLintPass for EarlyAttributes { impl EarlyLintPass for EarlyAttributes {
@ -803,6 +858,7 @@ impl EarlyLintPass for EarlyAttributes {
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
check_deprecated_cfg_attr(cx, attr, &self.msrv); check_deprecated_cfg_attr(cx, attr, &self.msrv);
check_deprecated_cfg(cx, attr);
check_mismatched_target_os(cx, attr); check_mismatched_target_os(cx, attr);
check_minimal_cfg_condition(cx, attr); check_minimal_cfg_condition(cx, attr);
check_misused_cfg(cx, attr); check_misused_cfg(cx, attr);
@ -857,15 +913,53 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It
} }
} }
fn check_cargo_clippy_attr(cx: &EarlyContext<'_>, item: &rustc_ast::MetaItem) {
if item.has_name(sym::feature) && item.value_str().is_some_and(|v| v.as_str() == "cargo-clippy") {
span_lint_and_sugg(
cx,
DEPRECATED_CLIPPY_CFG_ATTR,
item.span,
"`feature = \"cargo-clippy\"` was replaced by `clippy`",
"replace with",
"clippy".to_string(),
Applicability::MachineApplicable,
);
}
}
fn check_deprecated_cfg_recursively(cx: &EarlyContext<'_>, attr: &rustc_ast::MetaItem) {
if let Some(ident) = attr.ident() {
if ["any", "all", "not"].contains(&ident.name.as_str()) {
let Some(list) = attr.meta_item_list() else { return };
for item in list.iter().filter_map(|item| item.meta_item()) {
check_deprecated_cfg_recursively(cx, item);
}
} else {
check_cargo_clippy_attr(cx, attr);
}
}
}
fn check_deprecated_cfg(cx: &EarlyContext<'_>, attr: &Attribute) {
if attr.has_name(sym::cfg)
&& let Some(list) = attr.meta_item_list()
{
for item in list.iter().filter_map(|item| item.meta_item()) {
check_deprecated_cfg_recursively(cx, item);
}
}
}
fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) { fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) {
if msrv.meets(msrvs::TOOL_ATTRIBUTES)
// check cfg_attr // check cfg_attr
&& attr.has_name(sym::cfg_attr) if attr.has_name(sym::cfg_attr)
&& let Some(items) = attr.meta_item_list() && let Some(items) = attr.meta_item_list()
&& items.len() == 2 && items.len() == 2
// check for `rustfmt`
&& let Some(feature_item) = items[0].meta_item() && let Some(feature_item) = items[0].meta_item()
&& feature_item.has_name(sym::rustfmt) {
// check for `rustfmt`
if feature_item.has_name(sym::rustfmt)
&& msrv.meets(msrvs::TOOL_ATTRIBUTES)
// check for `rustfmt_skip` and `rustfmt::skip` // check for `rustfmt_skip` and `rustfmt::skip`
&& let Some(skip_item) = &items[1].meta_item() && let Some(skip_item) = &items[1].meta_item()
&& (skip_item.has_name(sym!(rustfmt_skip)) && (skip_item.has_name(sym!(rustfmt_skip))
@ -890,6 +984,75 @@ fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msr
"#[rustfmt::skip]".to_string(), "#[rustfmt::skip]".to_string(),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
} else {
check_deprecated_cfg_recursively(cx, feature_item);
if let Some(behind_cfg_attr) = items[1].meta_item() {
check_clippy_cfg_attr(cx, feature_item, behind_cfg_attr, attr);
}
}
}
}
fn check_clippy_cfg_attr(
cx: &EarlyContext<'_>,
cfg_attr: &rustc_ast::MetaItem,
behind_cfg_attr: &rustc_ast::MetaItem,
attr: &Attribute,
) {
if cfg_attr.has_name(sym::clippy)
&& let Some(ident) = behind_cfg_attr.ident()
&& Level::from_symbol(ident.name, Some(attr.id)).is_some()
&& let Some(items) = behind_cfg_attr.meta_item_list()
{
let nb_items = items.len();
let mut clippy_lints = Vec::with_capacity(items.len());
for item in items {
if let Some(meta_item) = item.meta_item()
&& let [part1, _] = meta_item.path.segments.as_slice()
&& part1.ident.name == sym::clippy
{
clippy_lints.push(item.span());
}
}
if clippy_lints.is_empty() {
return;
}
if nb_items == clippy_lints.len() {
if let Some(snippet) = snippet_opt(cx, behind_cfg_attr.span) {
span_lint_and_sugg(
cx,
UNNECESSARY_CLIPPY_CFG,
attr.span,
"no need to put clippy lints behind a `clippy` cfg",
"replace with",
format!(
"#{}[{}]",
if attr.style == AttrStyle::Inner { "!" } else { "" },
snippet
),
Applicability::MachineApplicable,
);
}
} else {
let snippet = clippy_lints
.iter()
.filter_map(|sp| snippet_opt(cx, *sp))
.collect::<Vec<_>>()
.join(",");
span_lint_and_note(
cx,
UNNECESSARY_CLIPPY_CFG,
clippy_lints,
"no need to put clippy lints behind a `clippy` cfg",
None,
&format!(
"write instead: `#{}[{}({})]`",
if attr.style == AttrStyle::Inner { "!" } else { "" },
ident.name,
snippet
),
);
}
} }
} }

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::source::snippet_block_with_applicability; use clippy_utils::source::snippet_block_with_applicability;
use clippy_utils::ty::implements_trait; use clippy_utils::ty::implements_trait;
use clippy_utils::visitors::{for_each_expr, Descend}; use clippy_utils::visitors::{for_each_expr, Descend};
use clippy_utils::{get_parent_expr, higher}; use clippy_utils::{get_parent_expr, higher, is_from_proc_macro};
use core::ops::ControlFlow; use core::ops::ControlFlow;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BlockCheckMode, Expr, ExprKind, MatchSource}; use rustc_hir::{BlockCheckMode, Expr, ExprKind, MatchSource};
@ -13,7 +13,7 @@ use rustc_span::sym;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for `if` conditions that use blocks containing an /// Checks for `if` and `match` conditions that use blocks containing an
/// expression, statements or conditions that use closures with blocks. /// expression, statements or conditions that use closures with blocks.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
@ -25,6 +25,11 @@ declare_clippy_lint! {
/// if { true } { /* ... */ } /// if { true } { /* ... */ }
/// ///
/// if { let x = somefunc(); x } { /* ... */ } /// if { let x = somefunc(); x } { /* ... */ }
///
/// match { let e = somefunc(); e } {
/// // ...
/// # _ => {}
/// }
/// ``` /// ```
/// ///
/// Use instead: /// Use instead:
@ -34,6 +39,12 @@ declare_clippy_lint! {
/// ///
/// let res = { let x = somefunc(); x }; /// let res = { let x = somefunc(); x };
/// if res { /* ... */ } /// if res { /* ... */ }
///
/// let res = { let e = somefunc(); e };
/// match res {
/// // ...
/// # _ => {}
/// }
/// ``` /// ```
#[clippy::version = "1.45.0"] #[clippy::version = "1.45.0"]
pub BLOCKS_IN_CONDITIONS, pub BLOCKS_IN_CONDITIONS,
@ -94,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInConditions {
} }
} else { } else {
let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span); let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
if span.from_expansion() || expr.span.from_expansion() { if span.from_expansion() || expr.span.from_expansion() || is_from_proc_macro(cx, cond) {
return; return;
} }
// move block higher // move block higher

View file

@ -85,7 +85,117 @@ impl<'tcx> LateLintPass<'tcx> for NonminimalBool {
) { ) {
NonminimalBoolVisitor { cx }.visit_body(body); NonminimalBoolVisitor { cx }.visit_body(body);
} }
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
match expr.kind {
ExprKind::Unary(UnOp::Not, sub) => check_inverted_condition(cx, expr.span, sub),
// This check the case where an element in a boolean comparison is inverted, like:
//
// ```
// let a = true;
// !a == false;
// ```
ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq | BinOpKind::Ne) => {
check_inverted_bool_in_condition(cx, expr.span, op.node, left, right);
},
_ => {},
} }
}
}
fn inverted_bin_op_eq_str(op: BinOpKind) -> Option<&'static str> {
match op {
BinOpKind::Eq => Some("!="),
BinOpKind::Ne => Some("=="),
_ => None,
}
}
fn bin_op_eq_str(op: BinOpKind) -> Option<&'static str> {
match op {
BinOpKind::Eq => Some("=="),
BinOpKind::Ne => Some("!="),
_ => None,
}
}
fn check_inverted_condition(cx: &LateContext<'_>, expr_span: Span, sub_expr: &Expr<'_>) {
if !expr_span.from_expansion()
&& let ExprKind::Binary(op, left, right) = sub_expr.kind
&& let Some(left) = snippet_opt(cx, left.span)
&& let Some(right) = snippet_opt(cx, right.span)
{
let Some(op) = inverted_bin_op_eq_str(op.node) else {
return;
};
span_lint_and_sugg(
cx,
NONMINIMAL_BOOL,
expr_span,
"this boolean expression can be simplified",
"try",
format!("{left} {op} {right}",),
Applicability::MachineApplicable,
);
}
}
fn check_inverted_bool_in_condition(
cx: &LateContext<'_>,
expr_span: Span,
op: BinOpKind,
left: &Expr<'_>,
right: &Expr<'_>,
) {
if expr_span.from_expansion()
&& (!cx.typeck_results().node_types()[left.hir_id].is_bool()
|| !cx.typeck_results().node_types()[right.hir_id].is_bool())
{
return;
}
let suggestion = match (left.kind, right.kind) {
(ExprKind::Unary(UnOp::Not, left_sub), ExprKind::Unary(UnOp::Not, right_sub)) => {
let Some(left) = snippet_opt(cx, left_sub.span) else {
return;
};
let Some(right) = snippet_opt(cx, right_sub.span) else {
return;
};
let Some(op) = bin_op_eq_str(op) else { return };
format!("{left} {op} {right}")
},
(ExprKind::Unary(UnOp::Not, left_sub), _) => {
let Some(left) = snippet_opt(cx, left_sub.span) else {
return;
};
let Some(right) = snippet_opt(cx, right.span) else {
return;
};
let Some(op) = inverted_bin_op_eq_str(op) else { return };
format!("{left} {op} {right}")
},
(_, ExprKind::Unary(UnOp::Not, right_sub)) => {
let Some(left) = snippet_opt(cx, left.span) else { return };
let Some(right) = snippet_opt(cx, right_sub.span) else {
return;
};
let Some(op) = inverted_bin_op_eq_str(op) else { return };
format!("{left} {op} {right}")
},
_ => return,
};
span_lint_and_sugg(
cx,
NONMINIMAL_BOOL,
expr_span,
"this boolean expression can be simplified",
"try",
suggestion,
Applicability::MachineApplicable,
);
}
struct NonminimalBoolVisitor<'a, 'tcx> { struct NonminimalBoolVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>, cx: &'a LateContext<'tcx>,
} }

View file

@ -1,11 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::macro_backtrace; use clippy_utils::macros::macro_backtrace;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::expr_sig; use clippy_utils::ty::expr_sig;
use clippy_utils::{get_parent_node, is_default_equivalent, path_def_id}; use clippy_utils::{is_default_equivalent, path_def_id};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::Res; use rustc_hir::def::Res;
use rustc_hir::intravisit::{walk_ty, Visitor}; use rustc_hir::intravisit::{walk_ty, Visitor};
use rustc_hir::{Block, Expr, ExprKind, Local, Node, QPath, TyKind}; use rustc_hir::{Block, Expr, ExprKind, Local, Node, QPath, Ty, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_middle::ty::print::with_forced_trimmed_paths;
@ -41,13 +42,24 @@ declare_lint_pass!(BoxDefault => [BOX_DEFAULT]);
impl LateLintPass<'_> for BoxDefault { impl LateLintPass<'_> for BoxDefault {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
// If the expression is a call (`Box::new(...)`)
if let ExprKind::Call(box_new, [arg]) = expr.kind if let ExprKind::Call(box_new, [arg]) = expr.kind
// And call is of the form `<T>::something`
// Here, it would be `<Box>::new`
&& let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_new.kind && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_new.kind
&& let ExprKind::Call(arg_path, ..) = arg.kind // And that method is `new`
&& !in_external_macro(cx.sess(), expr.span)
&& (expr.span.eq_ctxt(arg.span) || is_local_vec_expn(cx, arg, expr))
&& seg.ident.name == sym::new && seg.ident.name == sym::new
// And the call is that of a `Box` method
&& path_def_id(cx, ty).map_or(false, |id| Some(id) == cx.tcx.lang_items().owned_box()) && path_def_id(cx, ty).map_or(false, |id| Some(id) == cx.tcx.lang_items().owned_box())
// And the single argument to the call is another function call
// This is the `T::default()` of `Box::new(T::default())`
&& let ExprKind::Call(arg_path, inner_call_args) = arg.kind
// And we are not in a foreign crate's macro
&& !in_external_macro(cx.sess(), expr.span)
// And the argument expression has the same context as the outer call expression
// or that we are inside a `vec!` macro expansion
&& (expr.span.eq_ctxt(arg.span) || is_local_vec_expn(cx, arg, expr))
// And the argument is equivalent to `Default::default()`
&& is_default_equivalent(cx, arg) && is_default_equivalent(cx, arg)
{ {
span_lint_and_sugg( span_lint_and_sugg(
@ -59,7 +71,17 @@ impl LateLintPass<'_> for BoxDefault {
if is_plain_default(cx, arg_path) || given_type(cx, expr) { if is_plain_default(cx, arg_path) || given_type(cx, expr) {
"Box::default()".into() "Box::default()".into()
} else if let Some(arg_ty) = cx.typeck_results().expr_ty(arg).make_suggestable(cx.tcx, true) { } else if let Some(arg_ty) = cx.typeck_results().expr_ty(arg).make_suggestable(cx.tcx, true) {
// Check if we can copy from the source expression in the replacement.
// We need the call to have no argument (see `explicit_default_type`).
if inner_call_args.is_empty()
&& let Some(ty) = explicit_default_type(arg_path)
&& let Some(s) = snippet_opt(cx, ty.span)
{
format!("Box::<{s}>::default()")
} else {
// Otherwise, use the inferred type's formatting.
with_forced_trimmed_paths!(format!("Box::<{arg_ty}>::default()")) with_forced_trimmed_paths!(format!("Box::<{arg_ty}>::default()"))
}
} else { } else {
return; return;
}, },
@ -81,6 +103,20 @@ fn is_plain_default(cx: &LateContext<'_>, arg_path: &Expr<'_>) -> bool {
} }
} }
// Checks whether the call is of the form `A::B::f()`. Returns `A::B` if it is.
//
// In the event we have this kind of construct, it's easy to use `A::B` as a replacement in the
// quickfix. `f` must however have no parameter. Should `f` have some, then some of the type of
// `A::B` may be inferred from the arguments. This would be the case for `Vec::from([0; false])`,
// where the argument to `from` allows inferring this is a `Vec<bool>`
fn explicit_default_type<'a>(arg_path: &'a Expr<'_>) -> Option<&'a Ty<'a>> {
if let ExprKind::Path(QPath::TypeRelative(ty, _)) = &arg_path.kind {
Some(ty)
} else {
None
}
}
fn is_local_vec_expn(cx: &LateContext<'_>, expr: &Expr<'_>, ref_expr: &Expr<'_>) -> bool { fn is_local_vec_expn(cx: &LateContext<'_>, expr: &Expr<'_>, ref_expr: &Expr<'_>) -> bool {
macro_backtrace(expr.span).next().map_or(false, |call| { macro_backtrace(expr.span).next().map_or(false, |call| {
cx.tcx.is_diagnostic_item(sym::vec_macro, call.def_id) && call.span.eq_ctxt(ref_expr.span) cx.tcx.is_diagnostic_item(sym::vec_macro, call.def_id) && call.span.eq_ctxt(ref_expr.span)
@ -100,26 +136,23 @@ impl<'tcx> Visitor<'tcx> for InferVisitor {
} }
fn given_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { fn given_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match get_parent_node(cx.tcx, expr.hir_id) { match cx.tcx.parent_hir_node(expr.hir_id) {
Some(Node::Local(Local { ty: Some(ty), .. })) => { Node::Local(Local { ty: Some(ty), .. }) => {
let mut v = InferVisitor::default(); let mut v = InferVisitor::default();
v.visit_ty(ty); v.visit_ty(ty);
!v.0 !v.0
}, },
Some(
Node::Expr(Expr { Node::Expr(Expr {
kind: ExprKind::Call(path, args), kind: ExprKind::Call(path, args),
.. ..
}) })
| Node::Block(Block { | Node::Block(Block {
expr: expr: Some(Expr {
Some(Expr {
kind: ExprKind::Call(path, args), kind: ExprKind::Call(path, args),
.. ..
}), }),
.. ..
}), }) => {
) => {
if let Some(index) = args.iter().position(|arg| arg.hir_id == expr.hir_id) if let Some(index) = args.iter().position(|arg| arg.hir_id == expr.hir_id)
&& let Some(sig) = expr_sig(cx, path) && let Some(sig) = expr_sig(cx, path)
&& let Some(input) = sig.input(index) && let Some(input) = sig.input(index)

View file

@ -131,8 +131,7 @@ pub(super) fn check(
let cast_from_ptr_size = def.repr().int.map_or(true, |ty| matches!(ty, IntegerType::Pointer(_),)); let cast_from_ptr_size = def.repr().int.map_or(true, |ty| matches!(ty, IntegerType::Pointer(_),));
let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) { let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) {
(false, false) if from_nbits > to_nbits => "", (_, false) if from_nbits > to_nbits => "",
(true, false) if from_nbits > to_nbits => "",
(false, true) if from_nbits > 64 => "", (false, true) if from_nbits > 64 => "",
(false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers", (false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers",
_ => return, _ => return,

View file

@ -1,15 +1,47 @@
use std::convert::Infallible;
use std::ops::ControlFlow;
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use clippy_utils::{clip, method_chain_args, sext}; use clippy_utils::visitors::{for_each_expr, Descend};
use clippy_utils::{method_chain_args, sext};
use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty, UintTy}; use rustc_middle::ty::{self, Ty};
use super::CAST_SIGN_LOSS; use super::CAST_SIGN_LOSS;
const METHODS_RET_POSITIVE: &[&str] = &["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"]; /// A list of methods that can never return a negative value.
/// Includes methods that panic rather than returning a negative value.
///
/// Methods that can overflow and return a negative value must not be included in this list,
/// because casting their return values can still result in sign loss.
const METHODS_RET_POSITIVE: &[&str] = &[
"checked_abs",
"saturating_abs",
"isqrt",
"checked_isqrt",
"rem_euclid",
"checked_rem_euclid",
"wrapping_rem_euclid",
];
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { /// A list of methods that act like `pow()`. See `pow_call_result_sign()` for details.
///
/// Methods that can overflow and return a negative value must not be included in this list,
/// because casting their return values can still result in sign loss.
const METHODS_POW: &[&str] = &["pow", "saturating_pow", "checked_pow"];
/// A list of methods that act like `unwrap()`, and don't change the sign of the inner value.
const METHODS_UNWRAP: &[&str] = &["unwrap", "unwrap_unchecked", "expect", "into_ok"];
pub(super) fn check<'cx>(
cx: &LateContext<'cx>,
expr: &Expr<'_>,
cast_op: &Expr<'_>,
cast_from: Ty<'cx>,
cast_to: Ty<'_>,
) {
if should_lint(cx, cast_op, cast_from, cast_to) { if should_lint(cx, cast_op, cast_from, cast_to) {
span_lint( span_lint(
cx, cx,
@ -20,35 +52,27 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, c
} }
} }
fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) -> bool { fn should_lint<'cx>(cx: &LateContext<'cx>, cast_op: &Expr<'_>, cast_from: Ty<'cx>, cast_to: Ty<'_>) -> bool {
match (cast_from.is_integral(), cast_to.is_integral()) { match (cast_from.is_integral(), cast_to.is_integral()) {
(true, true) => { (true, true) => {
if !cast_from.is_signed() || cast_to.is_signed() { if !cast_from.is_signed() || cast_to.is_signed() {
return false; return false;
} }
// Don't lint if `cast_op` is known to be positive. // Don't lint if `cast_op` is known to be positive, ignoring overflow.
if let Sign::ZeroOrPositive = expr_sign(cx, cast_op, cast_from) { if let Sign::ZeroOrPositive = expr_sign(cx, cast_op, cast_from) {
return false; return false;
} }
let (mut uncertain_count, mut negative_count) = (0, 0); if let Sign::ZeroOrPositive = expr_muldiv_sign(cx, cast_op) {
// Peel off possible binary expressions, e.g. x * x * y => [x, x, y] return false;
let Some(exprs) = exprs_with_selected_binop_peeled(cast_op) else {
// Assume cast sign lose if we cannot determine the sign of `cast_op`
return true;
};
for expr in exprs {
let ty = cx.typeck_results().expr_ty(expr);
match expr_sign(cx, expr, ty) {
Sign::Negative => negative_count += 1,
Sign::Uncertain => uncertain_count += 1,
Sign::ZeroOrPositive => (),
};
} }
// Lint if there are odd number of uncertain or negative results if let Sign::ZeroOrPositive = expr_add_sign(cx, cast_op) {
uncertain_count % 2 == 1 || negative_count % 2 == 1 return false;
}
true
}, },
(false, true) => !cast_to.is_signed(), (false, true) => !cast_to.is_signed(),
@ -57,7 +81,13 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
} }
} }
fn get_const_int_eval(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Option<i128> { fn get_const_signed_int_eval<'cx>(
cx: &LateContext<'cx>,
expr: &Expr<'_>,
ty: impl Into<Option<Ty<'cx>>>,
) -> Option<i128> {
let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr));
if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)? if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)?
&& let ty::Int(ity) = *ty.kind() && let ty::Int(ity) = *ty.kind()
{ {
@ -66,29 +96,52 @@ fn get_const_int_eval(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Opti
None None
} }
fn get_const_unsigned_int_eval<'cx>(
cx: &LateContext<'cx>,
expr: &Expr<'_>,
ty: impl Into<Option<Ty<'cx>>>,
) -> Option<u128> {
let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr));
if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)?
&& let ty::Uint(_ity) = *ty.kind()
{
return Some(n);
}
None
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Sign { enum Sign {
ZeroOrPositive, ZeroOrPositive,
Negative, Negative,
Uncertain, Uncertain,
} }
fn expr_sign(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Sign { fn expr_sign<'cx>(cx: &LateContext<'cx>, expr: &Expr<'_>, ty: impl Into<Option<Ty<'cx>>>) -> Sign {
// Try evaluate this expr first to see if it's positive // Try evaluate this expr first to see if it's positive
if let Some(val) = get_const_int_eval(cx, expr, ty) { if let Some(val) = get_const_signed_int_eval(cx, expr, ty) {
return if val >= 0 { Sign::ZeroOrPositive } else { Sign::Negative }; return if val >= 0 { Sign::ZeroOrPositive } else { Sign::Negative };
} }
if let Some(_val) = get_const_unsigned_int_eval(cx, expr, None) {
return Sign::ZeroOrPositive;
}
// Calling on methods that always return non-negative values. // Calling on methods that always return non-negative values.
if let ExprKind::MethodCall(path, caller, args, ..) = expr.kind { if let ExprKind::MethodCall(path, caller, args, ..) = expr.kind {
let mut method_name = path.ident.name.as_str(); let mut method_name = path.ident.name.as_str();
if method_name == "unwrap" // Peel unwrap(), expect(), etc.
&& let Some(arglist) = method_chain_args(expr, &["unwrap"]) while let Some(&found_name) = METHODS_UNWRAP.iter().find(|&name| &method_name == name)
&& let Some(arglist) = method_chain_args(expr, &[found_name])
&& let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind && let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind
{ {
// The original type has changed, but we can't use `ty` here anyway, because it has been
// moved.
method_name = inner_path.ident.name.as_str(); method_name = inner_path.ident.name.as_str();
} }
if method_name == "pow" if METHODS_POW.iter().any(|&name| method_name == name)
&& let [arg] = args && let [arg] = args
{ {
return pow_call_result_sign(cx, caller, arg); return pow_call_result_sign(cx, caller, arg);
@ -100,53 +153,182 @@ fn expr_sign(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Sign {
Sign::Uncertain Sign::Uncertain
} }
/// Return the sign of the `pow` call's result. /// Return the sign of the `pow` call's result, ignoring overflow.
/// ///
/// If the caller is a positive number, the result is always positive, /// If the base is positive, the result is always positive.
/// If the `power_of` is a even number, the result is always positive as well, /// If the exponent is a even number, the result is always positive,
/// Otherwise a [`Sign::Uncertain`] will be returned. /// Otherwise, if the base is negative, and the exponent is an odd number, the result is always
fn pow_call_result_sign(cx: &LateContext<'_>, caller: &Expr<'_>, power_of: &Expr<'_>) -> Sign { /// negative.
let caller_ty = cx.typeck_results().expr_ty(caller); ///
if let Some(caller_val) = get_const_int_eval(cx, caller, caller_ty) /// Otherwise, returns [`Sign::Uncertain`].
&& caller_val >= 0 fn pow_call_result_sign(cx: &LateContext<'_>, base: &Expr<'_>, exponent: &Expr<'_>) -> Sign {
{ let base_sign = expr_sign(cx, base, None);
return Sign::ZeroOrPositive;
// Rust's integer pow() functions take an unsigned exponent.
let exponent_val = get_const_unsigned_int_eval(cx, exponent, None);
let exponent_is_even = exponent_val.map(|val| val % 2 == 0);
match (base_sign, exponent_is_even) {
// Non-negative bases always return non-negative results, ignoring overflow.
(Sign::ZeroOrPositive, _) |
// Any base raised to an even exponent is non-negative.
// These both hold even if we don't know the value of the base.
(_, Some(true))
=> Sign::ZeroOrPositive,
// A negative base raised to an odd exponent is non-negative.
(Sign::Negative, Some(false)) => Sign::Negative,
// Negative/unknown base to an unknown exponent, or unknown base to an odd exponent.
// Could be negative or positive depending on the actual values.
(Sign::Negative | Sign::Uncertain, None) |
(Sign::Uncertain, Some(false)) => Sign::Uncertain,
}
} }
if let Some(Constant::Int(n)) = constant(cx, cx.typeck_results(), power_of) /// Peels binary operators such as [`BinOpKind::Mul`] or [`BinOpKind::Rem`],
&& clip(cx.tcx, n, UintTy::U32) % 2 == 0 /// where the result could always be positive. See [`exprs_with_muldiv_binop_peeled()`] for details.
{ ///
return Sign::ZeroOrPositive; /// Returns the sign of the list of peeled expressions.
fn expr_muldiv_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign {
let mut negative_count = 0;
// Peel off possible binary expressions, for example:
// x * x / y => [x, x, y]
// a % b => [a]
let exprs = exprs_with_muldiv_binop_peeled(expr);
for expr in exprs {
match expr_sign(cx, expr, None) {
Sign::Negative => negative_count += 1,
// A mul/div is:
// - uncertain if there are any uncertain values (because they could be negative or positive),
Sign::Uncertain => return Sign::Uncertain,
Sign::ZeroOrPositive => (),
};
} }
// A mul/div is:
// - negative if there are an odd number of negative values,
// - positive or zero otherwise.
if negative_count % 2 == 1 {
Sign::Negative
} else {
Sign::ZeroOrPositive
}
}
/// Peels binary operators such as [`BinOpKind::Add`], where the result could always be positive.
/// See [`exprs_with_add_binop_peeled()`] for details.
///
/// Returns the sign of the list of peeled expressions.
fn expr_add_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign {
let mut negative_count = 0;
let mut positive_count = 0;
// Peel off possible binary expressions, for example:
// a + b + c => [a, b, c]
let exprs = exprs_with_add_binop_peeled(expr);
for expr in exprs {
match expr_sign(cx, expr, None) {
Sign::Negative => negative_count += 1,
// A sum is:
// - uncertain if there are any uncertain values (because they could be negative or positive),
Sign::Uncertain => return Sign::Uncertain,
Sign::ZeroOrPositive => positive_count += 1,
};
}
// A sum is:
// - positive or zero if there are only positive (or zero) values,
// - negative if there are only negative (or zero) values, or
// - uncertain if there are both.
// We could split Zero out into its own variant, but we don't yet.
if negative_count == 0 {
Sign::ZeroOrPositive
} else if positive_count == 0 {
Sign::Negative
} else {
Sign::Uncertain Sign::Uncertain
} }
}
/// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`], /// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`],
/// which the result could always be positive under certain condition. /// where the result depends on:
/// - the number of negative values in the entire expression, or
/// - the number of negative values on the left hand side of the expression.
/// Ignores overflow.
/// ///
/// Other operators such as `+`/`-` causing the result's sign hard to determine, which we will ///
/// return `None` /// Expressions using other operators are preserved, so we can try to evaluate them later.
fn exprs_with_selected_binop_peeled<'a>(expr: &'a Expr<'_>) -> Option<Vec<&'a Expr<'a>>> { fn exprs_with_muldiv_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
#[inline] let mut res = vec![];
fn collect_operands<'a>(expr: &'a Expr<'a>, operands: &mut Vec<&'a Expr<'a>>) -> Option<()> {
match expr.kind { for_each_expr(expr, |sub_expr| -> ControlFlow<Infallible, Descend> {
ExprKind::Binary(op, lhs, rhs) => { // We don't check for mul/div/rem methods here, but we could.
if matches!(op.node, BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem) { if let ExprKind::Binary(op, lhs, _rhs) = sub_expr.kind {
collect_operands(lhs, operands); if matches!(op.node, BinOpKind::Mul | BinOpKind::Div) {
operands.push(rhs); // For binary operators where both sides contribute to the sign of the result,
// collect all their operands, recursively. This ignores overflow.
ControlFlow::Continue(Descend::Yes)
} else if matches!(op.node, BinOpKind::Rem | BinOpKind::Shr) {
// For binary operators where the left hand side determines the sign of the result,
// only collect that side, recursively. Overflow panics, so this always holds.
//
// Large left shifts turn negatives into zeroes, so we can't use it here.
//
// > Given remainder = dividend % divisor, the remainder will have the same sign as the dividend
// > ...
// > Arithmetic right shift on signed integer types
// https://doc.rust-lang.org/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators
// We want to descend into the lhs, but skip the rhs.
// That's tricky to do using for_each_expr(), so we just keep the lhs intact.
res.push(lhs);
ControlFlow::Continue(Descend::No)
} else { } else {
// Things are complicated when there are other binary ops exist, // The sign of the result of other binary operators depends on the values of the operands,
// abort checking by returning `None` for now. // so try to evaluate the expression.
return None; res.push(sub_expr);
ControlFlow::Continue(Descend::No)
} }
}, } else {
_ => operands.push(expr), // For other expressions, including unary operators and constants, try to evaluate the expression.
res.push(sub_expr);
ControlFlow::Continue(Descend::No)
} }
Some(()) });
res
} }
/// Peels binary operators such as [`BinOpKind::Add`], where the result depends on:
/// - all the expressions being positive, or
/// - all the expressions being negative.
/// Ignores overflow.
///
/// Expressions using other operators are preserved, so we can try to evaluate them later.
fn exprs_with_add_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
let mut res = vec![]; let mut res = vec![];
collect_operands(expr, &mut res)?;
Some(res) for_each_expr(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) {
// For binary operators where both sides contribute to the sign of the result,
// collect all their operands, recursively. This ignores overflow.
ControlFlow::Continue(Descend::Yes)
} else {
// The sign of the result of other binary operators depends on the values of the operands,
// so try to evaluate the expression.
res.push(sub_expr);
ControlFlow::Continue(Descend::No)
}
} else {
// For other expressions, including unary operators and constants, try to evaluate the expression.
res.push(sub_expr);
ControlFlow::Continue(Descend::No)
}
});
res
} }

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_no_std_crate;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::{expr_use_ctxt, is_no_std_crate, ExprUseNode};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Expr, Mutability, Ty, TyKind}; use rustc_hir::{Expr, Mutability, Ty, TyKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
@ -9,7 +9,12 @@ use rustc_middle::ty::{self, TypeAndMut};
use super::REF_AS_PTR; use super::REF_AS_PTR;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_to_hir_ty: &Ty<'_>) { pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
cast_expr: &'tcx Expr<'_>,
cast_to_hir_ty: &Ty<'_>,
) {
let (cast_from, cast_to) = ( let (cast_from, cast_to) = (
cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(cast_expr),
cx.typeck_results().expr_ty(expr), cx.typeck_results().expr_ty(expr),
@ -17,6 +22,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
if matches!(cast_from.kind(), ty::Ref(..)) if matches!(cast_from.kind(), ty::Ref(..))
&& let ty::RawPtr(TypeAndMut { mutbl: to_mutbl, .. }) = cast_to.kind() && let ty::RawPtr(TypeAndMut { mutbl: to_mutbl, .. }) = cast_to.kind()
&& let Some(use_cx) = expr_use_ctxt(cx, expr)
// TODO: only block the lint if `cast_expr` is a temporary
&& !matches!(use_cx.node, ExprUseNode::Local(_) | ExprUseNode::ConstStatic(_))
{ {
let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" }; let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" };
let fn_name = match to_mutbl { let fn_name = match to_mutbl {

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal::NumericLiteral; use clippy_utils::numeric_literal::NumericLiteral;
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use clippy_utils::visitors::{for_each_expr, Visitable}; use clippy_utils::visitors::{for_each_expr, Visitable};
use clippy_utils::{get_parent_expr, get_parent_node, is_hir_ty_cfg_dependant, is_ty_alias, path_to_local}; 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_ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
@ -264,8 +264,7 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx
} }
// Local usage // Local usage
} else if let Res::Local(hir_id) = res } else if let Res::Local(hir_id) = res
&& let Some(parent) = get_parent_node(cx.tcx, hir_id) && let Node::Local(l) = cx.tcx.parent_hir_node(hir_id)
&& let Node::Local(l) = parent
{ {
if let Some(e) = l.init if let Some(e) = l.init
&& is_cast_from_ty_alias(cx, e, cast_from) && is_cast_from_ty_alias(cx, e, cast_from)

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::visitors::for_each_expr_with_closures; use clippy_utils::visitors::for_each_expr_with_closures;
use clippy_utils::{get_enclosing_block, get_parent_node, path_to_local_id}; use clippy_utils::{get_enclosing_block, path_to_local_id};
use core::ops::ControlFlow; use core::ops::ControlFlow;
use rustc_hir::{Block, ExprKind, HirId, LangItem, Local, Node, PatKind}; use rustc_hir::{Block, ExprKind, HirId, LangItem, Local, Node, PatKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -94,7 +94,7 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc
// `id` appearing in the left-hand side of an assignment is not a read access: // `id` appearing in the left-hand side of an assignment is not a read access:
// //
// id = ...; // Not reading `id`. // id = ...; // Not reading `id`.
if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id) if let Node::Expr(parent) = cx.tcx.parent_hir_node(expr.hir_id)
&& let ExprKind::Assign(lhs, ..) = parent.kind && let ExprKind::Assign(lhs, ..) = parent.kind
&& path_to_local_id(lhs, id) && path_to_local_id(lhs, id)
{ {
@ -108,7 +108,7 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc
// Only assuming this for "official" methods defined on the type. For methods defined in extension // Only assuming this for "official" methods defined on the type. For methods defined in extension
// traits (identified as local, based on the orphan rule), pessimistically assume that they might // traits (identified as local, based on the orphan rule), pessimistically assume that they might
// have side effects, so consider them a read. // have side effects, so consider them a read.
if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id) if let Node::Expr(parent) = cx.tcx.parent_hir_node(expr.hir_id)
&& let ExprKind::MethodCall(_, receiver, _, _) = parent.kind && let ExprKind::MethodCall(_, receiver, _, _) = parent.kind
&& path_to_local_id(receiver, id) && path_to_local_id(receiver, id)
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id) && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
@ -117,7 +117,7 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc
// The method call is a statement, so the return value is not used. That's not a read access: // The method call is a statement, so the return value is not used. That's not a read access:
// //
// id.foo(args); // id.foo(args);
if let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id) { if let Node::Stmt(..) = cx.tcx.parent_hir_node(parent.hir_id) {
return ControlFlow::Continue(()); return ControlFlow::Continue(());
} }

View file

@ -51,6 +51,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON_INFO, crate::attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON_INFO,
crate::attrs::BLANKET_CLIPPY_RESTRICTION_LINTS_INFO, crate::attrs::BLANKET_CLIPPY_RESTRICTION_LINTS_INFO,
crate::attrs::DEPRECATED_CFG_ATTR_INFO, crate::attrs::DEPRECATED_CFG_ATTR_INFO,
crate::attrs::DEPRECATED_CLIPPY_CFG_ATTR_INFO,
crate::attrs::DEPRECATED_SEMVER_INFO, crate::attrs::DEPRECATED_SEMVER_INFO,
crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO, crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO, crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
@ -59,6 +60,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::attrs::MISMATCHED_TARGET_OS_INFO, crate::attrs::MISMATCHED_TARGET_OS_INFO,
crate::attrs::NON_MINIMAL_CFG_INFO, crate::attrs::NON_MINIMAL_CFG_INFO,
crate::attrs::SHOULD_PANIC_WITHOUT_EXPECT_INFO, crate::attrs::SHOULD_PANIC_WITHOUT_EXPECT_INFO,
crate::attrs::UNNECESSARY_CLIPPY_CFG_INFO,
crate::attrs::USELESS_ATTRIBUTE_INFO, crate::attrs::USELESS_ATTRIBUTE_INFO,
crate::await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE_INFO, crate::await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE_INFO,
crate::await_holding_invalid::AWAIT_HOLDING_LOCK_INFO, crate::await_holding_invalid::AWAIT_HOLDING_LOCK_INFO,
@ -137,6 +139,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::disallowed_types::DISALLOWED_TYPES_INFO, crate::disallowed_types::DISALLOWED_TYPES_INFO,
crate::doc::DOC_LINK_WITH_QUOTES_INFO, crate::doc::DOC_LINK_WITH_QUOTES_INFO,
crate::doc::DOC_MARKDOWN_INFO, crate::doc::DOC_MARKDOWN_INFO,
crate::doc::EMPTY_DOCS_INFO,
crate::doc::MISSING_ERRORS_DOC_INFO, crate::doc::MISSING_ERRORS_DOC_INFO,
crate::doc::MISSING_PANICS_DOC_INFO, crate::doc::MISSING_PANICS_DOC_INFO,
crate::doc::MISSING_SAFETY_DOC_INFO, crate::doc::MISSING_SAFETY_DOC_INFO,
@ -453,6 +456,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::UNNECESSARY_FILTER_MAP_INFO, crate::methods::UNNECESSARY_FILTER_MAP_INFO,
crate::methods::UNNECESSARY_FIND_MAP_INFO, crate::methods::UNNECESSARY_FIND_MAP_INFO,
crate::methods::UNNECESSARY_FOLD_INFO, crate::methods::UNNECESSARY_FOLD_INFO,
crate::methods::UNNECESSARY_GET_THEN_CHECK_INFO,
crate::methods::UNNECESSARY_JOIN_INFO, crate::methods::UNNECESSARY_JOIN_INFO,
crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO, crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO, crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
@ -497,6 +501,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::module_style::MOD_MODULE_FILES_INFO, crate::module_style::MOD_MODULE_FILES_INFO,
crate::module_style::SELF_NAMED_MODULE_FILES_INFO, crate::module_style::SELF_NAMED_MODULE_FILES_INFO,
crate::multi_assignments::MULTI_ASSIGNMENTS_INFO, crate::multi_assignments::MULTI_ASSIGNMENTS_INFO,
crate::multiple_bound_locations::MULTIPLE_BOUND_LOCATIONS_INFO,
crate::multiple_unsafe_ops_per_block::MULTIPLE_UNSAFE_OPS_PER_BLOCK_INFO, crate::multiple_unsafe_ops_per_block::MULTIPLE_UNSAFE_OPS_PER_BLOCK_INFO,
crate::mut_key::MUTABLE_KEY_TYPE_INFO, crate::mut_key::MUTABLE_KEY_TYPE_INFO,
crate::mut_mut::MUT_MUT_INFO, crate::mut_mut::MUT_MUT_INFO,

View file

@ -2,9 +2,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs}; use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs};
use clippy_utils::{ use clippy_utils::{expr_use_ctxt, get_parent_expr, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode};
expr_use_ctxt, get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode,
};
use core::mem; use core::mem;
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::fx::FxIndexMap;
@ -1008,8 +1006,8 @@ fn report<'tcx>(
data.first_expr.span, data.first_expr.span,
state.msg, state.msg,
|diag| { |diag| {
let (precedence, calls_field) = match get_parent_node(cx.tcx, data.first_expr.hir_id) { let (precedence, calls_field) = match cx.tcx.parent_hir_node(data.first_expr.hir_id) {
Some(Node::Expr(e)) => match e.kind { Node::Expr(e) => match e.kind {
ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => (0, false), ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => (0, false),
ExprKind::Call(..) => (PREC_POSTFIX, matches!(expr.kind, ExprKind::Field(..))), ExprKind::Call(..) => (PREC_POSTFIX, matches!(expr.kind, ExprKind::Field(..))),
_ => (e.precedence().order(), false), _ => (e.precedence().order(), false),

View file

@ -1,13 +1,16 @@
use clippy_config::types::DisallowedPath; use clippy_config::types::DisallowedPath;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::macros::macro_backtrace; use clippy_utils::macros::macro_backtrace;
use rustc_ast::Attribute; use rustc_ast::Attribute;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_errors::DiagnosticBuilder;
use rustc_hir::def_id::DefIdMap; use rustc_hir::def_id::DefIdMap;
use rustc_hir::{Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, Pat, Path, Stmt, TraitItem, Ty}; use rustc_hir::{
Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, ItemKind, OwnerId, Pat, Path, Stmt, TraitItem, Ty,
};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::{ExpnId, Span}; use rustc_span::{ExpnId, MacroKind, Span};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -57,6 +60,10 @@ pub struct DisallowedMacros {
conf_disallowed: Vec<DisallowedPath>, conf_disallowed: Vec<DisallowedPath>,
disallowed: DefIdMap<usize>, disallowed: DefIdMap<usize>,
seen: FxHashSet<ExpnId>, seen: FxHashSet<ExpnId>,
// Track the most recently seen node that can have a `derive` attribute.
// Needed to use the correct lint level.
derive_src: Option<OwnerId>,
} }
impl DisallowedMacros { impl DisallowedMacros {
@ -65,10 +72,11 @@ impl DisallowedMacros {
conf_disallowed, conf_disallowed,
disallowed: DefIdMap::default(), disallowed: DefIdMap::default(),
seen: FxHashSet::default(), seen: FxHashSet::default(),
derive_src: None,
} }
} }
fn check(&mut self, cx: &LateContext<'_>, span: Span) { fn check(&mut self, cx: &LateContext<'_>, span: Span, derive_src: Option<OwnerId>) {
if self.conf_disallowed.is_empty() { if self.conf_disallowed.is_empty() {
return; return;
} }
@ -80,18 +88,26 @@ impl DisallowedMacros {
if let Some(&index) = self.disallowed.get(&mac.def_id) { if let Some(&index) = self.disallowed.get(&mac.def_id) {
let conf = &self.conf_disallowed[index]; let conf = &self.conf_disallowed[index];
let msg = format!("use of a disallowed macro `{}`", conf.path());
span_lint_and_then( let add_note = |diag: &mut DiagnosticBuilder<'_, _>| {
cx,
DISALLOWED_MACROS,
mac.span,
&format!("use of a disallowed macro `{}`", conf.path()),
|diag| {
if let Some(reason) = conf.reason() { if let Some(reason) = conf.reason() {
diag.note(reason); diag.note(reason);
} }
}, };
if matches!(mac.kind, MacroKind::Derive)
&& let Some(derive_src) = derive_src
{
span_lint_hir_and_then(
cx,
DISALLOWED_MACROS,
cx.tcx.local_def_id_to_hir_id(derive_src.def_id),
mac.span,
&msg,
add_note,
); );
} else {
span_lint_and_then(cx, DISALLOWED_MACROS, mac.span, &msg, add_note);
}
} }
} }
} }
@ -110,49 +126,57 @@ impl LateLintPass<'_> for DisallowedMacros {
} }
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
self.check(cx, expr.span); self.check(cx, expr.span, None);
// `$t + $t` can have the context of $t, check also the span of the binary operator // `$t + $t` can have the context of $t, check also the span of the binary operator
if let ExprKind::Binary(op, ..) = expr.kind { if let ExprKind::Binary(op, ..) = expr.kind {
self.check(cx, op.span); self.check(cx, op.span, None);
} }
} }
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) { fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
self.check(cx, stmt.span); self.check(cx, stmt.span, None);
} }
fn check_ty(&mut self, cx: &LateContext<'_>, ty: &Ty<'_>) { fn check_ty(&mut self, cx: &LateContext<'_>, ty: &Ty<'_>) {
self.check(cx, ty.span); self.check(cx, ty.span, None);
} }
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) { fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
self.check(cx, pat.span); self.check(cx, pat.span, None);
} }
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
self.check(cx, item.span); self.check(cx, item.span, self.derive_src);
self.check(cx, item.vis_span); self.check(cx, item.vis_span, None);
if matches!(
item.kind,
ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..)
) && macro_backtrace(item.span).all(|m| !matches!(m.kind, MacroKind::Derive))
{
self.derive_src = Some(item.owner_id);
}
} }
fn check_foreign_item(&mut self, cx: &LateContext<'_>, item: &ForeignItem<'_>) { fn check_foreign_item(&mut self, cx: &LateContext<'_>, item: &ForeignItem<'_>) {
self.check(cx, item.span); self.check(cx, item.span, None);
self.check(cx, item.vis_span); self.check(cx, item.vis_span, None);
} }
fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) { fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) {
self.check(cx, item.span); self.check(cx, item.span, None);
self.check(cx, item.vis_span); self.check(cx, item.vis_span, None);
} }
fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) {
self.check(cx, item.span); self.check(cx, item.span, None);
} }
fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) { fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) {
self.check(cx, path.span); self.check(cx, path.span, None);
} }
fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) { fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) {
self.check(cx, attr.span); self.check(cx, attr.span, self.derive_src);
} }
} }

View file

@ -19,7 +19,8 @@ use rustc_middle::hir::nested_filter;
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_middle::ty; use rustc_middle::ty;
use rustc_resolve::rustdoc::{ use rustc_resolve::rustdoc::{
add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range, DocFragment, add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range, span_of_fragments,
DocFragment,
}; };
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::edition::Edition; use rustc_span::edition::Edition;
@ -338,6 +339,30 @@ declare_clippy_lint! {
"suspicious usage of (outer) doc comments" "suspicious usage of (outer) doc comments"
} }
declare_clippy_lint! {
/// ### What it does
/// Detects documentation that is empty.
/// ### Why is this bad?
/// Empty docs clutter code without adding value, reducing readability and maintainability.
/// ### Example
/// ```no_run
/// ///
/// fn returns_true() -> bool {
/// true
/// }
/// ```
/// Use instead:
/// ```no_run
/// fn returns_true() -> bool {
/// true
/// }
/// ```
#[clippy::version = "1.78.0"]
pub EMPTY_DOCS,
suspicious,
"docstrings exist but documentation is empty"
}
#[derive(Clone)] #[derive(Clone)]
pub struct Documentation { pub struct Documentation {
valid_idents: FxHashSet<String>, valid_idents: FxHashSet<String>,
@ -364,7 +389,8 @@ impl_lint_pass!(Documentation => [
NEEDLESS_DOCTEST_MAIN, NEEDLESS_DOCTEST_MAIN,
TEST_ATTR_IN_DOCTEST, TEST_ATTR_IN_DOCTEST,
UNNECESSARY_SAFETY_DOC, UNNECESSARY_SAFETY_DOC,
SUSPICIOUS_DOC_COMMENTS SUSPICIOUS_DOC_COMMENTS,
EMPTY_DOCS,
]); ]);
impl<'tcx> LateLintPass<'tcx> for Documentation { impl<'tcx> LateLintPass<'tcx> for Documentation {
@ -373,11 +399,22 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
check_attrs(cx, &self.valid_idents, attrs); check_attrs(cx, &self.valid_idents, attrs);
} }
fn check_variant(&mut self, cx: &LateContext<'tcx>, variant: &'tcx hir::Variant<'tcx>) {
let attrs = cx.tcx.hir().attrs(variant.hir_id);
check_attrs(cx, &self.valid_idents, attrs);
}
fn check_field_def(&mut self, cx: &LateContext<'tcx>, variant: &'tcx hir::FieldDef<'tcx>) {
let attrs = cx.tcx.hir().attrs(variant.hir_id);
check_attrs(cx, &self.valid_idents, attrs);
}
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
let attrs = cx.tcx.hir().attrs(item.hir_id()); let attrs = cx.tcx.hir().attrs(item.hir_id());
let Some(headers) = check_attrs(cx, &self.valid_idents, attrs) else { let Some(headers) = check_attrs(cx, &self.valid_idents, attrs) else {
return; return;
}; };
match item.kind { match item.kind {
hir::ItemKind::Fn(ref sig, _, body_id) => { hir::ItemKind::Fn(ref sig, _, body_id) => {
if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) { if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) {
@ -502,13 +539,23 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
suspicious_doc_comments::check(cx, attrs); suspicious_doc_comments::check(cx, attrs);
let (fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true); let (fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true);
let mut doc = String::new(); let mut doc = fragments.iter().fold(String::new(), |mut acc, fragment| {
for fragment in &fragments { add_doc_fragment(&mut acc, fragment);
add_doc_fragment(&mut doc, fragment); acc
} });
doc.pop(); doc.pop();
if doc.is_empty() { if doc.trim().is_empty() {
if let Some(span) = span_of_fragments(&fragments) {
span_lint_and_help(
cx,
EMPTY_DOCS,
span,
"empty doc comment",
None,
"consider removing or filling it",
);
}
return Some(DocHeaders::default()); return Some(DocHeaders::default());
} }

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::is_must_use_func_call;
use clippy_utils::ty::{is_copy, is_must_use_ty, is_type_lang_item}; use clippy_utils::ty::{is_copy, is_must_use_ty, is_type_lang_item};
use clippy_utils::{get_parent_node, is_must_use_func_call};
use rustc_hir::{Arm, Expr, ExprKind, LangItem, Node}; use rustc_hir::{Arm, Expr, ExprKind, LangItem, Node};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
@ -144,8 +144,7 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
// } // }
fn is_single_call_in_arm<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'_>, drop_expr: &'tcx Expr<'_>) -> bool { fn is_single_call_in_arm<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'_>, drop_expr: &'tcx Expr<'_>) -> bool {
if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) { if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) {
let parent_node = get_parent_node(cx.tcx, drop_expr.hir_id); if let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id) {
if let Some(Node::Arm(Arm { body, .. })) = &parent_node {
return body.hir_id == drop_expr.hir_id; return body.hir_id == drop_expr.hir_id;
} }
} }

View file

@ -4,7 +4,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::is_diag_trait_item; use clippy_utils::is_diag_trait_item;
use clippy_utils::macros::{ use clippy_utils::macros::{
find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro,
is_format_macro, is_panic, root_macro_call, root_macro_call_first_node, FormatParamUsage, is_format_macro, is_panic, root_macro_call, root_macro_call_first_node, FormatParamUsage, MacroCall,
}; };
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_lang_item}; use clippy_utils::ty::{implements_trait, is_type_lang_item};
@ -20,7 +20,6 @@ use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::adjustment::{Adjust, Adjustment}; use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::Ty; use rustc_middle::ty::Ty;
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::def_id::DefId;
use rustc_span::edition::Edition::Edition2021; use rustc_span::edition::Edition::Edition2021;
use rustc_span::{sym, Span, Symbol}; use rustc_span::{sym, Span, Symbol};
@ -189,32 +188,18 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
&& is_format_macro(cx, macro_call.def_id) && is_format_macro(cx, macro_call.def_id)
&& let Some(format_args) = find_format_args(cx, expr, macro_call.expn) && let Some(format_args) = find_format_args(cx, expr, macro_call.expn)
{ {
for piece in &format_args.template { let linter = FormatArgsExpr {
if let FormatArgsPiece::Placeholder(placeholder) = piece cx,
&& let Ok(index) = placeholder.argument.index expr,
&& let Some(arg) = format_args.arguments.all_args().get(index) macro_call: &macro_call,
{ format_args: &format_args,
let arg_expr = find_format_arg_expr(expr, arg); ignore_mixed: self.ignore_mixed,
};
check_unused_format_specifier(cx, placeholder, arg_expr); linter.check_templates();
if placeholder.format_trait != FormatTrait::Display
|| placeholder.format_options != FormatOptions::default()
|| is_aliased(&format_args, index)
{
continue;
}
if let Ok(arg_hir_expr) = arg_expr {
let name = cx.tcx.item_name(macro_call.def_id);
check_format_in_format_args(cx, macro_call.span, name, arg_hir_expr);
check_to_string_in_format_args(cx, name, arg_hir_expr);
}
}
}
if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) { if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) {
check_uninlined_args(cx, &format_args, macro_call.span, macro_call.def_id, self.ignore_mixed); linter.check_uninlined_args();
} }
} }
} }
@ -222,15 +207,47 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
extract_msrv_attr!(LateContext); extract_msrv_attr!(LateContext);
} }
struct FormatArgsExpr<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
macro_call: &'a MacroCall,
format_args: &'a rustc_ast::FormatArgs,
ignore_mixed: bool,
}
impl<'a, 'tcx> FormatArgsExpr<'a, 'tcx> {
fn check_templates(&self) {
for piece in &self.format_args.template {
if let FormatArgsPiece::Placeholder(placeholder) = piece
&& let Ok(index) = placeholder.argument.index
&& let Some(arg) = self.format_args.arguments.all_args().get(index)
{
let arg_expr = find_format_arg_expr(self.expr, arg);
self.check_unused_format_specifier(placeholder, arg_expr);
if let Ok(arg_expr) = arg_expr
&& placeholder.format_trait == FormatTrait::Display
&& placeholder.format_options == FormatOptions::default()
&& !self.is_aliased(index)
{
let name = self.cx.tcx.item_name(self.macro_call.def_id);
self.check_format_in_format_args(name, arg_expr);
self.check_to_string_in_format_args(name, arg_expr);
}
}
}
}
fn check_unused_format_specifier( fn check_unused_format_specifier(
cx: &LateContext<'_>, &self,
placeholder: &FormatPlaceholder, placeholder: &FormatPlaceholder,
arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>, arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>,
) { ) {
let ty_or_ast_expr = arg_expr.map(|expr| cx.typeck_results().expr_ty(expr).peel_refs()); let ty_or_ast_expr = arg_expr.map(|expr| self.cx.typeck_results().expr_ty(expr).peel_refs());
let is_format_args = match ty_or_ast_expr { let is_format_args = match ty_or_ast_expr {
Ok(ty) => is_type_lang_item(cx, ty, LangItem::FormatArguments), Ok(ty) => is_type_lang_item(self.cx, ty, LangItem::FormatArguments),
Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)), Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)),
}; };
@ -246,7 +263,7 @@ fn check_unused_format_specifier(
&& *options != FormatOptions::default() && *options != FormatOptions::default()
{ {
span_lint_and_then( span_lint_and_then(
cx, self.cx,
UNUSED_FORMAT_SPECS, UNUSED_FORMAT_SPECS,
placeholder_span, placeholder_span,
"format specifiers have no effect on `format_args!()`", "format specifiers have no effect on `format_args!()`",
@ -255,10 +272,10 @@ fn check_unused_format_specifier(
let message = format!("for the {spec} to apply consider using `format!()`"); let message = format!("for the {spec} to apply consider using `format!()`");
if let Some(mac_call) = root_macro_call(arg_span) if let Some(mac_call) = root_macro_call(arg_span)
&& cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id) && self.cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id)
{ {
diag.span_suggestion( diag.span_suggestion(
cx.sess().source_map().span_until_char(mac_call.span, '!'), self.cx.sess().source_map().span_until_char(mac_call.span, '!'),
message, message,
"format", "format",
Applicability::MaybeIncorrect, Applicability::MaybeIncorrect,
@ -289,17 +306,13 @@ fn check_unused_format_specifier(
} }
} }
fn check_uninlined_args( fn check_uninlined_args(&self) {
cx: &LateContext<'_>, if self.format_args.span.from_expansion() {
args: &rustc_ast::FormatArgs,
call_site: Span,
def_id: DefId,
ignore_mixed: bool,
) {
if args.span.from_expansion() {
return; return;
} }
if call_site.edition() < Edition2021 && (is_panic(cx, def_id) || is_assert_macro(cx, def_id)) { if self.macro_call.span.edition() < Edition2021
&& (is_panic(self.cx, self.macro_call.def_id) || is_assert_macro(self.cx, self.macro_call.def_id))
{
// panic!, assert!, and debug_assert! before 2021 edition considers a single string argument as // panic!, assert!, and debug_assert! before 2021 edition considers a single string argument as
// non-format // non-format
return; return;
@ -311,8 +324,8 @@ fn check_uninlined_args(
// we cannot remove any other arguments in the format string, // we cannot remove any other arguments in the format string,
// because the index numbers might be wrong after inlining. // because the index numbers might be wrong after inlining.
// Example of an un-inlinable format: print!("{}{1}", foo, 2) // Example of an un-inlinable format: print!("{}{1}", foo, 2)
for (pos, usage) in format_arg_positions(args) { for (pos, usage) in self.format_arg_positions() {
if !check_one_arg(args, pos, usage, &mut fixes, ignore_mixed) { if !self.check_one_arg(pos, usage, &mut fixes) {
return; return;
} }
} }
@ -323,16 +336,18 @@ fn check_uninlined_args(
// multiline span display suggestion is sometimes broken: https://github.com/rust-lang/rust/pull/102729#discussion_r988704308 // multiline span display suggestion is sometimes broken: https://github.com/rust-lang/rust/pull/102729#discussion_r988704308
// in those cases, make the code suggestion hidden // in those cases, make the code suggestion hidden
let multiline_fix = fixes.iter().any(|(span, _)| cx.sess().source_map().is_multiline(*span)); let multiline_fix = fixes
.iter()
.any(|(span, _)| self.cx.sess().source_map().is_multiline(*span));
// Suggest removing each argument only once, for example in `format!("{0} {0}", arg)`. // Suggest removing each argument only once, for example in `format!("{0} {0}", arg)`.
fixes.sort_unstable_by_key(|(span, _)| *span); fixes.sort_unstable_by_key(|(span, _)| *span);
fixes.dedup_by_key(|(span, _)| *span); fixes.dedup_by_key(|(span, _)| *span);
span_lint_and_then( span_lint_and_then(
cx, self.cx,
UNINLINED_FORMAT_ARGS, UNINLINED_FORMAT_ARGS,
call_site, self.macro_call.span,
"variables can be used directly in the `format!` string", "variables can be used directly in the `format!` string",
|diag| { |diag| {
diag.multipart_suggestion_with_style( diag.multipart_suggestion_with_style(
@ -345,21 +360,15 @@ fn check_uninlined_args(
); );
} }
fn check_one_arg( fn check_one_arg(&self, pos: &FormatArgPosition, usage: FormatParamUsage, fixes: &mut Vec<(Span, String)>) -> bool {
args: &rustc_ast::FormatArgs,
pos: &FormatArgPosition,
usage: FormatParamUsage,
fixes: &mut Vec<(Span, String)>,
ignore_mixed: bool,
) -> bool {
let index = pos.index.unwrap(); let index = pos.index.unwrap();
let arg = &args.arguments.all_args()[index]; let arg = &self.format_args.arguments.all_args()[index];
if !matches!(arg.kind, FormatArgumentKind::Captured(_)) if !matches!(arg.kind, FormatArgumentKind::Captured(_))
&& let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind && let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind
&& let [segment] = path.segments.as_slice() && let [segment] = path.segments.as_slice()
&& segment.args.is_none() && segment.args.is_none()
&& let Some(arg_span) = format_arg_removal_span(args, index) && let Some(arg_span) = format_arg_removal_span(self.format_args, index)
&& let Some(pos_span) = pos.span && let Some(pos_span) = pos.span
{ {
let replacement = match usage { let replacement = match usage {
@ -375,23 +384,23 @@ fn check_one_arg(
// * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)` // * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)`
// * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already // * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already
pos.kind != FormatArgPositionKind::Number pos.kind != FormatArgPositionKind::Number
&& (!ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_))) && (!self.ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_)))
} }
} }
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &Expr<'_>) { fn check_format_in_format_args(&self, name: Symbol, arg: &Expr<'_>) {
let expn_data = arg.span.ctxt().outer_expn_data(); let expn_data = arg.span.ctxt().outer_expn_data();
if expn_data.call_site.from_expansion() { if expn_data.call_site.from_expansion() {
return; return;
} }
let Some(mac_id) = expn_data.macro_def_id else { return }; let Some(mac_id) = expn_data.macro_def_id else { return };
if !cx.tcx.is_diagnostic_item(sym::format_macro, mac_id) { if !self.cx.tcx.is_diagnostic_item(sym::format_macro, mac_id) {
return; return;
} }
span_lint_and_then( span_lint_and_then(
cx, self.cx,
FORMAT_IN_FORMAT_ARGS, FORMAT_IN_FORMAT_ARGS,
call_site, self.macro_call.span,
&format!("`format!` in `{name}!` args"), &format!("`format!` in `{name}!` args"),
|diag| { |diag| {
diag.help(format!( diag.help(format!(
@ -402,7 +411,8 @@ fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symb
); );
} }
fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Expr<'_>) { fn check_to_string_in_format_args(&self, name: Symbol, value: &Expr<'_>) {
let cx = self.cx;
if !value.span.from_expansion() if !value.span.from_expansion()
&& let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind && let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id) && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
@ -444,10 +454,8 @@ fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Ex
} }
} }
fn format_arg_positions( fn format_arg_positions(&self) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> {
format_args: &rustc_ast::FormatArgs, self.format_args.template.iter().flat_map(|piece| match piece {
) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> {
format_args.template.iter().flat_map(|piece| match piece {
FormatArgsPiece::Placeholder(placeholder) => { FormatArgsPiece::Placeholder(placeholder) => {
let mut positions = ArrayVec::<_, 3>::new(); let mut positions = ArrayVec::<_, 3>::new();
@ -466,12 +474,13 @@ fn format_arg_positions(
} }
/// Returns true if the format argument at `index` is referred to by multiple format params /// Returns true if the format argument at `index` is referred to by multiple format params
fn is_aliased(format_args: &rustc_ast::FormatArgs, index: usize) -> bool { fn is_aliased(&self, index: usize) -> bool {
format_arg_positions(format_args) self.format_arg_positions()
.filter(|(position, _)| position.index == Ok(index)) .filter(|(position, _)| position.index == Ok(index))
.at_most_one() .at_most_one()
.is_err() .is_err()
} }
}
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>) fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
where where

View file

@ -7,7 +7,7 @@ use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::symbol::kw; use rustc_span::symbol::kw;
use rustc_span::{sym, Span, Symbol}; use rustc_span::{sym, Symbol};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -126,48 +126,56 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl {
} }
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let Some(format_trait_impl) = self.format_trait_impl else { if let Some(format_trait_impl) = self.format_trait_impl {
return; let linter = FormatImplExpr {
cx,
expr,
format_trait_impl,
}; };
linter.check_to_string_in_display();
if format_trait_impl.name == sym::Display { linter.check_self_in_format_args();
check_to_string_in_display(cx, expr); linter.check_print_in_format_impl();
} }
check_self_in_format_args(cx, expr, format_trait_impl);
check_print_in_format_impl(cx, expr, format_trait_impl);
} }
} }
fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) { struct FormatImplExpr<'a, 'tcx> {
if let ExprKind::MethodCall(path, self_arg, ..) = expr.kind cx: &'a LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
format_trait_impl: FormatTraitNames,
}
impl<'a, 'tcx> FormatImplExpr<'a, 'tcx> {
fn check_to_string_in_display(&self) {
if self.format_trait_impl.name == sym::Display
&& let ExprKind::MethodCall(path, self_arg, ..) = self.expr.kind
// Get the hir_id of the object we are calling the method on // Get the hir_id of the object we are calling the method on
// Is the method to_string() ? // Is the method to_string() ?
&& path.ident.name == sym::to_string && path.ident.name == sym::to_string
// Is the method a part of the ToString trait? (i.e. not to_string() implemented // Is the method a part of the ToString trait? (i.e. not to_string() implemented
// separately) // separately)
&& let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && let Some(expr_def_id) = self.cx.typeck_results().type_dependent_def_id(self.expr.hir_id)
&& is_diag_trait_item(cx, expr_def_id, sym::ToString) && is_diag_trait_item(self.cx, expr_def_id, sym::ToString)
// Is the method is called on self // Is the method is called on self
&& let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind && let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind
&& let [segment] = path.segments && let [segment] = path.segments
&& segment.ident.name == kw::SelfLower && segment.ident.name == kw::SelfLower
{ {
span_lint( span_lint(
cx, self.cx,
RECURSIVE_FORMAT_IMPL, RECURSIVE_FORMAT_IMPL,
expr.span, self.expr.span,
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion", "using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
); );
} }
} }
fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTraitNames) { fn check_self_in_format_args(&self) {
// Check each arg in format calls - do we ever use Display on self (directly or via deref)? // Check each arg in format calls - do we ever use Display on self (directly or via deref)?
if let Some(outer_macro) = root_macro_call_first_node(cx, expr) if let Some(outer_macro) = root_macro_call_first_node(self.cx, self.expr)
&& let macro_def_id = outer_macro.def_id && let macro_def_id = outer_macro.def_id
&& is_format_macro(cx, macro_def_id) && is_format_macro(self.cx, macro_def_id)
&& let Some(format_args) = find_format_args(cx, expr, outer_macro.expn) && let Some(format_args) = find_format_args(self.cx, self.expr, outer_macro.expn)
{ {
for piece in &format_args.template { for piece in &format_args.template {
if let FormatArgsPiece::Placeholder(placeholder) = piece if let FormatArgsPiece::Placeholder(placeholder) = piece
@ -182,38 +190,38 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
FormatTrait::LowerHex => sym!(LowerHex), FormatTrait::LowerHex => sym!(LowerHex),
FormatTrait::UpperHex => sym!(UpperHex), FormatTrait::UpperHex => sym!(UpperHex),
} }
&& trait_name == impl_trait.name && trait_name == self.format_trait_impl.name
&& let Ok(index) = placeholder.argument.index && let Ok(index) = placeholder.argument.index
&& let Some(arg) = format_args.arguments.all_args().get(index) && let Some(arg) = format_args.arguments.all_args().get(index)
&& let Ok(arg_expr) = find_format_arg_expr(expr, arg) && let Ok(arg_expr) = find_format_arg_expr(self.expr, arg)
{ {
check_format_arg_self(cx, expr.span, arg_expr, impl_trait); self.check_format_arg_self(arg_expr);
} }
} }
} }
} }
fn check_format_arg_self(cx: &LateContext<'_>, span: Span, arg: &Expr<'_>, impl_trait: FormatTraitNames) { fn check_format_arg_self(&self, arg: &Expr<'_>) {
// Handle multiple dereferencing of references e.g. &&self // Handle multiple dereferencing of references e.g. &&self
// Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl) // Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl)
// Since the argument to fmt is itself a reference: &self // Since the argument to fmt is itself a reference: &self
let reference = peel_ref_operators(cx, arg); let reference = peel_ref_operators(self.cx, arg);
let map = cx.tcx.hir(); let map = self.cx.tcx.hir();
// Is the reference self? // Is the reference self?
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) { if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
let FormatTraitNames { name, .. } = impl_trait; let FormatTraitNames { name, .. } = self.format_trait_impl;
span_lint( span_lint(
cx, self.cx,
RECURSIVE_FORMAT_IMPL, RECURSIVE_FORMAT_IMPL,
span, self.expr.span,
&format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"), &format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
); );
} }
} }
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTraitNames) { fn check_print_in_format_impl(&self) {
if let Some(macro_call) = root_macro_call_first_node(cx, expr) if let Some(macro_call) = root_macro_call_first_node(self.cx, self.expr)
&& let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id) && let Some(name) = self.cx.tcx.get_diagnostic_name(macro_call.def_id)
{ {
let replacement = match name { let replacement = match name {
sym::print_macro | sym::eprint_macro => "write", sym::print_macro | sym::eprint_macro => "write",
@ -224,12 +232,12 @@ fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait:
let name = name.as_str().strip_suffix("_macro").unwrap(); let name = name.as_str().strip_suffix("_macro").unwrap();
span_lint_and_sugg( span_lint_and_sugg(
cx, self.cx,
PRINT_IN_FORMAT_IMPL, PRINT_IN_FORMAT_IMPL,
macro_call.span, macro_call.span,
&format!("use of `{name}!` in `{}` impl", impl_trait.name), &format!("use of `{name}!` in `{}` impl", self.format_trait_impl.name),
"replace with", "replace with",
if let Some(formatter_name) = impl_trait.formatter_name { if let Some(formatter_name) = self.format_trait_impl.formatter_name {
format!("{replacement}!({formatter_name}, ..)") format!("{replacement}!({formatter_name}, ..)")
} else { } else {
format!("{replacement}!(..)") format!("{replacement}!(..)")
@ -238,6 +246,7 @@ fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait:
); );
} }
} }
}
fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTraitNames> { fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTraitNames> {
if impl_item.ident.name == sym::fmt if impl_item.ident.name == sym::fmt

View file

@ -1,11 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use rustc_errors::{Applicability, SuggestionStyle}; use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{ use rustc_hir::{
Body, FnDecl, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, ItemKind, TraitBoundModifier, TraitItem, GenericArg, GenericBound, GenericBounds, ItemKind, PredicateOrigin, TraitBoundModifier, TyKind, TypeBinding,
TraitItemKind, TyKind, WherePredicate,
}; };
use rustc_hir_analysis::hir_ty_to_ty; use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -50,20 +49,17 @@ declare_clippy_lint! {
} }
declare_lint_pass!(ImpliedBoundsInImpls => [IMPLIED_BOUNDS_IN_IMPLS]); declare_lint_pass!(ImpliedBoundsInImpls => [IMPLIED_BOUNDS_IN_IMPLS]);
#[allow(clippy::too_many_arguments)]
fn emit_lint( fn emit_lint(
cx: &LateContext<'_>, cx: &LateContext<'_>,
poly_trait: &rustc_hir::PolyTraitRef<'_>, poly_trait: &rustc_hir::PolyTraitRef<'_>,
opaque_ty: &rustc_hir::OpaqueTy<'_>, bounds: GenericBounds<'_>,
index: usize, index: usize,
// The bindings that were implied // The bindings that were implied, used for suggestion purposes since removing a bound with associated types
// means we might need to then move it to a different bound
implied_bindings: &[rustc_hir::TypeBinding<'_>], implied_bindings: &[rustc_hir::TypeBinding<'_>],
// The original bindings that `implied_bindings` are implied from bound: &ImplTraitBound<'_>,
implied_by_bindings: &[rustc_hir::TypeBinding<'_>],
implied_by_args: &[GenericArg<'_>],
implied_by_span: Span,
) { ) {
let implied_by = snippet(cx, implied_by_span, ".."); let implied_by = snippet(cx, bound.span, "..");
span_lint_and_then( span_lint_and_then(
cx, cx,
@ -75,10 +71,10 @@ fn emit_lint(
// to include the `+` token that is ahead or behind, // to include the `+` token that is ahead or behind,
// so we don't end up with something like `impl + B` or `impl A + ` // so we don't end up with something like `impl + B` or `impl A + `
let implied_span_extended = if let Some(next_bound) = opaque_ty.bounds.get(index + 1) { let implied_span_extended = if let Some(next_bound) = bounds.get(index + 1) {
poly_trait.span.to(next_bound.span().shrink_to_lo()) poly_trait.span.to(next_bound.span().shrink_to_lo())
} else if index > 0 } else if index > 0
&& let Some(prev_bound) = opaque_ty.bounds.get(index - 1) && let Some(prev_bound) = bounds.get(index - 1)
{ {
prev_bound.span().shrink_to_hi().to(poly_trait.span.shrink_to_hi()) prev_bound.span().shrink_to_hi().to(poly_trait.span.shrink_to_hi())
} else { } else {
@ -93,17 +89,17 @@ fn emit_lint(
// If we're going to suggest removing `Deref<..>`, we'll need to put `<Target = u8>` on `DerefMut` // If we're going to suggest removing `Deref<..>`, we'll need to put `<Target = u8>` on `DerefMut`
let omitted_assoc_tys: Vec<_> = implied_bindings let omitted_assoc_tys: Vec<_> = implied_bindings
.iter() .iter()
.filter(|binding| !implied_by_bindings.iter().any(|b| b.ident == binding.ident)) .filter(|binding| !bound.bindings.iter().any(|b| b.ident == binding.ident))
.collect(); .collect();
if !omitted_assoc_tys.is_empty() { if !omitted_assoc_tys.is_empty() {
// `<>` needs to be added if there aren't yet any generic arguments or bindings // `<>` needs to be added if there aren't yet any generic arguments or bindings
let needs_angle_brackets = implied_by_args.is_empty() && implied_by_bindings.is_empty(); let needs_angle_brackets = bound.args.is_empty() && bound.bindings.is_empty();
let insert_span = match (implied_by_args, implied_by_bindings) { let insert_span = match (bound.args, bound.bindings) {
([.., arg], [.., binding]) => arg.span().max(binding.span).shrink_to_hi(), ([.., arg], [.., binding]) => arg.span().max(binding.span).shrink_to_hi(),
([.., arg], []) => arg.span().shrink_to_hi(), ([.., arg], []) => arg.span().shrink_to_hi(),
([], [.., binding]) => binding.span.shrink_to_hi(), ([], [.., binding]) => binding.span.shrink_to_hi(),
([], []) => implied_by_span.shrink_to_hi(), ([], []) => bound.span.shrink_to_hi(),
}; };
let mut associated_tys_sugg = if needs_angle_brackets { let mut associated_tys_sugg = if needs_angle_brackets {
@ -223,24 +219,27 @@ fn is_same_generics<'tcx>(
}) })
} }
fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) { struct ImplTraitBound<'tcx> {
if let FnRetTy::Return(ty) = decl.output /// The span of the bound in the `impl Trait` type
&&let TyKind::OpaqueDef(item_id, ..) = ty.kind span: Span,
&& let item = cx.tcx.hir().item(item_id) /// The predicates defined in the trait referenced by this bound. This also contains the actual
&& let ItemKind::OpaqueTy(opaque_ty) = item.kind /// supertrait bounds
// Very often there is only a single bound, e.g. `impl Deref<..>`, in which case predicates: &'tcx [(ty::Clause<'tcx>, Span)],
// we can avoid doing a bunch of stuff unnecessarily. /// The `DefId` of the trait being referenced by this bound
&& opaque_ty.bounds.len() > 1 trait_def_id: DefId,
{ /// The generic arguments on the `impl Trait` bound
// Get all the (implied) trait predicates in the bounds. args: &'tcx [GenericArg<'tcx>],
// For `impl Deref + DerefMut` this will contain [`Deref`]. /// The associated types on this bound
// The implied `Deref` comes from `DerefMut` because `trait DerefMut: Deref {}`. bindings: &'tcx [TypeBinding<'tcx>],
// N.B. (G)ATs are fine to disregard, because they must be the same for all of its supertraits. }
// Example:
// `impl Deref<Target = i32> + DerefMut<Target = u32>` is not allowed. /// Given an `impl Trait` type, gets all the supertraits from each bound ("implied bounds").
// `DerefMut::Target` needs to match `Deref::Target`. ///
let implied_bounds: Vec<_> = opaque_ty /// For `impl Deref + DerefMut + Eq` this returns `[Deref, PartialEq]`.
.bounds /// The `Deref` comes from `DerefMut` because `trait DerefMut: Deref {}`, and `PartialEq` comes from
/// `Eq`.
fn collect_supertrait_bounds<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds<'tcx>) -> Vec<ImplTraitBound<'tcx>> {
bounds
.iter() .iter()
.filter_map(|bound| { .filter_map(|bound| {
if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound
@ -248,86 +247,107 @@ fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
&& poly_trait.bound_generic_params.is_empty() && poly_trait.bound_generic_params.is_empty()
&& let Some(trait_def_id) = path.res.opt_def_id() && let Some(trait_def_id) = path.res.opt_def_id()
&& let predicates = cx.tcx.super_predicates_of(trait_def_id).predicates && let predicates = cx.tcx.super_predicates_of(trait_def_id).predicates
// If the trait has no supertrait, there is no need to collect anything from that bound
&& !predicates.is_empty() && !predicates.is_empty()
// If the trait has no supertrait, there is nothing to add.
{ {
Some((bound.span(), path, predicates, trait_def_id)) Some(ImplTraitBound {
predicates,
args: path.args.map_or([].as_slice(), |p| p.args),
bindings: path.args.map_or([].as_slice(), |p| p.bindings),
trait_def_id,
span: bound.span(),
})
} else { } else {
None None
} }
}) })
.collect(); .collect()
}
// Lint all bounds in the `impl Trait` type that are also in the `implied_bounds` vec. /// Given a bound in an `impl Trait` type, looks for a trait in the set of supertraits (previously
/// collected in [`collect_supertrait_bounds`]) that matches (same trait and generic arguments).
fn find_bound_in_supertraits<'a, 'tcx>(
cx: &LateContext<'tcx>,
trait_def_id: DefId,
args: &'tcx [GenericArg<'tcx>],
bounds: &'a [ImplTraitBound<'tcx>],
) -> Option<&'a ImplTraitBound<'tcx>> {
bounds.iter().find(|bound| {
bound.predicates.iter().any(|(clause, _)| {
if let ClauseKind::Trait(tr) = clause.kind().skip_binder()
&& tr.def_id() == trait_def_id
{
is_same_generics(
cx.tcx,
tr.trait_ref.args,
bound.args,
args,
bound.trait_def_id,
trait_def_id,
)
} else {
false
}
})
})
}
fn check<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds<'tcx>) {
if bounds.len() == 1 {
// Very often there is only a single bound, e.g. `impl Deref<..>`, in which case
// we can avoid doing a bunch of stuff unnecessarily; there will trivially be
// no duplicate bounds
return;
}
let supertraits = collect_supertrait_bounds(cx, bounds);
// Lint all bounds in the `impl Trait` type that we've previously also seen in the set of
// supertraits of each of the bounds.
// This involves some extra logic when generic arguments are present, since // This involves some extra logic when generic arguments are present, since
// simply comparing trait `DefId`s won't be enough. We also need to compare the generics. // simply comparing trait `DefId`s won't be enough. We also need to compare the generics.
for (index, bound) in opaque_ty.bounds.iter().enumerate() { for (index, bound) in bounds.iter().enumerate() {
if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound
&& let [.., path] = poly_trait.trait_ref.path.segments && let [.., path] = poly_trait.trait_ref.path.segments
&& let implied_args = path.args.map_or([].as_slice(), |a| a.args) && let implied_args = path.args.map_or([].as_slice(), |a| a.args)
&& let implied_bindings = path.args.map_or([].as_slice(), |a| a.bindings) && let implied_bindings = path.args.map_or([].as_slice(), |a| a.bindings)
&& let Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id() && let Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id()
&& let Some((implied_by_span, implied_by_args, implied_by_bindings)) = && let Some(bound) = find_bound_in_supertraits(cx, def_id, implied_args, &supertraits)
implied_bounds // If the implied bound has a type binding that also exists in the implied-by trait,
.iter() // then we shouldn't lint. See #11880 for an example.
.find_map(|&(span, implied_by_path, preds, implied_by_def_id)| { && let assocs = cx.tcx.associated_items(bound.trait_def_id)
let implied_by_args = implied_by_path.args.map_or([].as_slice(), |a| a.args); && !implied_bindings.iter().any(|binding| {
let implied_by_bindings = implied_by_path.args.map_or([].as_slice(), |a| a.bindings); assocs
.filter_by_name_unhygienic(binding.ident.name)
preds.iter().find_map(|(clause, _)| { .next()
if let ClauseKind::Trait(tr) = clause.kind().skip_binder() .is_some_and(|assoc| assoc.kind == ty::AssocKind::Type)
&& tr.def_id() == def_id
&& is_same_generics(
cx.tcx,
tr.trait_ref.args,
implied_by_args,
implied_args,
implied_by_def_id,
def_id,
)
{
Some((span, implied_by_args, implied_by_bindings))
} else {
None
}
})
}) })
{ {
emit_lint( emit_lint(cx, poly_trait, bounds, index, implied_bindings, bound);
cx,
poly_trait,
opaque_ty,
index,
implied_bindings,
implied_by_bindings,
implied_by_args,
implied_by_span,
);
}
} }
} }
} }
impl LateLintPass<'_> for ImpliedBoundsInImpls { impl<'tcx> LateLintPass<'tcx> for ImpliedBoundsInImpls {
fn check_fn( fn check_generics(&mut self, cx: &LateContext<'tcx>, generics: &rustc_hir::Generics<'tcx>) {
&mut self, for predicate in generics.predicates {
cx: &LateContext<'_>, if let WherePredicate::BoundPredicate(predicate) = predicate
_: FnKind<'_>, // In theory, the origin doesn't really matter,
decl: &FnDecl<'_>, // we *could* also lint on explicit where clauses written out by the user,
_: &Body<'_>, // not just impl trait desugared ones, but that contradicts with the lint name...
_: Span, && let PredicateOrigin::ImplTrait = predicate.origin
_: LocalDefId, {
) { check(cx, predicate.bounds);
check(cx, decl); }
} }
fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { }
if let TraitItemKind::Fn(sig, ..) = &item.kind {
check(cx, sig.decl); fn check_ty(&mut self, cx: &LateContext<'_>, ty: &rustc_hir::Ty<'_>) {
} if let TyKind::OpaqueDef(item_id, ..) = ty.kind
} && let item = cx.tcx.hir().item(item_id)
fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) { && let ItemKind::OpaqueTy(opaque_ty) = item.kind
if let ImplItemKind::Fn(sig, ..) = &item.kind { {
check(cx, sig.decl); check(cx, opaque_ty.bounds);
} }
} }
} }

View file

@ -1,14 +1,15 @@
use clippy_config::msrvs::Msrv; use clippy_config::msrvs::Msrv;
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_in_test_function;
use rustc_attr::{StabilityLevel, StableSince}; use rustc_attr::{StabilityLevel, StableSince};
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind, HirId};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt; use rustc_middle::ty::TyCtxt;
use rustc_semver::RustcVersion; use rustc_semver::RustcVersion;
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::def_id::DefId; use rustc_span::def_id::DefId;
use rustc_span::Span; use rustc_span::{ExpnKind, Span};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -81,13 +82,18 @@ impl IncompatibleMsrv {
version version
} }
fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, span: Span) { fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, node: HirId, span: Span) {
if def_id.is_local() { if def_id.is_local() {
// We don't check local items since their MSRV is supposed to always be valid. // We don't check local items since their MSRV is supposed to always be valid.
return; return;
} }
let version = self.get_def_id_version(cx.tcx, def_id); let version = self.get_def_id_version(cx.tcx, def_id);
if self.msrv.meets(version) { if self.msrv.meets(version) || is_in_test_function(cx.tcx, node) {
return;
}
if let ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) = span.ctxt().outer_expn_data().kind {
// Desugared expressions get to cheat and stability is ignored.
// Intentionally not using `.from_expansion()`, since we do still care about macro expansions
return; return;
} }
self.emit_lint_for(cx, span, version); self.emit_lint_for(cx, span, version);
@ -117,14 +123,14 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
match expr.kind { match expr.kind {
ExprKind::MethodCall(_, _, _, span) => { ExprKind::MethodCall(_, _, _, span) => {
if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
self.emit_lint_if_under_msrv(cx, method_did, span); self.emit_lint_if_under_msrv(cx, method_did, expr.hir_id, span);
} }
}, },
ExprKind::Call(call, [_]) => { ExprKind::Call(call, [_]) => {
if let ExprKind::Path(qpath) = call.kind if let ExprKind::Path(qpath) = call.kind
&& let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id() && let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id()
{ {
self.emit_lint_if_under_msrv(cx, path_def_id, call.span); self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, call.span);
} }
}, },
_ => {}, _ => {},

View file

@ -255,7 +255,9 @@ impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> {
&& let hir::Node::Expr(maybe_addrof_expr) = cx.tcx.parent_hir_node(parent_id) && let hir::Node::Expr(maybe_addrof_expr) = cx.tcx.parent_hir_node(parent_id)
&& let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind && let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind
{ {
use_info.index_use.push((index_value, cx.tcx.hir().span(parent_expr.hir_id))); use_info
.index_use
.push((index_value, cx.tcx.hir().span(parent_expr.hir_id)));
return; return;
} }

View file

@ -174,6 +174,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
// only `usize` index is legal in rust array index // only `usize` index is legal in rust array index
// leave other type to rustc // leave other type to rustc
if let Constant::Int(off) = constant if let Constant::Int(off) = constant
&& off <= usize::MAX as u128
&& let ty::Uint(utype) = cx.typeck_results().expr_ty(index).kind() && let ty::Uint(utype) = cx.typeck_results().expr_ty(index).kind()
&& *utype == ty::UintTy::Usize && *utype == ty::UintTy::Usize
&& let ty::Array(_, s) = ty.kind() && let ty::Array(_, s) = ty.kind()

View file

@ -385,7 +385,6 @@ impl LateLintPass<'_> for ItemNameRepetitions {
assert!(last.is_some()); assert!(last.is_some());
} }
#[expect(clippy::similar_names)]
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
let item_name = item.ident.name.as_str(); let item_name = item.ident.name.as_str();
let item_camel = to_camel_case(item_name); let item_camel = to_camel_case(item_name);

View file

@ -1,5 +1,4 @@
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use clippy_utils::get_parent_node;
use clippy_utils::ty::implements_trait; use clippy_utils::ty::implements_trait;
use rustc_hir::def_id::LocalDefId; use rustc_hir::def_id::LocalDefId;
use rustc_hir::{FnSig, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind}; use rustc_hir::{FnSig, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind};
@ -56,8 +55,8 @@ impl<'tcx> LateLintPass<'tcx> for IterNotReturningIterator {
let name = item.ident.name.as_str(); let name = item.ident.name.as_str();
if matches!(name, "iter" | "iter_mut") if matches!(name, "iter" | "iter_mut")
&& !matches!( && !matches!(
get_parent_node(cx.tcx, item.hir_id()), cx.tcx.parent_hir_node(item.hir_id()),
Some(Node::Item(Item { kind: ItemKind::Impl(i), .. })) if i.of_trait.is_some() Node::Item(Item { kind: ItemKind::Impl(i), .. }) if i.of_trait.is_some()
) )
{ {
if let ImplItemKind::Fn(fn_sig, _) = &item.kind { if let ImplItemKind::Fn(fn_sig, _) = &item.kind {

View file

@ -14,7 +14,7 @@
clippy::missing_docs_in_private_items, clippy::missing_docs_in_private_items,
clippy::must_use_candidate, clippy::must_use_candidate,
rustc::diagnostic_outside_of_impl, rustc::diagnostic_outside_of_impl,
rustc::untranslatable_diagnostic, rustc::untranslatable_diagnostic
)] )]
#![warn(trivial_casts, trivial_numeric_casts)] #![warn(trivial_casts, trivial_numeric_casts)]
// warn on lints, that are included in `rust-lang/rust`s bootstrap // warn on lints, that are included in `rust-lang/rust`s bootstrap
@ -231,6 +231,7 @@ mod missing_trait_methods;
mod mixed_read_write_in_expression; mod mixed_read_write_in_expression;
mod module_style; mod module_style;
mod multi_assignments; mod multi_assignments;
mod multiple_bound_locations;
mod multiple_unsafe_ops_per_block; mod multiple_unsafe_ops_per_block;
mod mut_key; mod mut_key;
mod mut_mut; mod mut_mut;
@ -1067,7 +1068,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| { store.register_late_pass(move |_| {
Box::new(single_call_fn::SingleCallFn { Box::new(single_call_fn::SingleCallFn {
avoid_breaking_exported_api, avoid_breaking_exported_api,
def_id_to_usage: rustc_data_structures::fx::FxHashMap::default(), def_id_to_usage: rustc_data_structures::fx::FxIndexMap::default(),
}) })
}); });
store.register_early_pass(move || { store.register_early_pass(move || {
@ -1116,6 +1117,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
}); });
store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv()))); store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv())));
store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl)); store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl));
store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations));
// add lints here, do not remove this comment, it's used in `new_lint` // add lints here, do not remove this comment, it's used in `new_lint`
} }

View file

@ -1,17 +1,18 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{fn_def_id, is_lint_allowed}; use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed};
use hir::intravisit::{walk_expr, Visitor}; use hir::intravisit::{walk_expr, Visitor};
use hir::{Expr, ExprKind, FnRetTy, FnSig, Node}; use hir::{Expr, ExprKind, FnRetTy, FnSig, Node};
use rustc_ast::Label; use rustc_ast::Label;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::LateContext; use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use super::INFINITE_LOOP; use super::INFINITE_LOOP;
pub(super) fn check<'tcx>( pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
expr: &Expr<'_>, expr: &Expr<'tcx>,
loop_block: &'tcx hir::Block<'_>, loop_block: &'tcx hir::Block<'_>,
label: Option<Label>, label: Option<Label>,
) { ) {
@ -34,6 +35,10 @@ pub(super) fn check<'tcx>(
return; return;
} }
if in_external_macro(cx.sess(), expr.span) || is_from_proc_macro(cx, expr) {
return;
}
let mut loop_visitor = LoopVisitor { let mut loop_visitor = LoopVisitor {
cx, cx,
label, label,

View file

@ -74,7 +74,7 @@ enum CharRange {
LowerChar, LowerChar,
/// 'A'..='Z' | b'A'..=b'Z' /// 'A'..='Z' | b'A'..=b'Z'
UpperChar, UpperChar,
/// AsciiLower | AsciiUpper /// `AsciiLower` | `AsciiUpper`
FullChar, FullChar,
/// '0..=9' /// '0..=9'
Digit, Digit,

View file

@ -64,6 +64,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
let min_index = usize::min(lindex, rindex); let min_index = usize::min(lindex, rindex);
let max_index = usize::max(lindex, rindex); let max_index = usize::max(lindex, rindex);
let check_eq_with_pat = |expr_a: &Expr<'_>, expr_b: &Expr<'_>| {
let mut local_map: HirIdMap<HirId> = HirIdMap::default(); let mut local_map: HirIdMap<HirId> = HirIdMap::default();
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
if let Some(a_id) = path_to_local(a) if let Some(a_id) = path_to_local(a)
@ -85,19 +86,30 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
false false
} }
}; };
// Arms with a guard are ignored, those cant always be merged together
// If both arms overlap with an arm in between then these can't be merged either. SpanlessEq::new(cx)
!(backwards_blocking_idxs[max_index] > min_index && forwards_blocking_idxs[min_index] < max_index)
&& lhs.guard.is_none()
&& rhs.guard.is_none()
&& SpanlessEq::new(cx)
.expr_fallback(eq_fallback) .expr_fallback(eq_fallback)
.eq_expr(lhs.body, rhs.body) .eq_expr(expr_a, expr_b)
// these checks could be removed to allow unused bindings // these checks could be removed to allow unused bindings
&& bindings_eq(lhs.pat, local_map.keys().copied().collect()) && bindings_eq(lhs.pat, local_map.keys().copied().collect())
&& bindings_eq(rhs.pat, local_map.values().copied().collect()) && bindings_eq(rhs.pat, local_map.values().copied().collect())
}; };
let check_same_guard = || match (&lhs.guard, &rhs.guard) {
(None, None) => true,
(Some(lhs_guard), Some(rhs_guard)) => check_eq_with_pat(lhs_guard, rhs_guard),
_ => false,
};
let check_same_body = || check_eq_with_pat(lhs.body, rhs.body);
// Arms with different guard are ignored, those cant always be merged together
// If both arms overlap with an arm in between then these can't be merged either.
!(backwards_blocking_idxs[max_index] > min_index && forwards_blocking_idxs[min_index] < max_index)
&& check_same_guard()
&& check_same_body()
};
let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) { for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) {
if matches!(arm2.pat.kind, PatKind::Wild) { if matches!(arm2.pat.kind, PatKind::Wild) {

View file

@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts}; use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
use clippy_utils::{ use clippy_utils::{
eq_expr_value, get_parent_expr_for_hir, get_parent_node, higher, is_else_clause, is_res_lang_ctor, over, path_res, eq_expr_value, get_parent_expr_for_hir, higher, is_else_clause, is_res_lang_ctor, over, path_res,
peel_blocks_with_stmt, peel_blocks_with_stmt,
}; };
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -123,8 +123,7 @@ fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
/// Manually check for coercion casting by checking if the type of the match operand or let expr /// Manually check for coercion casting by checking if the type of the match operand or let expr
/// differs with the assigned local variable or the function return type. /// differs with the assigned local variable or the function return type.
fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>) -> bool { fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>) -> bool {
if let Some(p_node) = get_parent_node(cx.tcx, p_expr.hir_id) { match cx.tcx.parent_hir_node(p_expr.hir_id) {
match p_node {
// Compare match_expr ty with local in `let local = match match_expr {..}` // Compare match_expr ty with local in `let local = match match_expr {..}`
Node::Local(local) => { Node::Local(local) => {
let results = cx.typeck_results(); let results = cx.typeck_results();
@ -154,7 +153,6 @@ fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>
}, },
_ => {}, _ => {},
} }
}
false false
} }

View file

@ -1,11 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::path_to_local;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::visitors::{for_each_expr, is_local_used}; use clippy_utils::visitors::{for_each_expr, is_local_used};
use clippy_utils::{in_constant, path_to_local};
use rustc_ast::{BorrowKind, LitKind}; use rustc_ast::{BorrowKind, LitKind};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, MatchSource, Node, Pat, PatKind}; use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, MatchSource, Node, Pat, PatKind, UnOp};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_span::symbol::Ident; use rustc_span::symbol::Ident;
use rustc_span::{Span, Symbol}; use rustc_span::{Span, Symbol};
@ -123,7 +123,7 @@ fn check_method_calls<'tcx>(
// `s if s.is_empty()` becomes "" // `s if s.is_empty()` becomes ""
// `arr if arr.is_empty()` becomes [] // `arr if arr.is_empty()` becomes []
if ty.is_str() { if ty.is_str() && !in_constant(cx, if_expr.hir_id) {
r#""""#.into() r#""""#.into()
} else if slice_like { } else if slice_like {
"[]".into() "[]".into()
@ -269,7 +269,11 @@ fn expr_can_be_pat(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Ctor(..), ..), Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Ctor(..), ..),
) )
}, },
ExprKind::AddrOf(..) | ExprKind::Array(..) | ExprKind::Tup(..) | ExprKind::Struct(..) => true, ExprKind::AddrOf(..)
| ExprKind::Array(..)
| ExprKind::Tup(..)
| ExprKind::Struct(..)
| ExprKind::Unary(UnOp::Neg, _) => true,
ExprKind::Lit(lit) if !matches!(lit.node, LitKind::Float(..)) => true, ExprKind::Lit(lit) if !matches!(lit.node, LitKind::Float(..)) => true,
_ => false, _ => false,
} { } {

View file

@ -37,7 +37,12 @@ pub(super) fn check<'tcx>(
} }
} }
fn set_diagnostic<'tcx>(diag: &mut DiagnosticBuilder<'_, ()>, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) { fn set_diagnostic<'tcx>(
diag: &mut DiagnosticBuilder<'_, ()>,
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
found: FoundSigDrop,
) {
if found.lint_suggestion == LintSuggestion::MoveAndClone { if found.lint_suggestion == LintSuggestion::MoveAndClone {
// If our suggestion is to move and clone, then we want to leave it to the user to // If our suggestion is to move and clone, then we want to leave it to the user to
// decide how to address this lint, since it may be that cloning is inappropriate. // decide how to address this lint, since it may be that cloning is inappropriate.

View file

@ -3,7 +3,9 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lin
use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_non_aggregate_primitive_type; use clippy_utils::ty::is_non_aggregate_primitive_type;
use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res, peel_ref_operators, std_or_core}; use clippy_utils::{
is_default_equivalent, is_expr_used_or_unified, is_res_lang_ctor, path_res, peel_ref_operators, std_or_core,
};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionNone; use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind};
@ -232,7 +234,7 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace {
// Check that second argument is `Option::None` // Check that second argument is `Option::None`
if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) { if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
check_replace_option_with_none(cx, dest, expr.span); check_replace_option_with_none(cx, dest, expr.span);
} else if self.msrv.meets(msrvs::MEM_TAKE) { } else if self.msrv.meets(msrvs::MEM_TAKE) && is_expr_used_or_unified(cx.tcx, expr) {
check_replace_with_default(cx, src, dest, expr.span); check_replace_with_default(cx, src, dest, expr.span);
} }
check_replace_with_uninit(cx, src, dest, expr.span); check_replace_with_uninit(cx, src, dest, expr.span);

View file

@ -38,6 +38,7 @@ pub(super) fn check<'tcx>(
&& ext_str.starts_with('.') && ext_str.starts_with('.')
&& (ext_str.chars().skip(1).all(|c| c.is_uppercase() || c.is_ascii_digit()) && (ext_str.chars().skip(1).all(|c| c.is_uppercase() || c.is_ascii_digit())
|| ext_str.chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit())) || ext_str.chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit()))
&& !ext_str.chars().skip(1).all(|c| c.is_ascii_digit())
&& let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs() && let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs()
&& (recv_ty.is_str() || is_type_lang_item(cx, recv_ty, LangItem::String)) && (recv_ty.is_str() || is_type_lang_item(cx, recv_ty, LangItem::String))
{ {

View file

@ -1,5 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::get_parent_node;
use clippy_utils::source::snippet_with_context; use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_copy; use clippy_utils::ty::is_copy;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -48,8 +47,8 @@ pub(super) fn check(
} }
if is_copy(cx, ty) { if is_copy(cx, ty) {
let parent_is_suffix_expr = match get_parent_node(cx.tcx, expr.hir_id) { let parent_is_suffix_expr = match cx.tcx.parent_hir_node(expr.hir_id) {
Some(Node::Expr(parent)) => match parent.kind { Node::Expr(parent) => match parent.kind {
// &*x is a nop, &x.clone() is not // &*x is a nop, &x.clone() is not
ExprKind::AddrOf(..) => return, ExprKind::AddrOf(..) => return,
// (*x).func() is useless, x.clone().func() can work in case func borrows self // (*x).func() is useless, x.clone().func() can work in case func borrows self
@ -70,7 +69,7 @@ pub(super) fn check(
_ => false, _ => false,
}, },
// local binding capturing a reference // local binding capturing a reference
Some(Node::Local(l)) if matches!(l.pat.kind, PatKind::Binding(BindingAnnotation(ByRef::Yes, _), ..)) => { Node::Local(l) if matches!(l.pat.kind, PatKind::Binding(BindingAnnotation(ByRef::Yes, _), ..)) => {
return; return;
}, },
_ => false, _ => false,

View file

@ -75,7 +75,7 @@ enum OffendingFilterExpr<'tcx> {
}, },
/// `.filter(|enum| matches!(enum, Enum::A(_)))` /// `.filter(|enum| matches!(enum, Enum::A(_)))`
Matches { Matches {
/// The DefId of the variant being matched /// The `DefId` of the variant being matched
variant_def_id: hir::def_id::DefId, variant_def_id: hir::def_id::DefId,
}, },
} }

View file

@ -110,6 +110,7 @@ mod unit_hash;
mod unnecessary_fallible_conversions; mod unnecessary_fallible_conversions;
mod unnecessary_filter_map; mod unnecessary_filter_map;
mod unnecessary_fold; mod unnecessary_fold;
mod unnecessary_get_then_check;
mod unnecessary_iter_cloned; mod unnecessary_iter_cloned;
mod unnecessary_join; mod unnecessary_join;
mod unnecessary_lazy_eval; mod unnecessary_lazy_eval;
@ -3419,11 +3420,12 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Looks for calls to [`Stdin::read_line`] to read a line from the standard input /// Looks for calls to [`Stdin::read_line`] to read a line from the standard input
/// into a string, then later attempting to parse this string into a type without first trimming it, which will /// into a string, then later attempting to use that string for an operation that will never
/// always fail because the string has a trailing newline in it. /// work for strings with a trailing newline character in it (e.g. parsing into a `i32`).
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// The `.parse()` call will always fail. /// The operation will always fail at runtime no matter what the user enters, thus
/// making it a useless operation.
/// ///
/// ### Example /// ### Example
/// ```rust,ignore /// ```rust,ignore
@ -4011,6 +4013,35 @@ declare_clippy_lint! {
r#"creating a `CStr` through functions when `c""` literals can be used"# r#"creating a `CStr` through functions when `c""` literals can be used"#
} }
declare_clippy_lint! {
/// ### What it does
/// Checks the usage of `.get().is_some()` or `.get().is_none()` on std map types.
///
/// ### Why is this bad?
/// It can be done in one call with `.contains()`/`.contains_keys()`.
///
/// ### Example
/// ```no_run
/// # use std::collections::HashSet;
/// let s: HashSet<String> = HashSet::new();
/// if s.get("a").is_some() {
/// // code
/// }
/// ```
/// Use instead:
/// ```no_run
/// # use std::collections::HashSet;
/// let s: HashSet<String> = HashSet::new();
/// if s.contains("a") {
/// // code
/// }
/// ```
#[clippy::version = "1.78.0"]
pub UNNECESSARY_GET_THEN_CHECK,
suspicious,
"calling `.get().is_some()` or `.get().is_none()` instead of `.contains()` or `.contains_key()`"
}
pub struct Methods { pub struct Methods {
avoid_breaking_exported_api: bool, avoid_breaking_exported_api: bool,
msrv: Msrv, msrv: Msrv,
@ -4171,6 +4202,7 @@ impl_lint_pass!(Methods => [
OPTION_AS_REF_CLONED, OPTION_AS_REF_CLONED,
UNNECESSARY_RESULT_MAP_OR_ELSE, UNNECESSARY_RESULT_MAP_OR_ELSE,
MANUAL_C_STR_LITERALS, MANUAL_C_STR_LITERALS,
UNNECESSARY_GET_THEN_CHECK,
]); ]);
/// Extracts a method call name, args, and `Span` of the method name. /// Extracts a method call name, args, and `Span` of the method name.
@ -4587,8 +4619,8 @@ impl Methods {
}, },
("is_file", []) => filetype_is_file::check(cx, expr, recv), ("is_file", []) => filetype_is_file::check(cx, expr, recv),
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, &self.msrv), ("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, &self.msrv),
("is_none", []) => check_is_some_is_none(cx, expr, recv, false), ("is_none", []) => check_is_some_is_none(cx, expr, recv, call_span, false),
("is_some", []) => check_is_some_is_none(cx, expr, recv, true), ("is_some", []) => check_is_some_is_none(cx, expr, recv, call_span, true),
("iter" | "iter_mut" | "into_iter", []) => { ("iter" | "iter_mut" | "into_iter", []) => {
iter_on_single_or_empty_collections::check(cx, expr, name, recv); iter_on_single_or_empty_collections::check(cx, expr, name, recv);
}, },
@ -4899,9 +4931,15 @@ impl Methods {
} }
} }
fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, is_some: bool) { fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, call_span: Span, is_some: bool) {
if let Some((name @ ("find" | "position" | "rposition"), f_recv, [arg], span, _)) = method_call(recv) { match method_call(recv) {
Some((name @ ("find" | "position" | "rposition"), f_recv, [arg], span, _)) => {
search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span); search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span);
},
Some(("get", f_recv, [arg], _, _)) => {
unnecessary_get_then_check::check(cx, call_span, recv, f_recv, arg, is_some);
},
_ => {},
} }
} }

View file

@ -4,8 +4,8 @@ use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{is_type_diagnostic_item, make_normalized_projection, make_projection}; use clippy_utils::ty::{is_type_diagnostic_item, make_normalized_projection, make_projection};
use clippy_utils::{ use clippy_utils::{
can_move_expr_to_closure, fn_def_id, get_enclosing_block, get_parent_node, higher, is_trait_method, path_to_local, can_move_expr_to_closure, fn_def_id, get_enclosing_block, higher, is_trait_method, path_to_local, path_to_local_id,
path_to_local_id, CaptureKind, CaptureKind,
}; };
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{Applicability, MultiSpan}; use rustc_errors::{Applicability, MultiSpan};
@ -28,8 +28,7 @@ pub(super) fn check<'tcx>(
iter_expr: &'tcx Expr<'tcx>, iter_expr: &'tcx Expr<'tcx>,
call_span: Span, call_span: Span,
) { ) {
if let Some(parent) = get_parent_node(cx.tcx, collect_expr.hir_id) { match cx.tcx.parent_hir_node(collect_expr.hir_id) {
match parent {
Node::Expr(parent) => { Node::Expr(parent) => {
check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr); check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr);
@ -127,7 +126,6 @@ pub(super) fn check<'tcx>(
_ => (), _ => (),
} }
} }
}
/// checks for for collecting into a (generic) method or function argument /// checks for for collecting into a (generic) method or function argument
/// taking an `IntoIterator` /// taking an `IntoIterator`

View file

@ -5,15 +5,26 @@ use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::for_each_local_use_after_expr; use clippy_utils::visitors::for_each_local_use_after_expr;
use clippy_utils::{get_parent_expr, match_def_path}; use clippy_utils::{get_parent_expr, match_def_path};
use rustc_ast::LitKind;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::Res; use rustc_hir::def::Res;
use rustc_hir::{Expr, ExprKind, QPath}; use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, Ty};
use rustc_span::sym; use rustc_span::sym;
use super::READ_LINE_WITHOUT_TRIM; use super::READ_LINE_WITHOUT_TRIM;
fn expr_is_string_literal_without_trailing_newline(expr: &Expr<'_>) -> bool {
if let ExprKind::Lit(lit) = expr.kind
&& let LitKind::Str(sym, _) = lit.node
{
!sym.as_str().ends_with('\n')
} else {
false
}
}
/// Will a `.parse::<ty>()` call fail if the input has a trailing newline? /// Will a `.parse::<ty>()` call fail if the input has a trailing newline?
fn parse_fails_on_trailing_newline(ty: Ty<'_>) -> bool { fn parse_fails_on_trailing_newline(ty: Ty<'_>) -> bool {
// only allow a very limited set of types for now, for which we 100% know parsing will fail // only allow a very limited set of types for now, for which we 100% know parsing will fail
@ -27,30 +38,66 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<
&& let Res::Local(local_id) = path.res && let Res::Local(local_id) = path.res
{ {
// We've checked that `call` is a call to `Stdin::read_line()` with the right receiver, // We've checked that `call` is a call to `Stdin::read_line()` with the right receiver,
// now let's check if the first use of the string passed to `::read_line()` is // now let's check if the first use of the string passed to `::read_line()`
// parsed into a type that will always fail if it has a trailing newline. // is used for operations that will always fail (e.g. parsing "6\n" into a number)
for_each_local_use_after_expr(cx, local_id, call.hir_id, |expr| { for_each_local_use_after_expr(cx, local_id, call.hir_id, |expr| {
if let Some(parent) = get_parent_expr(cx, expr) if let Some(parent) = get_parent_expr(cx, expr) {
&& let ExprKind::MethodCall(segment, .., span) = parent.kind let data = if let ExprKind::MethodCall(segment, recv, args, span) = parent.kind {
&& segment.ident.name == sym!(parse) if segment.ident.name == sym!(parse)
&& let parse_result_ty = cx.typeck_results().expr_ty(parent) && let parse_result_ty = cx.typeck_results().expr_ty(parent)
&& is_type_diagnostic_item(cx, parse_result_ty, sym::Result) && is_type_diagnostic_item(cx, parse_result_ty, sym::Result)
&& let ty::Adt(_, args) = parse_result_ty.kind() && let ty::Adt(_, substs) = parse_result_ty.kind()
&& let Some(ok_ty) = args[0].as_type() && let Some(ok_ty) = substs[0].as_type()
&& parse_fails_on_trailing_newline(ok_ty) && parse_fails_on_trailing_newline(ok_ty)
{ {
let local_snippet = snippet(cx, expr.span, "<expr>"); // Called `s.parse::<T>()` where `T` is a type we know for certain will fail
span_lint_and_then( // if the input has a trailing newline
cx, Some((
READ_LINE_WITHOUT_TRIM,
span, span,
"calling `.parse()` without trimming the trailing newline character", "calling `.parse()` on a string without trimming the trailing newline character",
|diag| { "checking",
))
} else if segment.ident.name == sym!(ends_with)
&& recv.span == expr.span
&& let [arg] = args
&& expr_is_string_literal_without_trailing_newline(arg)
{
// Called `s.ends_with(<some string literal>)` where the argument is a string literal that does
// not end with a newline, thus always evaluating to false
Some((
parent.span,
"checking the end of a string without trimming the trailing newline character",
"parsing",
))
} else {
None
}
} else if let ExprKind::Binary(binop, left, right) = parent.kind
&& let BinOpKind::Eq = binop.node
&& (expr_is_string_literal_without_trailing_newline(left)
|| expr_is_string_literal_without_trailing_newline(right))
{
// `s == <some string literal>` where the string literal does not end with a newline
Some((
parent.span,
"comparing a string literal without trimming the trailing newline character",
"comparison",
))
} else {
None
};
if let Some((primary_span, lint_message, operation)) = data {
span_lint_and_then(cx, READ_LINE_WITHOUT_TRIM, primary_span, lint_message, |diag| {
let local_snippet = snippet(cx, expr.span, "<expr>");
diag.span_note( diag.span_note(
call.span, call.span,
format!(
"call to `.read_line()` here, \ "call to `.read_line()` here, \
which leaves a trailing newline character in the buffer, \ which leaves a trailing newline character in the buffer, \
which in turn will cause `.parse()` to fail", which in turn will cause the {operation} to always fail"
),
); );
diag.span_suggestion( diag.span_suggestion(
@ -59,8 +106,8 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<
format!("{local_snippet}.trim_end()"), format!("{local_snippet}.trim_end()"),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
}, });
); }
} }
// only consider the first use to prevent this scenario: // only consider the first use to prevent this scenario:

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::get_parent_expr; use clippy_utils::get_parent_expr;
use clippy_utils::ty::implements_trait; use clippy_utils::ty::implements_trait;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty; use rustc_middle::ty;
use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_middle::ty::print::with_forced_trimmed_paths;
@ -10,17 +10,71 @@ use rustc_span::{sym, Span};
use super::UNNECESSARY_FALLIBLE_CONVERSIONS; use super::UNNECESSARY_FALLIBLE_CONVERSIONS;
#[derive(Copy, Clone)]
enum SpansKind {
TraitFn { trait_span: Span, fn_span: Span },
Fn { fn_span: Span },
}
/// What function is being called and whether that call is written as a method call or a function /// What function is being called and whether that call is written as a method call or a function
/// call /// call
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
#[expect(clippy::enum_variant_names)] #[expect(clippy::enum_variant_names)]
enum FunctionKind { enum FunctionKind {
/// `T::try_from(U)` /// `T::try_from(U)`
TryFromFunction, TryFromFunction(Option<SpansKind>),
/// `t.try_into()` /// `t.try_into()`
TryIntoMethod, TryIntoMethod,
/// `U::try_into(t)` /// `U::try_into(t)`
TryIntoFunction, TryIntoFunction(Option<SpansKind>),
}
impl FunctionKind {
fn appl_sugg(&self, parent_unwrap_call: Option<Span>, primary_span: Span) -> (Applicability, Vec<(Span, String)>) {
let Some(unwrap_span) = parent_unwrap_call else {
return (Applicability::Unspecified, self.default_sugg(primary_span));
};
match &self {
FunctionKind::TryFromFunction(None) | FunctionKind::TryIntoFunction(None) => {
(Applicability::Unspecified, self.default_sugg(primary_span))
},
_ => (
Applicability::MachineApplicable,
self.machine_applicable_sugg(primary_span, unwrap_span),
),
}
}
fn default_sugg(&self, primary_span: Span) -> Vec<(Span, String)> {
let replacement = match *self {
FunctionKind::TryFromFunction(_) => "From::from",
FunctionKind::TryIntoFunction(_) => "Into::into",
FunctionKind::TryIntoMethod => "into",
};
vec![(primary_span, String::from(replacement))]
}
fn machine_applicable_sugg(&self, primary_span: Span, unwrap_span: Span) -> Vec<(Span, String)> {
let (trait_name, fn_name) = match self {
FunctionKind::TryFromFunction(_) => ("From".to_owned(), "from".to_owned()),
FunctionKind::TryIntoFunction(_) | FunctionKind::TryIntoMethod => ("Into".to_owned(), "into".to_owned()),
};
let mut sugg = match *self {
FunctionKind::TryFromFunction(Some(spans)) | FunctionKind::TryIntoFunction(Some(spans)) => match spans {
SpansKind::TraitFn { trait_span, fn_span } => vec![(trait_span, trait_name), (fn_span, fn_name)],
SpansKind::Fn { fn_span } => vec![(fn_span, fn_name)],
},
FunctionKind::TryIntoMethod => vec![(primary_span, fn_name)],
// Or the suggestion is not machine-applicable
_ => unreachable!(),
};
sugg.push((unwrap_span, String::new()));
sugg
}
} }
fn check<'tcx>( fn check<'tcx>(
@ -35,8 +89,8 @@ fn check<'tcx>(
&& self_ty != other_ty && self_ty != other_ty
&& let Some(self_ty) = self_ty.as_type() && let Some(self_ty) = self_ty.as_type()
&& let Some(from_into_trait) = cx.tcx.get_diagnostic_item(match kind { && let Some(from_into_trait) = cx.tcx.get_diagnostic_item(match kind {
FunctionKind::TryFromFunction => sym::From, FunctionKind::TryFromFunction(_) => sym::From,
FunctionKind::TryIntoMethod | FunctionKind::TryIntoFunction => sym::Into, FunctionKind::TryIntoMethod | FunctionKind::TryIntoFunction(_) => sym::Into,
}) })
// If `T: TryFrom<U>` and `T: From<U>` both exist, then that means that the `TryFrom` // If `T: TryFrom<U>` and `T: From<U>` both exist, then that means that the `TryFrom`
// _must_ be from the blanket impl and cannot have been manually implemented // _must_ be from the blanket impl and cannot have been manually implemented
@ -45,49 +99,37 @@ fn check<'tcx>(
&& implements_trait(cx, self_ty, from_into_trait, &[other_ty]) && implements_trait(cx, self_ty, from_into_trait, &[other_ty])
&& let Some(other_ty) = other_ty.as_type() && let Some(other_ty) = other_ty.as_type()
{ {
let parent_unwrap_call = get_parent_expr(cx, expr).and_then(|parent| {
if let ExprKind::MethodCall(path, .., span) = parent.kind
&& let sym::unwrap | sym::expect = path.ident.name
{
Some(span)
} else {
None
}
});
let (source_ty, target_ty, sugg, span, applicability) = match kind {
FunctionKind::TryIntoMethod if let Some(unwrap_span) = parent_unwrap_call => {
// Extend the span to include the unwrap/expect call: // Extend the span to include the unwrap/expect call:
// `foo.try_into().expect("..")` // `foo.try_into().expect("..")`
// ^^^^^^^^^^^^^^^^^^^^^^^ // ^^^^^^^^^^^^^^^^^^^^^^^
// //
// `try_into().unwrap()` specifically can be trivially replaced with just `into()`, // `try_into().unwrap()` specifically can be trivially replaced with just `into()`,
// so that can be machine-applicable // so that can be machine-applicable
let parent_unwrap_call = get_parent_expr(cx, expr).and_then(|parent| {
if let ExprKind::MethodCall(path, .., span) = parent.kind
&& let sym::unwrap | sym::expect = path.ident.name
{
// include `.` before `unwrap`/`expect`
Some(span.with_lo(expr.span.hi()))
} else {
None
}
});
( // If there is an unwrap/expect call, extend the span to include the call
self_ty, let span = if let Some(unwrap_call) = parent_unwrap_call {
other_ty, primary_span.with_hi(unwrap_call.hi())
"into()", } else {
primary_span.with_hi(unwrap_span.hi()), primary_span
Applicability::MachineApplicable,
)
},
FunctionKind::TryFromFunction => (
other_ty,
self_ty,
"From::from",
primary_span,
Applicability::Unspecified,
),
FunctionKind::TryIntoFunction => (
self_ty,
other_ty,
"Into::into",
primary_span,
Applicability::Unspecified,
),
FunctionKind::TryIntoMethod => (self_ty, other_ty, "into", primary_span, Applicability::Unspecified),
}; };
let (source_ty, target_ty) = match kind {
FunctionKind::TryIntoMethod | FunctionKind::TryIntoFunction(_) => (self_ty, other_ty),
FunctionKind::TryFromFunction(_) => (other_ty, self_ty),
};
let (applicability, sugg) = kind.appl_sugg(parent_unwrap_call, primary_span);
span_lint_and_then( span_lint_and_then(
cx, cx,
UNNECESSARY_FALLIBLE_CONVERSIONS, UNNECESSARY_FALLIBLE_CONVERSIONS,
@ -97,7 +139,7 @@ fn check<'tcx>(
with_forced_trimmed_paths!({ with_forced_trimmed_paths!({
diag.note(format!("converting `{source_ty}` to `{target_ty}` cannot fail")); diag.note(format!("converting `{source_ty}` to `{target_ty}` cannot fail"));
}); });
diag.span_suggestion(span, "use", sugg, applicability); diag.multipart_suggestion("use", sugg, applicability);
}, },
); );
} }
@ -125,13 +167,30 @@ pub(super) fn check_function(cx: &LateContext<'_>, expr: &Expr<'_>, callee: &Exp
&& let Some(item_def_id) = cx.qpath_res(qpath, callee.hir_id).opt_def_id() && let Some(item_def_id) = cx.qpath_res(qpath, callee.hir_id).opt_def_id()
&& let Some(trait_def_id) = cx.tcx.trait_of_item(item_def_id) && let Some(trait_def_id) = cx.tcx.trait_of_item(item_def_id)
{ {
let qpath_spans = match qpath {
QPath::Resolved(_, path) => {
if let [trait_seg, fn_seg] = path.segments {
Some(SpansKind::TraitFn {
trait_span: trait_seg.ident.span,
fn_span: fn_seg.ident.span,
})
} else {
None
}
},
QPath::TypeRelative(_, seg) => Some(SpansKind::Fn {
fn_span: seg.ident.span,
}),
QPath::LangItem(_, _) => unreachable!("`TryFrom` and `TryInto` are not lang items"),
};
check( check(
cx, cx,
expr, expr,
cx.typeck_results().node_args(callee.hir_id), cx.typeck_results().node_args(callee.hir_id),
match cx.tcx.get_diagnostic_name(trait_def_id) { match cx.tcx.get_diagnostic_name(trait_def_id) {
Some(sym::TryFrom) => FunctionKind::TryFromFunction, Some(sym::TryFrom) => FunctionKind::TryFromFunction(qpath_spans),
Some(sym::TryInto) => FunctionKind::TryIntoFunction, Some(sym::TryInto) => FunctionKind::TryIntoFunction(qpath_spans),
_ => return, _ => return,
}, },
callee.span, callee.span,

View file

@ -0,0 +1,85 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_span::{sym, Span};
use super::UNNECESSARY_GET_THEN_CHECK;
fn is_a_std_set_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
is_type_diagnostic_item(cx, ty, sym::HashSet) || is_type_diagnostic_item(cx, ty, sym::BTreeSet)
}
fn is_a_std_map_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap)
}
pub(super) fn check(
cx: &LateContext<'_>,
call_span: Span,
get_call: &Expr<'_>,
get_caller: &Expr<'_>,
arg: &Expr<'_>,
is_some: bool,
) {
let caller_ty = cx.typeck_results().expr_ty(get_caller);
let is_set = is_a_std_set_type(cx, caller_ty);
let is_map = is_a_std_map_type(cx, caller_ty);
if !is_set && !is_map {
return;
}
let ExprKind::MethodCall(path, _, _, get_call_span) = get_call.kind else {
return;
};
let both_calls_span = get_call_span.with_hi(call_span.hi());
if let Some(snippet) = snippet_opt(cx, both_calls_span)
&& let Some(arg_snippet) = snippet_opt(cx, arg.span)
{
let generics_snippet = if let Some(generics) = path.args
&& let Some(generics_snippet) = snippet_opt(cx, generics.span_ext)
{
format!("::{generics_snippet}")
} else {
String::new()
};
let suggestion = if is_set {
format!("contains{generics_snippet}({arg_snippet})")
} else {
format!("contains_key{generics_snippet}({arg_snippet})")
};
if is_some {
span_lint_and_sugg(
cx,
UNNECESSARY_GET_THEN_CHECK,
both_calls_span,
&format!("unnecessary use of `{snippet}`"),
"replace it with",
suggestion,
Applicability::MaybeIncorrect,
);
} else if let Some(caller_snippet) = snippet_opt(cx, get_caller.span) {
let full_span = get_caller.span.with_hi(call_span.hi());
span_lint_and_then(
cx,
UNNECESSARY_GET_THEN_CHECK,
both_calls_span,
&format!("unnecessary use of `{snippet}`"),
|diag| {
diag.span_suggestion(
full_span,
"replace it with",
format!("{}{caller_snippet}.{suggestion}", if is_some { "" } else { "!" }),
Applicability::MaybeIncorrect,
);
},
);
}
}
}

View file

@ -3,7 +3,9 @@ use super::unnecessary_iter_cloned::{self, is_into_iter};
use clippy_config::msrvs::{self, Msrv}; use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, is_type_lang_item, peel_mid_ty_refs}; use clippy_utils::ty::{
get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item, peel_mid_ty_refs,
};
use clippy_utils::visitors::find_all_ret_expressions; use clippy_utils::visitors::find_all_ret_expressions;
use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty}; use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty};
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -16,7 +18,8 @@ use rustc_lint::LateContext;
use rustc_middle::mir::Mutability; use rustc_middle::mir::Mutability;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
use rustc_middle::ty::{ use rustc_middle::ty::{
self, ClauseKind, GenericArg, GenericArgKind, GenericArgsRef, ParamTy, ProjectionPredicate, TraitPredicate, Ty, self, ClauseKind, GenericArg, GenericArgKind, GenericArgsRef, ImplPolarity, ParamTy, ProjectionPredicate,
TraitPredicate, Ty,
}; };
use rustc_span::{sym, Symbol}; use rustc_span::{sym, Symbol};
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
@ -53,6 +56,8 @@ pub fn check<'tcx>(
} }
check_other_call_arg(cx, expr, method_name, receiver); check_other_call_arg(cx, expr, method_name, receiver);
} }
} else {
check_borrow_predicate(cx, expr);
} }
} }
@ -590,3 +595,92 @@ fn is_to_string_on_string_like<'a>(
false false
} }
} }
fn is_a_std_map_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
is_type_diagnostic_item(cx, ty, sym::HashSet)
|| is_type_diagnostic_item(cx, ty, sym::HashMap)
|| is_type_diagnostic_item(cx, ty, sym::BTreeMap)
|| is_type_diagnostic_item(cx, ty, sym::BTreeSet)
}
fn is_str_and_string(cx: &LateContext<'_>, arg_ty: Ty<'_>, original_arg_ty: Ty<'_>) -> bool {
original_arg_ty.is_str() && is_type_lang_item(cx, arg_ty, LangItem::String)
}
fn is_slice_and_vec(cx: &LateContext<'_>, arg_ty: Ty<'_>, original_arg_ty: Ty<'_>) -> bool {
(original_arg_ty.is_slice() || original_arg_ty.is_array() || original_arg_ty.is_array_slice())
&& is_type_diagnostic_item(cx, arg_ty, sym::Vec)
}
// This function will check the following:
// 1. The argument is a non-mutable reference.
// 2. It calls `to_owned()`, `to_string()` or `to_vec()`.
// 3. That the method is called on `String` or on `Vec` (only types supported for the moment).
fn check_if_applicable_to_argument<'tcx>(cx: &LateContext<'tcx>, arg: &Expr<'tcx>) {
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = arg.kind
&& let ExprKind::MethodCall(method_path, caller, &[], _) = expr.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& let method_name = method_path.ident.name.as_str()
&& match method_name {
"to_owned" => cx.tcx.is_diagnostic_item(sym::to_owned_method, method_def_id),
"to_string" => cx.tcx.is_diagnostic_item(sym::to_string_method, method_def_id),
"to_vec" => cx
.tcx
.impl_of_method(method_def_id)
.filter(|&impl_did| cx.tcx.type_of(impl_did).instantiate_identity().is_slice())
.is_some(),
_ => false,
}
&& let original_arg_ty = cx.typeck_results().node_type(caller.hir_id).peel_refs()
&& let arg_ty = cx.typeck_results().expr_ty(arg)
&& let ty::Ref(_, arg_ty, Mutability::Not) = arg_ty.kind()
// FIXME: try to fix `can_change_type` to make it work in this case.
// && can_change_type(cx, caller, *arg_ty)
&& let arg_ty = arg_ty.peel_refs()
// For now we limit this lint to `String` and `Vec`.
&& (is_str_and_string(cx, arg_ty, original_arg_ty) || is_slice_and_vec(cx, arg_ty, original_arg_ty))
&& let Some(snippet) = snippet_opt(cx, caller.span)
{
span_lint_and_sugg(
cx,
UNNECESSARY_TO_OWNED,
arg.span,
&format!("unnecessary use of `{method_name}`"),
"replace it with",
if original_arg_ty.is_array() {
format!("{snippet}.as_slice()")
} else {
snippet
},
Applicability::MaybeIncorrect,
);
}
}
// In std "map types", the getters all expect a `Borrow<Key>` generic argument. So in here, we
// check that:
// 1. This is a method with only one argument that doesn't come from a trait.
// 2. That it has `Borrow` in its generic predicates.
// 3. `Self` is a std "map type" (ie `HashSet`, `HashMap`, BTreeSet`, `BTreeMap`).
fn check_borrow_predicate<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if let ExprKind::MethodCall(_, caller, &[arg], _) = expr.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& cx.tcx.trait_of_item(method_def_id).is_none()
&& let Some(borrow_id) = cx.tcx.get_diagnostic_item(sym::Borrow)
&& cx.tcx.predicates_of(method_def_id).predicates.iter().any(|(pred, _)| {
if let ClauseKind::Trait(trait_pred) = pred.kind().skip_binder()
&& trait_pred.polarity == ImplPolarity::Positive
&& trait_pred.trait_ref.def_id == borrow_id
{
true
} else {
false
}
})
&& let caller_ty = cx.typeck_results().expr_ty(caller)
// For now we limit it to "map types".
&& is_a_std_map_type(cx, caller_ty)
{
check_if_applicable_to_argument(cx, &arg);
}
}

View file

@ -2,8 +2,8 @@ use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_from_proc_macro; use clippy_utils::is_from_proc_macro;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{walk_item, Visitor}; use rustc_hir::intravisit::{walk_item, walk_trait_item, Visitor};
use rustc_hir::{GenericParamKind, HirId, Item, ItemKind, ItemLocalId, Node, Pat, PatKind}; use rustc_hir::{GenericParamKind, HirId, Item, ItemKind, ItemLocalId, Node, Pat, PatKind, TraitItem, UsePath};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
@ -53,7 +53,7 @@ impl MinIdentChars {
&& str.len() <= self.min_ident_chars_threshold as usize && str.len() <= self.min_ident_chars_threshold as usize
&& !str.starts_with('_') && !str.starts_with('_')
&& !str.is_empty() && !str.is_empty()
&& self.allowed_idents_below_min_chars.get(&str.to_owned()).is_none() && !self.allowed_idents_below_min_chars.contains(str)
} }
} }
@ -66,6 +66,14 @@ impl LateLintPass<'_> for MinIdentChars {
walk_item(&mut IdentVisitor { conf: self, cx }, item); walk_item(&mut IdentVisitor { conf: self, cx }, item);
} }
fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) {
if self.min_ident_chars_threshold == 0 {
return;
}
walk_trait_item(&mut IdentVisitor { conf: self, cx }, item);
}
// This is necessary as `Node::Pat`s are not visited in `visit_id`. :/ // This is necessary as `Node::Pat`s are not visited in `visit_id`. :/
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) { fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
if let PatKind::Binding(_, _, ident, ..) = pat.kind if let PatKind::Binding(_, _, ident, ..) = pat.kind
@ -105,11 +113,26 @@ impl Visitor<'_> for IdentVisitor<'_, '_> {
let str = ident.as_str(); let str = ident.as_str();
if conf.is_ident_too_short(cx, str, ident.span) { if conf.is_ident_too_short(cx, str, ident.span) {
if let Node::Item(item) = node // Check whether the node is part of a `use` statement. We don't want to emit a warning if the user
&& let ItemKind::Use(..) = item.kind // has no control over the type.
let usenode = opt_as_use_node(node).or_else(|| {
cx.tcx
.hir()
.parent_iter(hir_id)
.find_map(|(_, node)| opt_as_use_node(node))
});
// If the name of the identifier is the same as the one of the imported item, this means that we
// found a `use foo::bar`. We can early-return to not emit the warning.
// If however the identifier is different, this means it is an alias (`use foo::bar as baz`). In
// this case, we need to emit the warning for `baz`.
if let Some(imported_item_path) = usenode
&& let Some(Res::Def(_, imported_item_defid)) = imported_item_path.res.first()
&& cx.tcx.item_name(*imported_item_defid).as_str() == str
{ {
return; return;
} }
// `struct Awa<T>(T)` // `struct Awa<T>(T)`
// ^ // ^
if let Node::PathSegment(path) = node { if let Node::PathSegment(path) = node {
@ -160,3 +183,16 @@ fn emit_min_ident_chars(conf: &MinIdentChars, cx: &impl LintContext, ident: &str
}; };
span_lint(cx, MIN_IDENT_CHARS, span, &help); span_lint(cx, MIN_IDENT_CHARS, span, &help);
} }
/// Attempt to convert the node to an [`ItemKind::Use`] node.
///
/// If it is, return the [`UsePath`] contained within.
fn opt_as_use_node(node: Node<'_>) -> Option<&'_ UsePath<'_>> {
if let Node::Item(item) = node
&& let ItemKind::Use(path, _) = item.kind
{
Some(path)
} else {
None
}
}

View file

@ -0,0 +1,84 @@
use rustc_ast::visit::FnKind;
use rustc_ast::{NodeId, WherePredicate};
use rustc_data_structures::fx::FxHashMap;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::source::snippet_opt;
declare_clippy_lint! {
/// ### What it does
/// Check if a generic is defined both in the bound predicate and in the `where` clause.
///
/// ### Why is this bad?
/// It can be confusing for developers when seeing bounds for a generic in multiple places.
///
/// ### Example
/// ```no_run
/// fn ty<F: std::fmt::Debug>(a: F)
/// where
/// F: Sized,
/// {}
/// ```
/// Use instead:
/// ```no_run
/// fn ty<F>(a: F)
/// where
/// F: Sized + std::fmt::Debug,
/// {}
/// ```
#[clippy::version = "1.77.0"]
pub MULTIPLE_BOUND_LOCATIONS,
suspicious,
"defining generic bounds in multiple locations"
}
declare_lint_pass!(MultipleBoundLocations => [MULTIPLE_BOUND_LOCATIONS]);
impl EarlyLintPass for MultipleBoundLocations {
fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, _: Span, _: NodeId) {
if let FnKind::Fn(_, _, _, _, generics, _) = kind
&& !generics.params.is_empty()
&& !generics.where_clause.predicates.is_empty()
{
let mut generic_params_with_bounds = FxHashMap::default();
for param in &generics.params {
if !param.bounds.is_empty() {
generic_params_with_bounds.insert(param.ident.name.as_str(), param.ident.span);
}
}
for clause in &generics.where_clause.predicates {
match clause {
WherePredicate::BoundPredicate(pred) => {
if (!pred.bound_generic_params.is_empty() || !pred.bounds.is_empty())
&& let Some(name) = snippet_opt(cx, pred.bounded_ty.span)
&& let Some(bound_span) = generic_params_with_bounds.get(name.as_str())
{
emit_lint(cx, *bound_span, pred.bounded_ty.span);
}
},
WherePredicate::RegionPredicate(pred) => {
if !pred.bounds.is_empty()
&& let Some(bound_span) = generic_params_with_bounds.get(&pred.lifetime.ident.name.as_str())
{
emit_lint(cx, *bound_span, pred.lifetime.ident.span);
}
},
WherePredicate::EqPredicate(_) => {},
}
}
}
}
}
fn emit_lint(cx: &EarlyContext<'_>, bound_span: Span, where_span: Span) {
span_lint(
cx,
MULTIPLE_BOUND_LOCATIONS,
vec![bound_span, where_span],
"bound is defined in more than one place",
);
}

View file

@ -6,8 +6,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::{ use clippy_utils::{
get_parent_node, higher, is_else_clause, is_expn_of, peel_blocks, peel_blocks_with_stmt, span_extract_comment, higher, is_else_clause, is_expn_of, peel_blocks, peel_blocks_with_stmt, span_extract_comment, SpanlessEq,
SpanlessEq,
}; };
use rustc_ast::ast::LitKind; use rustc_ast::ast::LitKind;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -138,8 +137,8 @@ fn condition_needs_parentheses(e: &Expr<'_>) -> bool {
fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool { fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool {
matches!( matches!(
get_parent_node(cx.tcx, id), cx.tcx.parent_hir_node(id),
Some(Node::Stmt(..) | Node::Block(Block { stmts: &[], .. })) Node::Stmt(..) | Node::Block(Block { stmts: &[], .. })
) )
} }

View file

@ -2,7 +2,7 @@ use super::needless_pass_by_value::requires_exact_signature;
use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::visitors::for_each_expr_with_closures; use clippy_utils::visitors::for_each_expr_with_closures;
use clippy_utils::{get_parent_node, inherits_cfg, is_from_proc_macro, is_self}; use clippy_utils::{inherits_cfg, is_from_proc_macro, is_self};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::FnKind;
@ -236,11 +236,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
// #11182; do not lint if mutability is required elsewhere // #11182; do not lint if mutability is required elsewhere
if let ExprKind::Path(..) = expr.kind if let ExprKind::Path(..) = expr.kind
&& let Some(parent) = get_parent_node(cx.tcx, expr.hir_id)
&& let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(expr).kind() && let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(expr).kind()
&& let Some(def_id) = def_id.as_local() && let Some(def_id) = def_id.as_local()
{ {
if let Node::Expr(e) = parent if let Node::Expr(e) = cx.tcx.parent_hir_node(expr.hir_id)
&& let ExprKind::Call(call, _) = e.kind && let ExprKind::Call(call, _) = e.kind
&& call.hir_id == expr.hir_id && call.hir_id == expr.hir_id
{ {

View file

@ -75,10 +75,6 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind { if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind {
let name = impl_item.ident.name; let name = impl_item.ident.name;
let id = impl_item.owner_id; let id = impl_item.owner_id;
if sig.header.constness == hir::Constness::Const {
// can't be implemented by default
return;
}
if sig.header.unsafety == hir::Unsafety::Unsafe { if sig.header.unsafety == hir::Unsafety::Unsafe {
// can't be implemented for unsafe new // can't be implemented for unsafe new
return; return;

View file

@ -1,12 +1,12 @@
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use clippy_utils::ty::has_drop; use clippy_utils::ty::has_drop;
use clippy_utils::{any_parent_is_automatically_derived, get_parent_node, is_lint_allowed, path_to_local, peel_blocks}; use clippy_utils::{any_parent_is_automatically_derived, is_lint_allowed, path_to_local, peel_blocks};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::{ use rustc_hir::{
is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, HirId, HirIdMap, ItemKind, Node, PatKind, Stmt, is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, HirId, HirIdMap, ItemKind, LocalSource, Node, PatKind,
StmtKind, UnsafeSource, Stmt, StmtKind, UnsafeSource,
}; };
use rustc_infer::infer::TyCtxtInferExt as _; use rustc_infer::infer::TyCtxtInferExt as _;
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -43,10 +43,6 @@ declare_clippy_lint! {
/// executed. However, as they have no effect and shouldn't be used further on, all they /// executed. However, as they have no effect and shouldn't be used further on, all they
/// do is make the code less readable. /// do is make the code less readable.
/// ///
/// ### Known problems
/// Further usage of this variable is not checked, which can lead to false positives if it is
/// used later in the code.
///
/// ### Example /// ### Example
/// ```rust,ignore /// ```rust,ignore
/// let _i_serve_no_purpose = 1; /// let _i_serve_no_purpose = 1;
@ -144,7 +140,7 @@ impl NoEffect {
for parent in cx.tcx.hir().parent_iter(stmt.hir_id) { for parent in cx.tcx.hir().parent_iter(stmt.hir_id) {
if let Node::Item(item) = parent.1 if let Node::Item(item) = parent.1
&& let ItemKind::Fn(..) = item.kind && let ItemKind::Fn(..) = item.kind
&& let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id) && let Node::Block(block) = cx.tcx.parent_hir_node(stmt.hir_id)
&& let [.., final_stmt] = block.stmts && let [.., final_stmt] = block.stmts
&& final_stmt.hir_id == stmt.hir_id && final_stmt.hir_id == stmt.hir_id
{ {
@ -180,6 +176,7 @@ impl NoEffect {
} }
} else if let StmtKind::Local(local) = stmt.kind { } else if let StmtKind::Local(local) = stmt.kind {
if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id) if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id)
&& !matches!(local.source, LocalSource::AsyncFn)
&& let Some(init) = local.init && let Some(init) = local.init
&& local.els.is_none() && local.els.is_none()
&& !local.pat.span.from_expansion() && !local.pat.span.from_expansion()

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::ty::implements_trait; use clippy_utils::ty::implements_trait;
use clippy_utils::{get_parent_node, is_res_lang_ctor, last_path_segment, path_res, std_or_core}; use clippy_utils::{is_res_lang_ctor, last_path_segment, path_res, std_or_core};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def_id::LocalDefId; use rustc_hir::def_id::LocalDefId;
use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, LangItem, Node, UnOp}; use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, LangItem, Node, UnOp};
@ -112,7 +112,7 @@ declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL
impl LateLintPass<'_> for NonCanonicalImpls { impl LateLintPass<'_> for NonCanonicalImpls {
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) { fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
let Some(Node::Item(item)) = get_parent_node(cx.tcx, impl_item.hir_id()) else { let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id()) else {
return; return;
}; };
let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) else { let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) else {

View file

@ -119,7 +119,6 @@ impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> {
// this list contains lists of names that are allowed to be similar // this list contains lists of names that are allowed to be similar
// the assumption is that no name is ever contained in multiple lists. // the assumption is that no name is ever contained in multiple lists.
#[rustfmt::skip]
const ALLOWED_TO_BE_SIMILAR: &[&[&str]] = &[ const ALLOWED_TO_BE_SIMILAR: &[&[&str]] = &[
&["parsed", "parser"], &["parsed", "parser"],
&["lhs", "rhs"], &["lhs", "rhs"],
@ -132,6 +131,14 @@ const ALLOWED_TO_BE_SIMILAR: &[&[&str]] = &[
&["iter", "item"], &["iter", "item"],
]; ];
/// Characters that look visually similar
const SIMILAR_CHARS: &[(char, char)] = &[('l', 'i'), ('l', '1'), ('i', '1'), ('u', 'v')];
/// Return true if two characters are visually similar
fn chars_are_similar(a: char, b: char) -> bool {
a == b || SIMILAR_CHARS.contains(&(a, b)) || SIMILAR_CHARS.contains(&(b, a))
}
struct SimilarNamesNameVisitor<'a, 'tcx, 'b>(&'b mut SimilarNamesLocalVisitor<'a, 'tcx>); struct SimilarNamesNameVisitor<'a, 'tcx, 'b>(&'b mut SimilarNamesLocalVisitor<'a, 'tcx>);
impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> { impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> {
@ -189,7 +196,6 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
} }
} }
#[expect(clippy::too_many_lines)]
fn check_ident(&mut self, ident: Ident) { fn check_ident(&mut self, ident: Ident) {
let interned_name = ident.name.as_str(); let interned_name = ident.name.as_str();
if interned_name.chars().any(char::is_uppercase) { if interned_name.chars().any(char::is_uppercase) {
@ -219,71 +225,28 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
if allowed_to_be_similar(interned_name, existing_name.exemptions) { if allowed_to_be_similar(interned_name, existing_name.exemptions) {
continue; continue;
} }
match existing_name.len.cmp(&count) {
Ordering::Greater => {
if existing_name.len - count != 1
|| levenstein_not_1(interned_name, existing_name.interned.as_str())
{
continue;
}
},
Ordering::Less => {
if count - existing_name.len != 1
|| levenstein_not_1(existing_name.interned.as_str(), interned_name)
{
continue;
}
},
Ordering::Equal => {
let mut interned_chars = interned_name.chars();
let interned_str = existing_name.interned.as_str();
let mut existing_chars = interned_str.chars();
let first_i = interned_chars.next().expect("we know we have at least one char");
let first_e = existing_chars.next().expect("we know we have at least one char");
let eq_or_numeric = |(a, b): (char, char)| a == b || a.is_numeric() && b.is_numeric();
if eq_or_numeric((first_i, first_e)) { let existing_str = existing_name.interned.as_str();
let last_i = interned_chars.next_back().expect("we know we have at least two chars");
let last_e = existing_chars.next_back().expect("we know we have at least two chars"); // The first char being different is usually enough to set identifiers apart, as long
if eq_or_numeric((last_i, last_e)) { // as the characters aren't too similar.
if interned_chars if !chars_are_similar(
.zip(existing_chars) interned_name.chars().next().expect("len >= 1"),
.filter(|&ie| !eq_or_numeric(ie)) existing_str.chars().next().expect("len >= 1"),
.count() ) {
!= 1
{
continue; continue;
} }
} else {
let second_last_i = interned_chars let dissimilar = match existing_name.len.cmp(&count) {
.next_back() Ordering::Greater => existing_name.len - count != 1 || levenstein_not_1(interned_name, existing_str),
.expect("we know we have at least three chars"); Ordering::Less => count - existing_name.len != 1 || levenstein_not_1(existing_str, interned_name),
let second_last_e = existing_chars Ordering::Equal => Self::equal_length_strs_not_similar(interned_name, existing_str),
.next_back() };
.expect("we know we have at least three chars");
if !eq_or_numeric((second_last_i, second_last_e)) if dissimilar {
|| second_last_i == '_'
|| !interned_chars.zip(existing_chars).all(eq_or_numeric)
{
// allowed similarity foo_x, foo_y
// or too many chars differ (foo_x, boo_y) or (foox, booy)
continue; continue;
} }
}
} else {
let second_i = interned_chars.next().expect("we know we have at least two chars");
let second_e = existing_chars.next().expect("we know we have at least two chars");
if !eq_or_numeric((second_i, second_e))
|| second_i == '_'
|| !interned_chars.zip(existing_chars).all(eq_or_numeric)
{
// allowed similarity x_foo, y_foo
// or too many chars differ (x_foo, y_boo) or (xfoo, yboo)
continue;
}
}
},
}
span_lint_and_then( span_lint_and_then(
self.0.cx, self.0.cx,
SIMILAR_NAMES, SIMILAR_NAMES,
@ -302,6 +265,57 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
len: count, len: count,
}); });
} }
fn equal_length_strs_not_similar(interned_name: &str, existing_name: &str) -> bool {
let mut interned_chars = interned_name.chars();
let mut existing_chars = existing_name.chars();
let first_i = interned_chars.next().expect("we know we have at least one char");
let first_e = existing_chars.next().expect("we know we have at least one char");
let eq_or_numeric = |(a, b): (char, char)| a == b || a.is_numeric() && b.is_numeric();
if eq_or_numeric((first_i, first_e)) {
let last_i = interned_chars.next_back().expect("we know we have at least two chars");
let last_e = existing_chars.next_back().expect("we know we have at least two chars");
if eq_or_numeric((last_i, last_e)) {
if interned_chars
.zip(existing_chars)
.filter(|&ie| !eq_or_numeric(ie))
.count()
!= 1
{
return true;
}
} else {
let second_last_i = interned_chars
.next_back()
.expect("we know we have at least three chars");
let second_last_e = existing_chars
.next_back()
.expect("we know we have at least three chars");
if !eq_or_numeric((second_last_i, second_last_e))
|| second_last_i == '_'
|| !interned_chars.zip(existing_chars).all(eq_or_numeric)
{
// allowed similarity foo_x, foo_y
// or too many chars differ (foo_x, boo_y) or (foox, booy)
return true;
}
}
} else {
let second_i = interned_chars.next().expect("we know we have at least two chars");
let second_e = existing_chars.next().expect("we know we have at least two chars");
if !eq_or_numeric((second_i, second_e))
|| second_i == '_'
|| !interned_chars.zip(existing_chars).all(eq_or_numeric)
{
// allowed similarity x_foo, y_foo
// or too many chars differ (x_foo, y_boo) or (xfoo, yboo)
return true;
}
}
false
}
} }
impl<'a, 'b> SimilarNamesLocalVisitor<'a, 'b> { impl<'a, 'b> SimilarNamesLocalVisitor<'a, 'b> {

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{get_expr_use_or_unification_node, get_parent_node, path_def_id, path_to_local, path_to_local_id}; use clippy_utils::{get_expr_use_or_unification_node, path_def_id, path_to_local, path_to_local_id};
use core::cell::Cell; use core::cell::Cell;
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -227,24 +227,24 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
} }
// `skip_params` is either `0` or `1` to skip the `self` parameter in trait functions. // `skip_params` is either `0` or `1` to skip the `self` parameter in trait functions.
// It can't be renamed, and it can't be removed without removing it from multiple functions. // It can't be renamed, and it can't be removed without removing it from multiple functions.
let (fn_id, fn_kind, skip_params) = match get_parent_node(cx.tcx, body.value.hir_id) { let (fn_id, fn_kind, skip_params) = match cx.tcx.parent_hir_node(body.value.hir_id) {
Some(Node::Item(i)) => (i.owner_id.to_def_id(), FnKind::Fn, 0), Node::Item(i) => (i.owner_id.to_def_id(), FnKind::Fn, 0),
Some(Node::TraitItem(&TraitItem { Node::TraitItem(&TraitItem {
kind: TraitItemKind::Fn(ref sig, _), kind: TraitItemKind::Fn(ref sig, _),
owner_id, owner_id,
.. ..
})) => ( }) => (
owner_id.to_def_id(), owner_id.to_def_id(),
FnKind::TraitFn, FnKind::TraitFn,
usize::from(sig.decl.implicit_self.has_implicit_self()), usize::from(sig.decl.implicit_self.has_implicit_self()),
), ),
Some(Node::ImplItem(&ImplItem { Node::ImplItem(&ImplItem {
kind: ImplItemKind::Fn(ref sig, _), kind: ImplItemKind::Fn(ref sig, _),
owner_id, owner_id,
.. ..
})) => { }) => {
#[allow(trivial_casts)] #[allow(trivial_casts)]
if let Some(Node::Item(item)) = get_parent_node(cx.tcx, owner_id.into()) if let Node::Item(item) = cx.tcx.parent_hir_node(owner_id.into())
&& let Some(trait_ref) = cx && let Some(trait_ref) = cx
.tcx .tcx
.impl_trait_ref(item.owner_id) .impl_trait_ref(item.owner_id)

View file

@ -8,7 +8,6 @@ use rustc_span::Span;
use super::DOUBLE_COMPARISONS; use super::DOUBLE_COMPARISONS;
#[expect(clippy::similar_names)]
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) { pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) {
let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) { let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) {
(ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => { (ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => {

View file

@ -11,7 +11,7 @@ use rustc_middle::ty::{self, Ty};
use super::OP_REF; use super::OP_REF;
#[expect(clippy::similar_names, clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
pub(crate) fn check<'tcx>( pub(crate) fn check<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>, e: &'tcx Expr<'_>,

View file

@ -1,6 +1,6 @@
use clippy_config::types::PubUnderscoreFieldsBehaviour; use clippy_config::types::PubUnderscoreFieldsBehaviour;
use clippy_utils::attrs::is_doc_hidden; use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::is_path_lang_item; use clippy_utils::is_path_lang_item;
use rustc_hir::{FieldDef, Item, ItemKind, LangItem}; use rustc_hir::{FieldDef, Item, ItemKind, LangItem};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -69,13 +69,15 @@ impl<'tcx> LateLintPass<'tcx> for PubUnderscoreFields {
// We ignore fields that are `PhantomData`. // We ignore fields that are `PhantomData`.
&& !is_path_lang_item(cx, field.ty, LangItem::PhantomData) && !is_path_lang_item(cx, field.ty, LangItem::PhantomData)
{ {
span_lint_and_help( span_lint_hir_and_then(
cx, cx,
PUB_UNDERSCORE_FIELDS, PUB_UNDERSCORE_FIELDS,
field.hir_id,
field.vis_span.to(field.ident.span), field.vis_span.to(field.ident.span),
"field marked as public but also inferred as unused because it's prefixed with `_`", "field marked as public but also inferred as unused because it's prefixed with `_`",
None, |diag| {
"consider removing the underscore, or making the field private", diag.help("consider removing the underscore, or making the field private");
},
); );
} }
} }

View file

@ -6,9 +6,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{ use clippy_utils::{
eq_expr_value, get_parent_node, higher, in_constant, is_else_clause, is_lint_allowed, is_path_lang_item, eq_expr_value, higher, in_constant, is_else_clause, is_lint_allowed, is_path_lang_item, is_res_lang_ctor,
is_res_lang_ctor, pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks, pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
peel_blocks_with_stmt, span_contains_comment, span_contains_comment,
}; };
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::Res; use rustc_hir::def::Res;
@ -74,12 +74,12 @@ impl QuestionMark {
enum IfBlockType<'hir> { enum IfBlockType<'hir> {
/// An `if x.is_xxx() { a } else { b } ` expression. /// An `if x.is_xxx() { a } else { b } ` expression.
/// ///
/// Contains: caller (x), caller_type, call_sym (is_xxx), if_then (a), if_else (b) /// Contains: `caller (x), caller_type, call_sym (is_xxx), if_then (a), if_else (b)`
IfIs(&'hir Expr<'hir>, Ty<'hir>, Symbol, &'hir Expr<'hir>), IfIs(&'hir Expr<'hir>, Ty<'hir>, Symbol, &'hir Expr<'hir>),
/// An `if let Xxx(a) = b { c } else { d }` expression. /// An `if let Xxx(a) = b { c } else { d }` expression.
/// ///
/// Contains: let_pat_qpath (Xxx), let_pat_type, let_pat_sym (a), let_expr (b), if_then (c), /// Contains: `let_pat_qpath (Xxx), let_pat_type, let_pat_sym (a), let_expr (b), if_then (c),
/// if_else (d) /// if_else (d)`
IfLet( IfLet(
Res, Res,
Ty<'hir>, Ty<'hir>,
@ -289,7 +289,7 @@ impl QuestionMark {
{ {
let mut applicability = Applicability::MachineApplicable; let mut applicability = Applicability::MachineApplicable;
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability); let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
let requires_semi = matches!(get_parent_node(cx.tcx, expr.hir_id), Some(Node::Stmt(_))); let requires_semi = matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(_));
let sugg = format!( let sugg = format!(
"{receiver_str}{}?{}", "{receiver_str}{}?{}",
if by_ref == ByRef::Yes { ".as_ref()" } else { "" }, if by_ref == ByRef::Yes { ".as_ref()" } else { "" },

View file

@ -200,11 +200,11 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
hint = hint.asyncify(); hint = hint.asyncify();
} }
let is_in_fn_call_arg = let is_in_fn_call_arg = if let Node::Expr(expr) = cx.tcx.parent_hir_node(expr.hir_id) {
clippy_utils::get_parent_node(cx.tcx, expr.hir_id).is_some_and(|x| match x { matches!(expr.kind, hir::ExprKind::Call(_, _))
Node::Expr(expr) => matches!(expr.kind, hir::ExprKind::Call(_, _)), } else {
_ => false, false
}); };
// avoid clippy::double_parens // avoid clippy::double_parens
if !is_in_fn_call_arg { if !is_in_fn_call_arg {

View file

@ -47,7 +47,6 @@ struct ExistingName {
} }
impl<'tcx> LateLintPass<'tcx> for SameNameMethod { impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
#[expect(clippy::too_many_lines)]
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
let mut map = FxHashMap::<Res, ExistingName>::default(); let mut map = FxHashMap::<Res, ExistingName>::default();
@ -75,8 +74,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
match of_trait { match of_trait {
Some(trait_ref) => { Some(trait_ref) => {
let mut methods_in_trait: BTreeSet<Symbol> = let mut methods_in_trait: BTreeSet<Symbol> = if let Node::TraitRef(TraitRef { path, .. }) =
if let Node::TraitRef(TraitRef { path, .. }) =
cx.tcx.hir_node(trait_ref.hir_ref_id) cx.tcx.hir_node(trait_ref.hir_ref_id)
&& let Res::Def(DefKind::Trait, did) = path.res && let Res::Def(DefKind::Trait, did) = path.res
{ {

View file

@ -1,11 +1,10 @@
use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::{is_from_proc_macro, is_in_test_function}; use clippy_utils::{is_from_proc_macro, is_in_test_function};
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::{FxIndexMap, IndexEntry};
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalDefId; use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; use rustc_hir::{Expr, ExprKind, HirId, Node};
use rustc_hir::{Body, Expr, ExprKind, FnDecl};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::hir::nested_filter::OnlyBodies;
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::Span; use rustc_span::Span;
@ -54,82 +53,93 @@ declare_clippy_lint! {
} }
impl_lint_pass!(SingleCallFn => [SINGLE_CALL_FN]); impl_lint_pass!(SingleCallFn => [SINGLE_CALL_FN]);
#[derive(Debug, Clone)]
pub enum CallState {
Once { call_site: Span },
Multiple,
}
#[derive(Clone)] #[derive(Clone)]
pub struct SingleCallFn { pub struct SingleCallFn {
pub avoid_breaking_exported_api: bool, pub avoid_breaking_exported_api: bool,
pub def_id_to_usage: FxHashMap<LocalDefId, (Span, Vec<Span>)>, pub def_id_to_usage: FxIndexMap<LocalDefId, CallState>,
}
impl SingleCallFn {
fn is_function_allowed(
&self,
cx: &LateContext<'_>,
fn_def_id: LocalDefId,
fn_hir_id: HirId,
fn_span: Span,
) -> bool {
(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(fn_def_id))
|| in_external_macro(cx.sess(), fn_span)
|| cx
.tcx
.hir()
.maybe_body_owned_by(fn_def_id)
.map(|body| cx.tcx.hir().body(body))
.map_or(true, |body| is_in_test_function(cx.tcx, body.value.hir_id))
|| match cx.tcx.hir_node(fn_hir_id) {
Node::Item(item) => is_from_proc_macro(cx, item),
Node::ImplItem(item) => is_from_proc_macro(cx, item),
Node::TraitItem(item) => is_from_proc_macro(cx, item),
_ => true,
}
}
}
/// Whether a called function is a kind of item that the lint cares about.
/// For example, calling an `extern "C" { fn fun(); }` only once is totally fine and does not
/// to be considered.
fn is_valid_item_kind(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
matches!(
cx.tcx.hir_node(cx.tcx.local_def_id_to_hir_id(def_id)),
Node::Item(_) | Node::ImplItem(_) | Node::TraitItem(_)
)
} }
impl<'tcx> LateLintPass<'tcx> for SingleCallFn { impl<'tcx> LateLintPass<'tcx> for SingleCallFn {
fn check_fn( fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) {
&mut self, if let ExprKind::Path(qpath) = expr.kind
cx: &LateContext<'tcx>, && let res = cx.qpath_res(&qpath, expr.hir_id)
kind: FnKind<'tcx>, && let Some(call_def_id) = res.opt_def_id()
_: &'tcx FnDecl<'_>, && let Some(def_id) = call_def_id.as_local()
body: &'tcx Body<'_>, && let DefKind::Fn | DefKind::AssocFn = cx.tcx.def_kind(def_id)
span: Span, && is_valid_item_kind(cx, def_id)
def_id: LocalDefId,
) {
if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id)
|| in_external_macro(cx.sess(), span)
|| is_in_test_function(cx.tcx, body.value.hir_id)
|| is_from_proc_macro(cx, &(&kind, body, cx.tcx.local_def_id_to_hir_id(def_id), span))
{ {
return; match self.def_id_to_usage.entry(def_id) {
IndexEntry::Occupied(mut entry) => {
if let CallState::Once { .. } = entry.get() {
entry.insert(CallState::Multiple);
}
},
IndexEntry::Vacant(entry) => {
entry.insert(CallState::Once { call_site: expr.span });
},
}
} }
self.def_id_to_usage.insert(def_id, (span, vec![]));
} }
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
let mut v = FnUsageVisitor {
cx,
def_id_to_usage: &mut self.def_id_to_usage,
};
cx.tcx.hir().visit_all_item_likes_in_crate(&mut v);
for (&def_id, usage) in &self.def_id_to_usage { for (&def_id, usage) in &self.def_id_to_usage {
let single_call_fn_span = usage.0; if let CallState::Once { call_site } = *usage
if let [caller_span] = *usage.1 { && let fn_hir_id = cx.tcx.local_def_id_to_hir_id(def_id)
&& let fn_span = cx.tcx.hir().span_with_body(fn_hir_id)
&& !self.is_function_allowed(cx, def_id, fn_hir_id, fn_span)
{
span_lint_hir_and_then( span_lint_hir_and_then(
cx, cx,
SINGLE_CALL_FN, SINGLE_CALL_FN,
cx.tcx.local_def_id_to_hir_id(def_id), fn_hir_id,
single_call_fn_span, fn_span,
"this function is only used once", "this function is only used once",
|diag| { |diag| {
diag.span_help(caller_span, "used here"); diag.span_note(call_site, "used here");
}, },
); );
} }
} }
} }
} }
struct FnUsageVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
def_id_to_usage: &'a mut FxHashMap<LocalDefId, (Span, Vec<Span>)>,
}
impl<'a, 'tcx> Visitor<'tcx> for FnUsageVisitor<'a, 'tcx> {
type NestedFilter = OnlyBodies;
fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir()
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
let Self { cx, .. } = *self;
if let ExprKind::Path(qpath) = expr.kind
&& let res = cx.qpath_res(&qpath, expr.hir_id)
&& let Some(call_def_id) = res.opt_def_id()
&& let Some(def_id) = call_def_id.as_local()
&& let Some(usage) = self.def_id_to_usage.get_mut(&def_id)
{
usage.1.push(expr.span);
}
walk_expr(self, expr);
}
}

View file

@ -62,7 +62,7 @@ declare_lint_pass!(SlowVectorInit => [SLOW_VECTOR_INITIALIZATION]);
/// assigned to a variable. For example, `let mut vec = Vec::with_capacity(0)` or /// assigned to a variable. For example, `let mut vec = Vec::with_capacity(0)` or
/// `vec = Vec::with_capacity(0)` /// `vec = Vec::with_capacity(0)`
struct VecAllocation<'tcx> { struct VecAllocation<'tcx> {
/// HirId of the variable /// `HirId` of the variable
local_id: HirId, local_id: HirId,
/// Reference to the expression which allocates the vector /// Reference to the expression which allocates the vector

View file

@ -1,8 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::match_libc_symbol;
use clippy_utils::source::snippet_with_context; use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::visitors::is_expr_unsafe; use clippy_utils::visitors::is_expr_unsafe;
use clippy_utils::{get_parent_node, match_libc_symbol};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, LangItem, Node, UnsafeSource}; use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, LangItem, Node, UnsafeSource};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -50,12 +50,12 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings {
&& path.ident.name == sym::as_ptr && path.ident.name == sym::as_ptr
{ {
let ctxt = expr.span.ctxt(); let ctxt = expr.span.ctxt();
let span = match get_parent_node(cx.tcx, expr.hir_id) { let span = match cx.tcx.parent_hir_node(expr.hir_id) {
Some(Node::Block(&Block { Node::Block(&Block {
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
span, span,
.. ..
})) if span.ctxt() == ctxt && !is_expr_unsafe(cx, self_arg) => span, }) if span.ctxt() == ctxt && !is_expr_unsafe(cx, self_arg) => span,
_ => expr.span, _ => expr.span,
}; };

View file

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::implements_trait;
use rustc_hir::{Impl, Item, ItemKind}; use rustc_hir::{Impl, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
@ -53,6 +54,8 @@ impl<'tcx> LateLintPass<'tcx> for ToStringTraitImpl {
}) = it.kind }) = it.kind
&& let Some(trait_did) = trait_ref.trait_def_id() && let Some(trait_did) = trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::ToString, trait_did) && cx.tcx.is_diagnostic_item(sym::ToString, trait_did)
&& let Some(display_did) = cx.tcx.get_diagnostic_item(sym::Display)
&& !implements_trait(cx, cx.tcx.type_of(it.owner_id).instantiate_identity(), display_did, &[])
{ {
span_lint_and_help( span_lint_and_help(
cx, cx,

View file

@ -153,10 +153,7 @@ fn all_bindings_are_for_conv<'tcx>(
let Some(locals) = locals.iter().map(|e| path_to_local(e)).collect::<Option<Vec<_>>>() else { let Some(locals) = locals.iter().map(|e| path_to_local(e)).collect::<Option<Vec<_>>>() else {
return false; return false;
}; };
let local_parents = locals let local_parents = locals.iter().map(|l| cx.tcx.parent_hir_node(*l)).collect::<Vec<_>>();
.iter()
.map(|l| cx.tcx.parent_hir_node(*l))
.collect::<Vec<_>>();
local_parents local_parents
.iter() .iter()

View file

@ -1,9 +1,9 @@
use std::ops::ControlFlow; use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_and_help; 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::source::walk_span_to_context;
use clippy_utils::visitors::{for_each_expr_with_closures, Descend}; use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
use clippy_utils::{get_parent_node, is_lint_allowed};
use hir::HirId; use hir::HirId;
use rustc_data_structures::sync::Lrc; use rustc_data_structures::sync::Lrc;
use rustc_hir as hir; use rustc_hir as hir;
@ -340,16 +340,15 @@ fn block_parents_have_safety_comment(
cx: &LateContext<'_>, cx: &LateContext<'_>,
id: hir::HirId, id: hir::HirId,
) -> bool { ) -> bool {
if let Some(node) = get_parent_node(cx.tcx, id) { let (span, hir_id) = match cx.tcx.parent_hir_node(id) {
let (span, hir_id) = match node { Node::Expr(expr) => match cx.tcx.parent_hir_node(expr.hir_id) {
Node::Expr(expr) => match get_parent_node(cx.tcx, expr.hir_id) { Node::Local(hir::Local { span, hir_id, .. }) => (*span, *hir_id),
Some(Node::Local(hir::Local { span, hir_id, .. })) => (*span, *hir_id), Node::Item(hir::Item {
Some(Node::Item(hir::Item {
kind: hir::ItemKind::Const(..) | ItemKind::Static(..), kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
span, span,
owner_id, owner_id,
.. ..
})) => (*span, cx.tcx.local_def_id_to_hir_id(owner_id.def_id)), }) => (*span, cx.tcx.local_def_id_to_hir_id(owner_id.def_id)),
_ => { _ => {
if is_branchy(expr) { if is_branchy(expr) {
return false; return false;
@ -378,9 +377,6 @@ fn block_parents_have_safety_comment(
// we accept the safety comment in the line the precedes this statement. // we accept the safety comment in the line the precedes this statement.
accept_comment_above_statement accept_comment_above_statement
&& span_with_attrs_has_safety_comment(cx, span, hir_id, accept_comment_above_attributes) && span_with_attrs_has_safety_comment(cx, span, hir_id, accept_comment_above_attributes)
} else {
false
}
} }
/// Extends `span` to also include its attributes, then checks if that span has a safety comment. /// Extends `span` to also include its attributes, then checks if that span has a safety comment.
@ -449,11 +445,8 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
if item.span.ctxt() != SyntaxContext::root() { if item.span.ctxt() != SyntaxContext::root() {
return HasSafetyComment::No; return HasSafetyComment::No;
} }
if let Some(parent_node) = get_parent_node(cx.tcx, item.hir_id()) { let comment_start = match cx.tcx.parent_hir_node(item.hir_id()) {
let comment_start = match parent_node { Node::Crate(parent_mod) => comment_start_before_item_in_mod(cx, parent_mod, parent_mod.spans.inner_span, item),
Node::Crate(parent_mod) => {
comment_start_before_item_in_mod(cx, parent_mod, parent_mod.spans.inner_span, item)
},
Node::Item(parent_item) => { Node::Item(parent_item) => {
if let ItemKind::Mod(parent_mod) = &parent_item.kind { if let ItemKind::Mod(parent_mod) = &parent_item.kind {
comment_start_before_item_in_mod(cx, parent_mod, parent_item.span, item) comment_start_before_item_in_mod(cx, parent_mod, parent_item.span, item)
@ -463,7 +456,7 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
} }
}, },
Node::Stmt(stmt) => { Node::Stmt(stmt) => {
if let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id) { if let Node::Block(block) = cx.tcx.parent_hir_node(stmt.hir_id) {
walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo) walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo)
} else { } else {
// Problem getting the parent node. Pretend a comment was found. // Problem getting the parent node. Pretend a comment was found.
@ -496,7 +489,6 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
} }
}; };
} }
}
HasSafetyComment::Maybe HasSafetyComment::Maybe
} }
@ -512,8 +504,7 @@ fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> H
return HasSafetyComment::No; return HasSafetyComment::No;
} }
if let Some(parent_node) = get_parent_node(cx.tcx, hir_id) { let comment_start = match cx.tcx.parent_hir_node(hir_id) {
let comment_start = match parent_node {
Node::Block(block) => walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo), Node::Block(block) => walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo),
_ => return HasSafetyComment::Maybe, _ => return HasSafetyComment::Maybe,
}; };
@ -538,7 +529,6 @@ fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> H
} }
}; };
} }
}
HasSafetyComment::Maybe HasSafetyComment::Maybe
} }

View file

@ -1,5 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::get_parent_node;
use clippy_utils::source::snippet_with_context; use clippy_utils::source::snippet_with_context;
use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source}; use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source};
use core::ops::ControlFlow; use core::ops::ControlFlow;
@ -103,7 +102,7 @@ fn expr_needs_inferred_result<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -
return false; return false;
} }
while let Some(id) = locals_to_check.pop() { while let Some(id) = locals_to_check.pop() {
if let Some(Node::Local(l)) = get_parent_node(cx.tcx, id) { if let Node::Local(l) = cx.tcx.parent_hir_node(id) {
if !l.ty.map_or(true, |ty| matches!(ty.kind, TyKind::Infer)) { if !l.ty.map_or(true, |ty| matches!(ty.kind, TyKind::Infer)) {
return false; return false;
} }

View file

@ -58,6 +58,7 @@ impl EarlyLintPass for UnusedUnit {
&& let ctxt = block.span.ctxt() && let ctxt = block.span.ctxt()
&& stmt.span.ctxt() == ctxt && stmt.span.ctxt() == ctxt
&& expr.span.ctxt() == ctxt && expr.span.ctxt() == ctxt
&& expr.attrs.is_empty()
{ {
let sp = expr.span; let sp = expr.span;
span_lint_and_sugg( span_lint_and_sugg(

View file

@ -1005,8 +1005,6 @@ fn get_parent_local<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -
} }
fn get_parent_local_hir_id<'hir>(cx: &LateContext<'hir>, hir_id: hir::HirId) -> Option<&'hir hir::Local<'hir>> { fn get_parent_local_hir_id<'hir>(cx: &LateContext<'hir>, hir_id: hir::HirId) -> Option<&'hir hir::Local<'hir>> {
let map = cx.tcx.hir();
match cx.tcx.parent_hir_node(hir_id) { match cx.tcx.parent_hir_node(hir_id) {
hir::Node::Local(local) => Some(local), hir::Node::Local(local) => Some(local),
hir::Node::Pat(pattern) => get_parent_local_hir_id(cx, pattern.hir_id), hir::Node::Pat(pattern) => get_parent_local_hir_id(cx, pattern.hir_id),

View file

@ -4,12 +4,12 @@ use std::ops::ControlFlow;
use clippy_config::msrvs::{self, Msrv}; use clippy_config::msrvs::{self, Msrv};
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_copy; use clippy_utils::ty::is_copy;
use clippy_utils::visitors::for_each_local_use_after_expr; use clippy_utils::visitors::for_each_local_use_after_expr;
use clippy_utils::{get_parent_expr, higher, is_trait_method}; use clippy_utils::{get_parent_expr, higher, is_trait_method};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Node, PatKind}; use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Local, Mutability, Node, Pat, PatKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty; use rustc_middle::ty;
use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::layout::LayoutOf;
@ -52,6 +52,172 @@ declare_clippy_lint! {
impl_lint_pass!(UselessVec => [USELESS_VEC]); impl_lint_pass!(UselessVec => [USELESS_VEC]);
impl<'tcx> LateLintPass<'tcx> for UselessVec {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows()) else {
return;
};
// the parent callsite of this `vec!` expression, or span to the borrowed one such as `&vec!`
let callsite = expr.span.parent_callsite().unwrap_or(expr.span);
match cx.tcx.parent_hir_node(expr.hir_id) {
// search for `let foo = vec![_]` expressions where all uses of `foo`
// adjust to slices or call a method that exist on slices (e.g. len)
Node::Local(Local {
ty: None,
pat:
Pat {
kind: PatKind::Binding(_, id, ..),
..
},
..
}) => {
let only_slice_uses = for_each_local_use_after_expr(cx, *id, expr.hir_id, |expr| {
// allow indexing into a vec and some set of allowed method calls that exist on slices, too
if let Some(parent) = get_parent_expr(cx, expr)
&& (adjusts_to_slice(cx, expr)
|| matches!(parent.kind, ExprKind::Index(..))
|| is_allowed_vec_method(cx, parent))
{
ControlFlow::Continue(())
} else {
ControlFlow::Break(())
}
})
.is_continue();
if only_slice_uses {
self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, SuggestedType::Array);
} else {
self.span_to_lint_map.insert(callsite, None);
}
},
// if the local pattern has a specified type, do not lint.
Node::Local(Local { ty: Some(_), .. }) if higher::VecArgs::hir(cx, expr).is_some() => {
self.span_to_lint_map.insert(callsite, None);
},
// search for `for _ in vec![...]`
Node::Expr(Expr { span, .. })
if span.is_desugaring(DesugaringKind::ForLoop) && self.msrv.meets(msrvs::ARRAY_INTO_ITERATOR) =>
{
let suggest_slice = suggest_type(expr);
self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, suggest_slice);
},
// search for `&vec![_]` or `vec![_]` expressions where the adjusted type is `&[_]`
_ => {
let suggest_slice = suggest_type(expr);
if adjusts_to_slice(cx, expr) {
self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, suggest_slice);
} else {
self.span_to_lint_map.insert(callsite, None);
}
},
}
}
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
for (span, lint_opt) in &self.span_to_lint_map {
if let Some((hir_id, suggest_slice, snippet, applicability)) = lint_opt {
let help_msg = format!("you can use {} directly", suggest_slice.desc(),);
span_lint_hir_and_then(cx, USELESS_VEC, *hir_id, *span, "useless use of `vec!`", |diag| {
diag.span_suggestion(*span, help_msg, snippet, *applicability);
});
}
}
}
extract_msrv_attr!(LateContext);
}
impl UselessVec {
fn check_vec_macro<'tcx>(
&mut self,
cx: &LateContext<'tcx>,
vec_args: &higher::VecArgs<'tcx>,
span: Span,
hir_id: HirId,
suggest_slice: SuggestedType,
) {
if span.from_expansion() {
return;
}
let snippet = match *vec_args {
higher::VecArgs::Repeat(elem, len) => {
if let Some(Constant::Int(len_constant)) = constant(cx, cx.typeck_results(), len) {
// vec![ty; N] works when ty is Clone, [ty; N] requires it to be Copy also
if !is_copy(cx, cx.typeck_results().expr_ty(elem)) {
return;
}
#[expect(clippy::cast_possible_truncation)]
if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack {
return;
}
suggest_slice.snippet(cx, Some(elem.span), Some(len.span))
} else {
return;
}
},
higher::VecArgs::Vec(args) => {
let args_span = if let Some(last) = args.iter().last() {
if args.len() as u64 * size_of(cx, last) > self.too_large_for_stack {
return;
}
Some(args[0].span.source_callsite().to(last.span.source_callsite()))
} else {
None
};
suggest_slice.snippet(cx, args_span, None)
},
};
self.span_to_lint_map.entry(span).or_insert(Some((
hir_id,
suggest_slice,
snippet,
Applicability::MachineApplicable,
)));
}
}
#[derive(Copy, Clone)]
pub(crate) enum SuggestedType {
/// Suggest using a slice `&[..]` / `&mut [..]`
SliceRef(Mutability),
/// Suggest using an array: `[..]`
Array,
}
impl SuggestedType {
fn desc(self) -> &'static str {
match self {
Self::SliceRef(_) => "a slice",
Self::Array => "an array",
}
}
fn snippet(self, cx: &LateContext<'_>, args_span: Option<Span>, len_span: Option<Span>) -> String {
let maybe_args = args_span.and_then(|sp| snippet_opt(cx, sp)).unwrap_or_default();
let maybe_len = len_span
.and_then(|sp| snippet_opt(cx, sp).map(|s| format!("; {s}")))
.unwrap_or_default();
match self {
Self::SliceRef(Mutability::Mut) => format!("&mut [{maybe_args}{maybe_len}]"),
Self::SliceRef(Mutability::Not) => format!("&[{maybe_args}{maybe_len}]"),
Self::Array => format!("[{maybe_args}{maybe_len}]"),
}
}
}
fn size_of(cx: &LateContext<'_>, expr: &Expr<'_>) -> u64 {
let ty = cx.typeck_results().expr_ty_adjusted(expr);
cx.layout_of(ty).map_or(0, |l| l.size.bytes())
}
fn adjusts_to_slice(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { fn adjusts_to_slice(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
matches!(cx.typeck_results().expr_ty_adjusted(e).kind(), ty::Ref(_, ty, _) if ty.is_slice()) matches!(cx.typeck_results().expr_ty_adjusted(e).kind(), ty::Ref(_, ty, _) if ty.is_slice())
} }
@ -69,179 +235,12 @@ pub fn is_allowed_vec_method(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
} }
} }
impl<'tcx> LateLintPass<'tcx> for UselessVec { fn suggest_type(expr: &Expr<'_>) -> SuggestedType {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::AddrOf(BorrowKind::Ref, mutability, _) = expr.kind {
if let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows()) {
// search for `let foo = vec![_]` expressions where all uses of `foo`
// adjust to slices or call a method that exist on slices (e.g. len)
if let Node::Local(local) = cx.tcx.parent_hir_node(expr.hir_id)
// for now ignore locals with type annotations.
// this is to avoid compile errors when doing the suggestion here: let _: Vec<_> = vec![..];
&& local.ty.is_none()
&& let PatKind::Binding(_, id, ..) = local.pat.kind
{
let only_slice_uses = for_each_local_use_after_expr(cx, id, expr.hir_id, |expr| {
// allow indexing into a vec and some set of allowed method calls that exist on slices, too
if let Some(parent) = get_parent_expr(cx, expr)
&& (adjusts_to_slice(cx, expr)
|| matches!(parent.kind, ExprKind::Index(..))
|| is_allowed_vec_method(cx, parent))
{
ControlFlow::Continue(())
} else {
ControlFlow::Break(())
}
})
.is_continue();
let span = expr.span.ctxt().outer_expn_data().call_site;
if only_slice_uses {
self.check_vec_macro(cx, &vec_args, span, expr.hir_id, SuggestedType::Array);
} else {
self.span_to_lint_map.insert(span, None);
}
}
// if the local pattern has a specified type, do not lint.
else if let Some(_) = higher::VecArgs::hir(cx, expr)
&& let Node::Local(local) = cx.tcx.parent_hir_node(expr.hir_id)
&& local.ty.is_some()
{
let span = expr.span.ctxt().outer_expn_data().call_site;
self.span_to_lint_map.insert(span, None);
}
// search for `for _ in vec![...]`
else if let Some(parent) = get_parent_expr(cx, expr)
&& parent.span.is_desugaring(DesugaringKind::ForLoop)
&& self.msrv.meets(msrvs::ARRAY_INTO_ITERATOR)
{
// report the error around the `vec!` not inside `<std macros>:`
let span = expr.span.ctxt().outer_expn_data().call_site;
self.check_vec_macro(cx, &vec_args, span, expr.hir_id, SuggestedType::Array);
}
// search for `&vec![_]` or `vec![_]` expressions where the adjusted type is `&[_]`
else {
let (suggest_slice, span) = if let ExprKind::AddrOf(BorrowKind::Ref, mutability, _) = expr.kind {
// `expr` is `&vec![_]`, so suggest `&[_]` (or `&mut[_]` resp.) // `expr` is `&vec![_]`, so suggest `&[_]` (or `&mut[_]` resp.)
(SuggestedType::SliceRef(mutability), expr.span) SuggestedType::SliceRef(mutability)
} else { } else {
// `expr` is the `vec![_]` expansion, so suggest `[_]` // `expr` is the `vec![_]` expansion, so suggest `[_]`
// and also use the span of the actual `vec![_]` expression SuggestedType::Array
(SuggestedType::Array, expr.span.ctxt().outer_expn_data().call_site)
};
if adjusts_to_slice(cx, expr) {
self.check_vec_macro(cx, &vec_args, span, expr.hir_id, suggest_slice);
} else {
self.span_to_lint_map.insert(span, None);
} }
} }
}
}
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
for (span, lint_opt) in &self.span_to_lint_map {
if let Some((hir_id, suggest_slice, snippet, applicability)) = lint_opt {
let help_msg = format!(
"you can use {} directly",
match suggest_slice {
SuggestedType::SliceRef(_) => "a slice",
SuggestedType::Array => "an array",
}
);
span_lint_hir_and_then(cx, USELESS_VEC, *hir_id, *span, "useless use of `vec!`", |diag| {
diag.span_suggestion(*span, help_msg, snippet, *applicability);
});
}
}
}
extract_msrv_attr!(LateContext);
}
#[derive(Copy, Clone)]
pub(crate) enum SuggestedType {
/// Suggest using a slice `&[..]` / `&mut [..]`
SliceRef(Mutability),
/// Suggest using an array: `[..]`
Array,
}
impl UselessVec {
fn check_vec_macro<'tcx>(
&mut self,
cx: &LateContext<'tcx>,
vec_args: &higher::VecArgs<'tcx>,
span: Span,
hir_id: HirId,
suggest_slice: SuggestedType,
) {
if span.from_expansion() {
return;
}
let mut applicability = Applicability::MachineApplicable;
let snippet = match *vec_args {
higher::VecArgs::Repeat(elem, len) => {
if let Some(Constant::Int(len_constant)) = constant(cx, cx.typeck_results(), len) {
// vec![ty; N] works when ty is Clone, [ty; N] requires it to be Copy also
if !is_copy(cx, cx.typeck_results().expr_ty(elem)) {
return;
}
#[expect(clippy::cast_possible_truncation)]
if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack {
return;
}
let elem = snippet_with_applicability(cx, elem.span, "elem", &mut applicability);
let len = snippet_with_applicability(cx, len.span, "len", &mut applicability);
match suggest_slice {
SuggestedType::SliceRef(Mutability::Mut) => format!("&mut [{elem}; {len}]"),
SuggestedType::SliceRef(Mutability::Not) => format!("&[{elem}; {len}]"),
SuggestedType::Array => format!("[{elem}; {len}]"),
}
} else {
return;
}
},
higher::VecArgs::Vec(args) => {
if let Some(last) = args.iter().last() {
if args.len() as u64 * size_of(cx, last) > self.too_large_for_stack {
return;
}
let span = args[0].span.source_callsite().to(last.span.source_callsite());
let args = snippet_with_applicability(cx, span, "..", &mut applicability);
match suggest_slice {
SuggestedType::SliceRef(Mutability::Mut) => {
format!("&mut [{args}]")
},
SuggestedType::SliceRef(Mutability::Not) => {
format!("&[{args}]")
},
SuggestedType::Array => {
format!("[{args}]")
},
}
} else {
match suggest_slice {
SuggestedType::SliceRef(Mutability::Mut) => "&mut []".to_owned(),
SuggestedType::SliceRef(Mutability::Not) => "&[]".to_owned(),
SuggestedType::Array => "[]".to_owned(),
}
}
},
};
self.span_to_lint_map
.entry(span)
.or_insert(Some((hir_id, suggest_slice, snippet, applicability)));
}
}
fn size_of(cx: &LateContext<'_>, expr: &Expr<'_>) -> u64 {
let ty = cx.typeck_results().expr_ty_adjusted(expr);
cx.layout_of(ty).map_or(0, |l| l.size.bytes())
}

View file

@ -7,7 +7,7 @@ publish = false
[dependencies] [dependencies]
clippy_config = { path = "../clippy_config" } clippy_config = { path = "../clippy_config" }
arrayvec = { version = "0.7", default-features = false } arrayvec = { version = "0.7", default-features = false }
itertools = "0.11" itertools = "0.12"
rustc-semver = "1.1" rustc-semver = "1.1"
[features] [features]

View file

@ -2,7 +2,7 @@
//! //!
//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s. //! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s.
#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)] #![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
use crate::{both, over}; use crate::{both, over};
use rustc_ast::ptr::P; use rustc_ast::ptr::P;
@ -297,7 +297,7 @@ pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> b
eq_id(l.ident, r.ident) && over(&l.attrs, &r.attrs, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind) eq_id(l.ident, r.ident) && over(&l.attrs, &r.attrs, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind)
} }
#[expect(clippy::too_many_lines)] // Just a big match statement #[expect(clippy::similar_names, clippy::too_many_lines)] // Just a big match statement
pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
use ItemKind::*; use ItemKind::*;
match (l, r) { match (l, r) {
@ -691,7 +691,9 @@ pub fn eq_ty(l: &Ty, r: &Ty) -> bool {
match (&l.kind, &r.kind) { match (&l.kind, &r.kind) {
(Paren(l), _) => eq_ty(l, r), (Paren(l), _) => eq_ty(l, r),
(_, Paren(r)) => eq_ty(l, r), (_, Paren(r)) => eq_ty(l, r),
(Never, Never) | (Infer, Infer) | (ImplicitSelf, ImplicitSelf) | (Err(_), Err(_)) | (CVarArgs, CVarArgs) => true, (Never, Never) | (Infer, Infer) | (ImplicitSelf, ImplicitSelf) | (Err(_), Err(_)) | (CVarArgs, CVarArgs) => {
true
},
(Slice(l), Slice(r)) => eq_ty(l, r), (Slice(l), Slice(r)) => eq_ty(l, r),
(Array(le, ls), Array(re, rs)) => eq_ty(le, re) && eq_expr(&ls.value, &rs.value), (Array(le, ls), Array(re, rs)) => eq_ty(le, re) && eq_expr(&ls.value, &rs.value),
(Ptr(l), Ptr(r)) => l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty), (Ptr(l), Ptr(r)) => l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty),

View file

@ -40,7 +40,7 @@ fn docs_link(diag: &mut DiagnosticBuilder<'_, ()>, lint: &'static Lint) {
/// ///
/// ```ignore /// ```ignore
/// error: usage of mem::forget on Drop type /// error: usage of mem::forget on Drop type
/// --> $DIR/mem_forget.rs:17:5 /// --> tests/ui/mem_forget.rs:17:5
/// | /// |
/// 17 | std::mem::forget(seven); /// 17 | std::mem::forget(seven);
/// | ^^^^^^^^^^^^^^^^^^^^^^^ /// | ^^^^^^^^^^^^^^^^^^^^^^^
@ -65,7 +65,7 @@ pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<Mult
/// ///
/// ```text /// ```text
/// error: constant division of 0.0 with 0.0 will always result in NaN /// error: constant division of 0.0 with 0.0 will always result in NaN
/// --> $DIR/zero_div_zero.rs:6:25 /// --> tests/ui/zero_div_zero.rs:6:25
/// | /// |
/// 6 | let other_f64_nan = 0.0f64 / 0.0; /// 6 | let other_f64_nan = 0.0f64 / 0.0;
/// | ^^^^^^^^^^^^ /// | ^^^^^^^^^^^^
@ -103,14 +103,14 @@ pub fn span_lint_and_help<T: LintContext>(
/// ///
/// ```text /// ```text
/// error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing. /// error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing.
/// --> $DIR/drop_forget_ref.rs:10:5 /// --> tests/ui/drop_forget_ref.rs:10:5
/// | /// |
/// 10 | forget(&SomeStruct); /// 10 | forget(&SomeStruct);
/// | ^^^^^^^^^^^^^^^^^^^ /// | ^^^^^^^^^^^^^^^^^^^
/// | /// |
/// = note: `-D clippy::forget-ref` implied by `-D warnings` /// = note: `-D clippy::forget-ref` implied by `-D warnings`
/// note: argument has type &SomeStruct /// note: argument has type &SomeStruct
/// --> $DIR/drop_forget_ref.rs:10:12 /// --> tests/ui/drop_forget_ref.rs:10:12
/// | /// |
/// 10 | forget(&SomeStruct); /// 10 | forget(&SomeStruct);
/// | ^^^^^^^^^^^ /// | ^^^^^^^^^^^
@ -186,7 +186,7 @@ pub fn span_lint_hir_and_then(
/// ///
/// ```text /// ```text
/// error: This `.fold` can be more succinctly expressed as `.any` /// error: This `.fold` can be more succinctly expressed as `.any`
/// --> $DIR/methods.rs:390:13 /// --> tests/ui/methods.rs:390:13
/// | /// |
/// 390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2); /// 390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2);
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)` /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`

View file

@ -132,7 +132,6 @@ impl HirEqInterExpr<'_, '_, '_> {
} }
/// Checks whether two blocks are the same. /// Checks whether two blocks are the same.
#[expect(clippy::similar_names)]
fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool { fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
use TokenKind::{Semi, Whitespace}; use TokenKind::{Semi, Whitespace};
if left.stmts.len() != right.stmts.len() { if left.stmts.len() != right.stmts.len() {
@ -247,7 +246,7 @@ impl HirEqInterExpr<'_, '_, '_> {
res res
} }
#[expect(clippy::similar_names, clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool { pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
if !self.check_ctxt(left.span.ctxt(), right.span.ctxt()) { if !self.check_ctxt(left.span.ctxt(), right.span.ctxt()) {
return false; return false;
@ -463,7 +462,6 @@ impl HirEqInterExpr<'_, '_, '_> {
} }
} }
#[expect(clippy::similar_names)]
fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool { fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool {
match (left, right) { match (left, right) {
(&QPath::Resolved(ref lty, lpath), &QPath::Resolved(ref rty, rpath)) => { (&QPath::Resolved(ref lty, lpath), &QPath::Resolved(ref rty, rpath)) => {
@ -1159,7 +1157,6 @@ pub fn hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 {
h.finish() h.finish()
} }
#[expect(clippy::similar_names)]
fn eq_span_tokens( fn eq_span_tokens(
cx: &LateContext<'_>, cx: &LateContext<'_>,
left: impl SpanRange, left: impl SpanRange,

View file

@ -14,7 +14,7 @@
clippy::missing_panics_doc, clippy::missing_panics_doc,
clippy::must_use_candidate, clippy::must_use_candidate,
rustc::diagnostic_outside_of_impl, rustc::diagnostic_outside_of_impl,
rustc::untranslatable_diagnostic, rustc::untranslatable_diagnostic
)] )]
// warn on the same lints as `clippy_lints` // warn on the same lints as `clippy_lints`
#![warn(trivial_casts, trivial_numeric_casts)] #![warn(trivial_casts, trivial_numeric_casts)]
@ -1307,11 +1307,6 @@ pub fn contains_return<'tcx>(expr: impl Visitable<'tcx>) -> bool {
.is_some() .is_some()
} }
/// Gets the parent node, if any.
pub fn get_parent_node(tcx: TyCtxt<'_>, id: HirId) -> Option<Node<'_>> {
Some(tcx.parent_hir_node(id))
}
/// Gets the parent expression, if any - this is useful to constrain a lint. /// Gets the parent expression, if any - this is useful to constrain a lint.
pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> { pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
get_parent_expr_for_hir(cx, e.hir_id) get_parent_expr_for_hir(cx, e.hir_id)
@ -1320,8 +1315,8 @@ pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'t
/// This retrieves the parent for the given `HirId` if it's an expression. This is useful for /// This retrieves the parent for the given `HirId` if it's an expression. This is useful for
/// constraint lints /// constraint lints
pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: hir::HirId) -> Option<&'tcx Expr<'tcx>> { pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: hir::HirId) -> Option<&'tcx Expr<'tcx>> {
match get_parent_node(cx.tcx, hir_id) { match cx.tcx.parent_hir_node(hir_id) {
Some(Node::Expr(parent)) => Some(parent), Node::Expr(parent) => Some(parent),
_ => None, _ => None,
} }
} }
@ -2182,7 +2177,7 @@ pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
/// Checks if the expression is the final expression returned from a block. /// Checks if the expression is the final expression returned from a block.
pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..))) matches!(tcx.parent_hir_node(expr.hir_id), Node::Block(..))
} }
pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> { pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> {

View file

@ -11,7 +11,7 @@ pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
["rustc_lint_defs", "Applicability", "MaybeIncorrect"], ["rustc_lint_defs", "Applicability", "MaybeIncorrect"],
["rustc_lint_defs", "Applicability", "MachineApplicable"], ["rustc_lint_defs", "Applicability", "MachineApplicable"],
]; ];
pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"]; pub const DIAGNOSTIC_BUILDER: [&str; 2] = ["rustc_errors", "DiagnosticBuilder"];
pub const BINARYHEAP_ITER: [&str; 5] = ["alloc", "collections", "binary_heap", "BinaryHeap", "iter"]; pub const BINARYHEAP_ITER: [&str; 5] = ["alloc", "collections", "binary_heap", "BinaryHeap", "iter"];
pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"]; pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"];
pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"]; pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"];

View file

@ -174,9 +174,8 @@ fn check_rvalue<'tcx>(
)) ))
} }
}, },
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::DebugAssertions, _) | Rvalue::ShallowInitBox(_, _) => { Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::DebugAssertions, _)
Ok(()) | Rvalue::ShallowInitBox(_, _) => Ok(()),
},
Rvalue::UnaryOp(_, operand) => { Rvalue::UnaryOp(_, operand) => {
let ty = operand.ty(body, tcx); let ty = operand.ty(body, tcx);
if ty.is_integral() || ty.is_bool() { if ty.is_integral() || ty.is_bool() {
@ -389,7 +388,6 @@ fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool {
}) })
} }
#[expect(clippy::similar_names)] // bit too pedantic
fn is_ty_const_destruct<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool { fn is_ty_const_destruct<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool {
// FIXME(effects, fee1-dead) revert to const destruct once it works again // FIXME(effects, fee1-dead) revert to const destruct once it works again
#[expect(unused)] #[expect(unused)]

View file

@ -8,7 +8,7 @@ publish = false
proc-macro = true proc-macro = true
[dependencies] [dependencies]
itertools = "0.11" itertools = "0.12"
quote = "1.0.21" quote = "1.0.21"
syn = "2.0" syn = "2.0"

View file

@ -749,7 +749,7 @@ fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, us
// list all new counts (key is in new stats but not in old stats) // list all new counts (key is in new stats but not in old stats)
new_stats_deduped new_stats_deduped
.iter() .iter()
.filter(|(new_key, _)| old_stats_deduped.get::<str>(new_key).is_none()) .filter(|(new_key, _)| !old_stats_deduped.contains_key::<str>(new_key))
.for_each(|(new_key, new_value)| { .for_each(|(new_key, new_value)| {
println!("{new_key} 0 => {new_value}"); println!("{new_key} 0 => {new_value}");
}); });
@ -757,7 +757,7 @@ fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, us
// list all changed counts (key is in both maps but value differs) // list all changed counts (key is in both maps but value differs)
new_stats_deduped new_stats_deduped
.iter() .iter()
.filter(|(new_key, _new_val)| old_stats_deduped.get::<str>(new_key).is_some()) .filter(|(new_key, _new_val)| old_stats_deduped.contains_key::<str>(new_key))
.for_each(|(new_key, new_val)| { .for_each(|(new_key, new_val)| {
let old_val = old_stats_deduped.get::<str>(new_key).unwrap(); let old_val = old_stats_deduped.get::<str>(new_key).unwrap();
println!("{new_key} {old_val} => {new_val}"); println!("{new_key} {old_val} => {new_val}");
@ -766,7 +766,7 @@ fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, us
// list all gone counts (key is in old status but not in new stats) // list all gone counts (key is in old status but not in new stats)
old_stats_deduped old_stats_deduped
.iter() .iter()
.filter(|(old_key, _)| new_stats_deduped.get::<&String>(old_key).is_none()) .filter(|(old_key, _)| !new_stats_deduped.contains_key::<&String>(old_key))
.filter(|(old_key, _)| lint_filter.is_empty() || lint_filter.contains(old_key)) .filter(|(old_key, _)| lint_filter.is_empty() || lint_filter.contains(old_key))
.for_each(|(old_key, old_value)| { .for_each(|(old_key, old_value)| {
println!("{old_key} {old_value} => 0"); println!("{old_key} {old_value} => 0");

View file

@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "nightly-2024-02-08" channel = "nightly-2024-02-22"
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]

View file

@ -272,7 +272,9 @@ pub fn main() {
}, },
_ => Some(s.to_string()), _ => Some(s.to_string()),
}) })
// FIXME: remove this line in 1.79 to only keep `--cfg clippy`.
.chain(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()]) .chain(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()])
.chain(vec!["--cfg".into(), "clippy".into()])
.collect::<Vec<String>>(); .collect::<Vec<String>>();
// We enable Clippy if one of the following conditions is met // We enable Clippy if one of the following conditions is met

View file

@ -4,6 +4,7 @@
#![warn(rust_2018_idioms, unused_lifetimes)] #![warn(rust_2018_idioms, unused_lifetimes)]
#![allow(unused_extern_crates)] #![allow(unused_extern_crates)]
use ui_test::spanned::Spanned;
use ui_test::{status_emitter, Args, CommandBuilder, Config, Match, Mode, OutputConflictHandling}; use ui_test::{status_emitter, Args, CommandBuilder, Config, Match, Mode, OutputConflictHandling};
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -112,20 +113,21 @@ fn base_config(test_dir: &str) -> (Config, Args) {
let target_dir = PathBuf::from(var_os("CARGO_TARGET_DIR").unwrap_or_else(|| "target".into())); let target_dir = PathBuf::from(var_os("CARGO_TARGET_DIR").unwrap_or_else(|| "target".into()));
let mut config = Config { let mut config = Config {
mode: Mode::Yolo { output_conflict_handling: OutputConflictHandling::Error,
rustfix: ui_test::RustfixMode::Everything,
},
filter_files: env::var("TESTNAME") filter_files: env::var("TESTNAME")
.map(|filters| filters.split(',').map(str::to_string).collect()) .map(|filters| filters.split(',').map(str::to_string).collect())
.unwrap_or_default(), .unwrap_or_default(),
target: None, target: None,
bless_command: Some("cargo uibless".into()),
out_dir: target_dir.join("ui_test"), out_dir: target_dir.join("ui_test"),
..Config::rustc(Path::new("tests").join(test_dir)) ..Config::rustc(Path::new("tests").join(test_dir))
}; };
config.with_args(&args, /* bless by default */ false); config.comment_defaults.base().mode = Some(Spanned::dummy(Mode::Yolo {
if let OutputConflictHandling::Error(err) = &mut config.output_conflict_handling { rustfix: ui_test::RustfixMode::Everything,
*err = "cargo uibless".into(); }))
} .into();
config.comment_defaults.base().diagnostic_code_prefix = Some(Spanned::dummy("clippy::".into())).into();
config.with_args(&args);
let current_exe_path = env::current_exe().unwrap(); let current_exe_path = env::current_exe().unwrap();
let deps_path = current_exe_path.parent().unwrap(); let deps_path = current_exe_path.parent().unwrap();
let profile_path = deps_path.parent().unwrap(); let profile_path = deps_path.parent().unwrap();
@ -179,9 +181,7 @@ fn run_internal_tests() {
return; return;
} }
let (mut config, args) = base_config("ui-internal"); let (mut config, args) = base_config("ui-internal");
if let OutputConflictHandling::Error(err) = &mut config.output_conflict_handling { config.bless_command = Some("cargo uitest --features internal -- -- --bless".into());
*err = "cargo uitest --features internal -- -- --bless".into();
}
ui_test::run_tests_generic( ui_test::run_tests_generic(
vec![config], vec![config],
@ -196,8 +196,10 @@ fn run_ui_toml() {
let (mut config, args) = base_config("ui-toml"); let (mut config, args) = base_config("ui-toml");
config config
.stderr_filters .comment_defaults
.push((Match::from(env::current_dir().unwrap().as_path()), b"$DIR")); .base()
.normalize_stderr
.push((Match::from(env::current_dir().unwrap().as_path()), b"$DIR".into()));
ui_test::run_tests_generic( ui_test::run_tests_generic(
vec![config], vec![config],
@ -213,6 +215,8 @@ fn run_ui_toml() {
.unwrap(); .unwrap();
} }
// Allow `Default::default` as `OptWithSpan` is not nameable
#[allow(clippy::default_trait_access)]
fn run_ui_cargo() { fn run_ui_cargo() {
if IS_RUSTC_TEST_SUITE { if IS_RUSTC_TEST_SUITE {
return; return;
@ -234,11 +238,13 @@ fn run_ui_cargo() {
} else { } else {
"cargo-clippy" "cargo-clippy"
}); });
config.edition = None; config.comment_defaults.base().edition = Default::default();
config config
.stderr_filters .comment_defaults
.push((Match::from(env::current_dir().unwrap().as_path()), b"$DIR")); .base()
.normalize_stderr
.push((Match::from(env::current_dir().unwrap().as_path()), b"$DIR".into()));
let ignored_32bit = |path: &Path| { let ignored_32bit = |path: &Path| {
// FIXME: for some reason the modules are linted in a different order for this test // FIXME: for some reason the modules are linted in a different order for this test
@ -248,7 +254,8 @@ fn run_ui_cargo() {
ui_test::run_tests_generic( ui_test::run_tests_generic(
vec![config], vec![config],
|path, config| { |path, config| {
path.ends_with("Cargo.toml") && ui_test::default_any_file_filter(path, config) && !ignored_32bit(path) path.ends_with("Cargo.toml")
.then(|| ui_test::default_any_file_filter(path, config) && !ignored_32bit(path))
}, },
|_config, _path, _file_contents| {}, |_config, _path, _file_contents| {},
status_emitter::Text::from(args.format), status_emitter::Text::from(args.format),

View file

@ -1,2 +1,2 @@
warning: using config file `$DIR/$DIR/.clippy.toml`, `$DIR/$DIR/clippy.toml` will be ignored warning: using config file `$DIR/tests/ui-cargo/multiple_config_files/warn/.clippy.toml`, `$DIR/tests/ui-cargo/multiple_config_files/warn/clippy.toml` will be ignored

View file

@ -1,5 +1,5 @@
error: this item has an invalid `clippy::version` attribute error: this item has an invalid `clippy::version` attribute
--> $DIR/check_clippy_version_attribute.rs:40:1 --> tests/ui-internal/check_clippy_version_attribute.rs:40:1
| |
LL | / declare_tool_lint! { LL | / declare_tool_lint! {
LL | | #[clippy::version = "1.2.3.4.5.6"] LL | | #[clippy::version = "1.2.3.4.5.6"]
@ -12,7 +12,7 @@ LL | | }
| |
= help: please use a valid semantic version, see `doc/adding_lints.md` = help: please use a valid semantic version, see `doc/adding_lints.md`
note: the lint level is defined here note: the lint level is defined here
--> $DIR/check_clippy_version_attribute.rs:1:9 --> tests/ui-internal/check_clippy_version_attribute.rs:1:9
| |
LL | #![deny(clippy::internal)] LL | #![deny(clippy::internal)]
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
@ -20,7 +20,7 @@ LL | #![deny(clippy::internal)]
= note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
error: this item has an invalid `clippy::version` attribute error: this item has an invalid `clippy::version` attribute
--> $DIR/check_clippy_version_attribute.rs:48:1 --> tests/ui-internal/check_clippy_version_attribute.rs:48:1
| |
LL | / declare_tool_lint! { LL | / declare_tool_lint! {
LL | | #[clippy::version = "I'm a string"] LL | | #[clippy::version = "I'm a string"]
@ -35,7 +35,7 @@ LL | | }
= note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
error: this lint is missing the `clippy::version` attribute or version value error: this lint is missing the `clippy::version` attribute or version value
--> $DIR/check_clippy_version_attribute.rs:59:1 --> tests/ui-internal/check_clippy_version_attribute.rs:59:1
| |
LL | / declare_tool_lint! { LL | / declare_tool_lint! {
LL | | #[clippy::version] LL | | #[clippy::version]
@ -51,7 +51,7 @@ LL | | }
= note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
error: this lint is missing the `clippy::version` attribute or version value error: this lint is missing the `clippy::version` attribute or version value
--> $DIR/check_clippy_version_attribute.rs:67:1 --> tests/ui-internal/check_clippy_version_attribute.rs:67:1
| |
LL | / declare_tool_lint! { LL | / declare_tool_lint! {
LL | | pub clippy::MISSING_ATTRIBUTE_TWO, LL | | pub clippy::MISSING_ATTRIBUTE_TWO,

View file

@ -1,5 +1,5 @@
error: non-standard lint formulation error: non-standard lint formulation
--> $DIR/check_formulation.rs:23:5 --> tests/ui-internal/check_formulation.rs:23:5
| |
LL | /// Check for lint formulations that are correct LL | /// Check for lint formulations that are correct
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -9,7 +9,7 @@ LL | /// Check for lint formulations that are correct
= help: to override `-D warnings` add `#[allow(clippy::almost_standard_lint_formulation)]` = help: to override `-D warnings` add `#[allow(clippy::almost_standard_lint_formulation)]`
error: non-standard lint formulation error: non-standard lint formulation
--> $DIR/check_formulation.rs:33:5 --> tests/ui-internal/check_formulation.rs:33:5
| |
LL | /// Detects uses of incorrect formulations LL | /// Detects uses of incorrect formulations
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -1,5 +1,5 @@
error: this call is collapsible error: this call is collapsible
--> $DIR/collapsible_span_lint_calls.rs:35:9 --> tests/ui-internal/collapsible_span_lint_calls.rs:35:9
| |
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | | db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable); LL | | db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable);
@ -7,14 +7,14 @@ LL | | });
| |__________^ help: collapse into: `span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, help_msg, sugg.to_string(), Applicability::MachineApplicable)` | |__________^ help: collapse into: `span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, help_msg, sugg.to_string(), Applicability::MachineApplicable)`
| |
note: the lint level is defined here note: the lint level is defined here
--> $DIR/collapsible_span_lint_calls.rs:1:9 --> tests/ui-internal/collapsible_span_lint_calls.rs:1:9
| |
LL | #![deny(clippy::internal)] LL | #![deny(clippy::internal)]
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
= note: `#[deny(clippy::collapsible_span_lint_calls)]` implied by `#[deny(clippy::internal)]` = note: `#[deny(clippy::collapsible_span_lint_calls)]` implied by `#[deny(clippy::internal)]`
error: this call is collapsible error: this call is collapsible
--> $DIR/collapsible_span_lint_calls.rs:38:9 --> tests/ui-internal/collapsible_span_lint_calls.rs:38:9
| |
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | | db.span_help(expr.span, help_msg); LL | | db.span_help(expr.span, help_msg);
@ -22,7 +22,7 @@ LL | | });
| |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg)` | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg)`
error: this call is collapsible error: this call is collapsible
--> $DIR/collapsible_span_lint_calls.rs:41:9 --> tests/ui-internal/collapsible_span_lint_calls.rs:41:9
| |
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | | db.help(help_msg); LL | | db.help(help_msg);
@ -30,7 +30,7 @@ LL | | });
| |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg)` | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg)`
error: this call is collapsible error: this call is collapsible
--> $DIR/collapsible_span_lint_calls.rs:44:9 --> tests/ui-internal/collapsible_span_lint_calls.rs:44:9
| |
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | | db.span_note(expr.span, note_msg); LL | | db.span_note(expr.span, note_msg);
@ -38,7 +38,7 @@ LL | | });
| |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg)` | |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg)`
error: this call is collapsible error: this call is collapsible
--> $DIR/collapsible_span_lint_calls.rs:47:9 --> tests/ui-internal/collapsible_span_lint_calls.rs:47:9
| |
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| { LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | | db.note(note_msg); LL | | db.note(note_msg);

View file

@ -1,5 +1,5 @@
error: the lint `COOL_LINT_DEFAULT` has the default deprecation reason error: the lint `COOL_LINT_DEFAULT` has the default deprecation reason
--> $DIR/default_deprecation_reason.rs:8:1 --> tests/ui-internal/default_deprecation_reason.rs:8:1
| |
LL | / declare_deprecated_lint! { LL | / declare_deprecated_lint! {
LL | | /// ### What it does LL | | /// ### What it does
@ -11,7 +11,7 @@ LL | | }
| |_^ | |_^
| |
note: the lint level is defined here note: the lint level is defined here
--> $DIR/default_deprecation_reason.rs:1:9 --> tests/ui-internal/default_deprecation_reason.rs:1:9
| |
LL | #![deny(clippy::internal)] LL | #![deny(clippy::internal)]
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^

View file

@ -1,5 +1,5 @@
error: the lint `TEST_LINT_DEFAULT` has the default lint description error: the lint `TEST_LINT_DEFAULT` has the default lint description
--> $DIR/default_lint.rs:18:1 --> tests/ui-internal/default_lint.rs:18:1
| |
LL | / declare_tool_lint! { LL | / declare_tool_lint! {
LL | | pub clippy::TEST_LINT_DEFAULT, LL | | pub clippy::TEST_LINT_DEFAULT,
@ -10,7 +10,7 @@ LL | | }
| |_^ | |_^
| |
note: the lint level is defined here note: the lint level is defined here
--> $DIR/default_lint.rs:1:9 --> tests/ui-internal/default_lint.rs:1:9
| |
LL | #![deny(clippy::internal)] LL | #![deny(clippy::internal)]
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^

View file

@ -1,5 +1,5 @@
error: use of a disallowed method `rustc_lint::context::LintContext::span_lint` error: use of a disallowed method `rustc_lint::context::LintContext::span_lint`
--> $DIR/disallow_span_lint.rs:14:5 --> tests/ui-internal/disallow_span_lint.rs:14:5
| |
LL | cx.span_lint(lint, span, msg, |_| {}); LL | cx.span_lint(lint, span, msg, |_| {});
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -8,7 +8,7 @@ LL | cx.span_lint(lint, span, msg, |_| {});
= help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]` = help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]`
error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_lint` error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_lint`
--> $DIR/disallow_span_lint.rs:24:5 --> tests/ui-internal/disallow_span_lint.rs:24:5
| |
LL | tcx.node_span_lint(lint, hir_id, span, msg, |_| {}); LL | tcx.node_span_lint(lint, hir_id, span, msg, |_| {});
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -1,30 +1,30 @@
error: interning a defined symbol error: interning a defined symbol
--> $DIR/interning_defined_symbol.rs:17:13 --> tests/ui-internal/interning_defined_symbol.rs:17:13
| |
LL | let _ = Symbol::intern("f32"); LL | let _ = Symbol::intern("f32");
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::f32` | ^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::f32`
| |
note: the lint level is defined here note: the lint level is defined here
--> $DIR/interning_defined_symbol.rs:1:9 --> tests/ui-internal/interning_defined_symbol.rs:1:9
| |
LL | #![deny(clippy::internal)] LL | #![deny(clippy::internal)]
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
= note: `#[deny(clippy::interning_defined_symbol)]` implied by `#[deny(clippy::internal)]` = note: `#[deny(clippy::interning_defined_symbol)]` implied by `#[deny(clippy::internal)]`
error: interning a defined symbol error: interning a defined symbol
--> $DIR/interning_defined_symbol.rs:20:13 --> tests/ui-internal/interning_defined_symbol.rs:20:13
| |
LL | let _ = sym!(f32); LL | let _ = sym!(f32);
| ^^^^^^^^^ help: try: `rustc_span::sym::f32` | ^^^^^^^^^ help: try: `rustc_span::sym::f32`
error: interning a defined symbol error: interning a defined symbol
--> $DIR/interning_defined_symbol.rs:23:13 --> tests/ui-internal/interning_defined_symbol.rs:23:13
| |
LL | let _ = Symbol::intern("proc-macro"); LL | let _ = Symbol::intern("proc-macro");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::proc_dash_macro` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::proc_dash_macro`
error: interning a defined symbol error: interning a defined symbol
--> $DIR/interning_defined_symbol.rs:26:13 --> tests/ui-internal/interning_defined_symbol.rs:26:13
| |
LL | let _ = Symbol::intern("self"); LL | let _ = Symbol::intern("self");
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::symbol::kw::SelfLower` | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::symbol::kw::SelfLower`

View file

@ -1,11 +1,11 @@
error: `extract_msrv_attr!` macro missing from `LateLintPass` implementation error: `extract_msrv_attr!` macro missing from `LateLintPass` implementation
--> $DIR/invalid_msrv_attr_impl.rs:28:1 --> tests/ui-internal/invalid_msrv_attr_impl.rs:28:1
| |
LL | impl LateLintPass<'_> for Pass { LL | impl LateLintPass<'_> for Pass {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
note: the lint level is defined here note: the lint level is defined here
--> $DIR/invalid_msrv_attr_impl.rs:1:9 --> tests/ui-internal/invalid_msrv_attr_impl.rs:1:9
| |
LL | #![deny(clippy::internal)] LL | #![deny(clippy::internal)]
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
@ -17,7 +17,7 @@ LL + extract_msrv_attr!(LateContext);
| |
error: `extract_msrv_attr!` macro missing from `EarlyLintPass` implementation error: `extract_msrv_attr!` macro missing from `EarlyLintPass` implementation
--> $DIR/invalid_msrv_attr_impl.rs:32:1 --> tests/ui-internal/invalid_msrv_attr_impl.rs:32:1
| |
LL | impl EarlyLintPass for Pass { LL | impl EarlyLintPass for Pass {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -1,5 +1,5 @@
error: invalid path error: invalid path
--> $DIR/invalid_paths.rs:15:5 --> tests/ui-internal/invalid_paths.rs:15:5
| |
LL | pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"]; LL | pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -8,13 +8,13 @@ LL | pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"
= help: to override `-D warnings` add `#[allow(clippy::invalid_paths)]` = help: to override `-D warnings` add `#[allow(clippy::invalid_paths)]`
error: invalid path error: invalid path
--> $DIR/invalid_paths.rs:18:5 --> tests/ui-internal/invalid_paths.rs:18:5
| |
LL | pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"]; LL | pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: invalid path error: invalid path
--> $DIR/invalid_paths.rs:21:5 --> tests/ui-internal/invalid_paths.rs:21:5
| |
LL | pub const BAD_MOD_PATH: [&str; 2] = ["std", "xxx"]; LL | pub const BAD_MOD_PATH: [&str; 2] = ["std", "xxx"];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -1,5 +1,5 @@
error: the lint `TEST_LINT` is not added to any `LintPass` error: the lint `TEST_LINT` is not added to any `LintPass`
--> $DIR/lint_without_lint_pass.rs:12:1 --> tests/ui-internal/lint_without_lint_pass.rs:12:1
| |
LL | / declare_tool_lint! { LL | / declare_tool_lint! {
LL | | pub clippy::TEST_LINT, LL | | pub clippy::TEST_LINT,
@ -10,7 +10,7 @@ LL | | }
| |_^ | |_^
| |
note: the lint level is defined here note: the lint level is defined here
--> $DIR/lint_without_lint_pass.rs:1:9 --> tests/ui-internal/lint_without_lint_pass.rs:1:9
| |
LL | #![deny(clippy::internal)] LL | #![deny(clippy::internal)]
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^

View file

@ -1,11 +1,11 @@
error: usage of `outer_expn().expn_data()` error: usage of `outer_expn().expn_data()`
--> $DIR/outer_expn_data.rs:23:34 --> tests/ui-internal/outer_expn_data.rs:23:34
| |
LL | let _ = expr.span.ctxt().outer_expn().expn_data(); LL | let _ = expr.span.ctxt().outer_expn().expn_data();
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `outer_expn_data()` | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `outer_expn_data()`
| |
note: the lint level is defined here note: the lint level is defined here
--> $DIR/outer_expn_data.rs:1:9 --> tests/ui-internal/outer_expn_data.rs:1:9
| |
LL | #![deny(clippy::internal)] LL | #![deny(clippy::internal)]
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^

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