mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 23:04:33 +00:00
Merge commit '10136170fe9ed01e46aeb4f4479175b79eb0e3c7' into clippy-subtree-update
This commit is contained in:
parent
5931794375
commit
7be6e2178e
4
.github/driver.sh
vendored
4
.github/driver.sh
vendored
@ -32,7 +32,7 @@ test "$sysroot" = $desired_sysroot
|
||||
)
|
||||
|
||||
# Check that the --sysroot argument is only passed once via arg_file.txt (SYSROOT is ignored)
|
||||
(
|
||||
(
|
||||
echo "fn main() {}" > target/driver_test.rs
|
||||
echo "--sysroot="$(./target/debug/clippy-driver --print sysroot)"" > arg_file.txt
|
||||
echo "--verbose" >> arg_file.txt
|
||||
@ -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
|
||||
# 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
|
||||
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
|
||||
|
||||
# make sure "clippy-driver --rustc --arg" and "rustc --arg" behave the same
|
||||
|
@ -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_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_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
|
||||
[`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
|
||||
@ -5157,6 +5158,7 @@ Released 2018-09-13
|
||||
[`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
|
||||
[`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_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
|
||||
@ -5429,6 +5431,7 @@ Released 2018-09-13
|
||||
[`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
|
||||
[`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_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
|
||||
@ -5725,10 +5728,12 @@ Released 2018-09-13
|
||||
[`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_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_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_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_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
|
||||
|
@ -27,17 +27,17 @@ rustc_tools_util = "0.3.0"
|
||||
tempfile = { version = "3.2", optional = true }
|
||||
termize = "0.1"
|
||||
color-print = "0.3.4"
|
||||
anstream = "0.5.0"
|
||||
anstream = "0.6.0"
|
||||
|
||||
[dev-dependencies]
|
||||
ui_test = "0.21.2"
|
||||
ui_test = "0.22.2"
|
||||
tester = "0.9"
|
||||
regex = "1.5"
|
||||
toml = "0.7.3"
|
||||
walkdir = "2.3"
|
||||
# This is used by the `collect-metadata` alias.
|
||||
filetime = "0.2"
|
||||
itertools = "0.11"
|
||||
itertools = "0.12"
|
||||
|
||||
# UI test dependencies
|
||||
clippy_utils = { path = "clippy_utils" }
|
||||
|
@ -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`
|
||||
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)]`,
|
||||
`#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive lints prone to false
|
||||
positives.
|
||||
* the whole set of `warn`-by-default lints using the `clippy` lint group (`#![allow(clippy::all)]`)
|
||||
|
||||
* 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.)
|
||||
|
||||
* `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`
|
||||
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.
|
||||
the lint will emit an error, when triggering for your code. An error causes Clippy to exit with an error code, so is
|
||||
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
|
||||
flags to Clippy during the run:
|
||||
#### Command Line Flags
|
||||
|
||||
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
|
||||
|
||||
@ -66,19 +69,33 @@ And to warn on `lint_name`, run
|
||||
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
|
||||
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:
|
||||
|
||||
```terminal
|
||||
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
|
||||
|
||||
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
|
||||
[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
|
||||
#[cfg(not(feature = "cargo-clippy"))]
|
||||
#[cfg(not(clippy)]
|
||||
include!(concat!(env!("OUT_DIR"), "/my_big_function-generated.rs"));
|
||||
|
||||
#[cfg(feature = "cargo-clippy")]
|
||||
#[cfg(clippy)]
|
||||
fn my_big_function(_input: &str) -> Option<MyStruct> {
|
||||
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.
|
||||
|
@ -82,7 +82,7 @@ The output looks something like this (from the example earlier):
|
||||
|
||||
```text
|
||||
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 {}
|
||||
| ^^^^^^^^ help: use: `1..=1`
|
||||
@ -135,14 +135,14 @@ Examples:
|
||||
|
||||
```text
|
||||
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);
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `-D clippy::forget-ref` implied by `-D warnings`
|
||||
note: argument has type &SomeStruct
|
||||
--> $DIR/drop_forget_ref.rs:10:12
|
||||
--> tests/ui/drop_forget_ref.rs:10:12
|
||||
|
|
||||
10 | forget(&SomeStruct);
|
||||
| ^^^^^^^^^^^
|
||||
@ -158,7 +158,7 @@ Example:
|
||||
|
||||
```text
|
||||
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;
|
||||
| ^^^^^^^^^^^^
|
||||
@ -176,7 +176,7 @@ Example:
|
||||
|
||||
```text
|
||||
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);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`
|
||||
|
@ -97,19 +97,19 @@ failures:
|
||||
---- compile_test stdout ----
|
||||
normalized stderr:
|
||||
error: function called "foo"
|
||||
--> $DIR/foo_functions.rs:6:12
|
||||
--> tests/ui/foo_functions.rs:6:12
|
||||
|
|
||||
LL | pub fn foo(&self) {}
|
||||
| ^^^
|
||||
|
|
||||
= note: `-D clippy::foo-functions` implied by `-D warnings`
|
||||
error: function called "foo"
|
||||
--> $DIR/foo_functions.rs:13:8
|
||||
--> tests/ui/foo_functions.rs:13:8
|
||||
|
|
||||
LL | fn foo(&self) {}
|
||||
| ^^^
|
||||
error: function called "foo"
|
||||
--> $DIR/foo_functions.rs:19:4
|
||||
--> tests/ui/foo_functions.rs:19:4
|
||||
|
|
||||
LL | fn foo() {}
|
||||
| ^^^
|
||||
|
@ -278,7 +278,7 @@ The minimum number of struct fields for the lints about field names to trigger
|
||||
|
||||
---
|
||||
**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`
|
||||
|
@ -325,7 +325,7 @@ define_Conf! {
|
||||
///
|
||||
/// The minimum number of enum variants for the lints about variant names to trigger
|
||||
(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
|
||||
(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.disallowed_names, DEFAULT_DISALLOWED_NAMES);
|
||||
// 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
|
||||
.allowed_idents_below_min_chars
|
||||
.extend(DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string));
|
||||
|
@ -6,7 +6,7 @@
|
||||
clippy::missing_panics_doc,
|
||||
rustc::diagnostic_outside_of_impl,
|
||||
rustc::untranslatable_diagnostic,
|
||||
rustc::untranslatable_diagnostic_trivial,
|
||||
rustc::untranslatable_diagnostic_trivial
|
||||
)]
|
||||
|
||||
extern crate rustc_ast;
|
||||
|
@ -7,7 +7,7 @@ edition = "2021"
|
||||
aho-corasick = "1.0"
|
||||
clap = "4.1.4"
|
||||
indoc = "1.0"
|
||||
itertools = "0.11"
|
||||
itertools = "0.12"
|
||||
opener = "0.6"
|
||||
shell-escape = "0.1"
|
||||
walkdir = "2.3"
|
||||
|
@ -14,7 +14,7 @@ cargo_metadata = "0.18"
|
||||
clippy_config = { path = "../clippy_config" }
|
||||
clippy_utils = { path = "../clippy_utils" }
|
||||
declare_clippy_lint = { path = "../declare_clippy_lint" }
|
||||
itertools = "0.11"
|
||||
itertools = "0.12"
|
||||
quine-mc_cluskey = "0.2"
|
||||
regex-syntax = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
@ -2,8 +2,11 @@ use std::fmt;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
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_span::Span;
|
||||
use rustc_target::asm::InlineAsmArch;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
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) {
|
||||
if let ExprKind::InlineAsm(ref inline_asm) = expr.kind {
|
||||
fn check_asm_syntax(
|
||||
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) {
|
||||
AsmStyle::Att
|
||||
} else {
|
||||
@ -43,7 +52,7 @@ fn check_expr_asm_syntax(lint: &'static Lint, cx: &EarlyContext<'_>, expr: &Expr
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
lint,
|
||||
expr.span,
|
||||
span,
|
||||
&format!("{style} x86 assembly syntax used"),
|
||||
None,
|
||||
&format!("use {} x86 assembly syntax", !style),
|
||||
@ -89,7 +98,15 @@ declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]);
|
||||
|
||||
impl EarlyLintPass for InlineAsmX86IntelSyntax {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
//! checks for attributes
|
||||
|
||||
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::macros::{is_panic, macro_backtrace};
|
||||
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"
|
||||
}
|
||||
|
||||
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 => [
|
||||
ALLOW_ATTRIBUTES_WITHOUT_REASON,
|
||||
INLINE_ALWAYS,
|
||||
@ -512,6 +564,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|
||||
|| is_word(lint, sym::deprecated)
|
||||
|| is_word(lint, sym!(unreachable_pub))
|
||||
|| is_word(lint, sym!(unused))
|
||||
|| is_word(lint, sym!(unused_import_braces))
|
||||
|| extract_clippy_lint(lint).map_or(false, |s| {
|
||||
matches!(
|
||||
s.as_str(),
|
||||
@ -794,6 +847,8 @@ impl_lint_pass!(EarlyAttributes => [
|
||||
EMPTY_LINE_AFTER_DOC_COMMENTS,
|
||||
NON_MINIMAL_CFG,
|
||||
MAYBE_MISUSED_CFG,
|
||||
DEPRECATED_CLIPPY_CFG_ATTR,
|
||||
UNNECESSARY_CLIPPY_CFG,
|
||||
]);
|
||||
|
||||
impl EarlyLintPass for EarlyAttributes {
|
||||
@ -803,6 +858,7 @@ impl EarlyLintPass for EarlyAttributes {
|
||||
|
||||
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
check_deprecated_cfg_attr(cx, attr, &self.msrv);
|
||||
check_deprecated_cfg(cx, attr);
|
||||
check_mismatched_target_os(cx, attr);
|
||||
check_minimal_cfg_condition(cx, attr);
|
||||
check_misused_cfg(cx, attr);
|
||||
@ -857,42 +913,149 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It
|
||||
}
|
||||
}
|
||||
|
||||
fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) {
|
||||
if msrv.meets(msrvs::TOOL_ATTRIBUTES)
|
||||
// check cfg_attr
|
||||
&& attr.has_name(sym::cfg_attr)
|
||||
&& let Some(items) = attr.meta_item_list()
|
||||
&& items.len() == 2
|
||||
// check for `rustfmt`
|
||||
&& let Some(feature_item) = items[0].meta_item()
|
||||
&& feature_item.has_name(sym::rustfmt)
|
||||
// check for `rustfmt_skip` and `rustfmt::skip`
|
||||
&& let Some(skip_item) = &items[1].meta_item()
|
||||
&& (skip_item.has_name(sym!(rustfmt_skip))
|
||||
|| skip_item
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
.expect("empty path in attribute")
|
||||
.ident
|
||||
.name
|
||||
== sym::skip)
|
||||
// Only lint outer attributes, because custom inner attributes are unstable
|
||||
// Tracking issue: https://github.com/rust-lang/rust/issues/54726
|
||||
&& attr.style == AttrStyle::Outer
|
||||
{
|
||||
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_CFG_ATTR,
|
||||
attr.span,
|
||||
"`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
|
||||
"use",
|
||||
"#[rustfmt::skip]".to_string(),
|
||||
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) {
|
||||
// check cfg_attr
|
||||
if attr.has_name(sym::cfg_attr)
|
||||
&& let Some(items) = attr.meta_item_list()
|
||||
&& items.len() == 2
|
||||
&& let Some(feature_item) = items[0].meta_item()
|
||||
{
|
||||
// check for `rustfmt`
|
||||
if feature_item.has_name(sym::rustfmt)
|
||||
&& msrv.meets(msrvs::TOOL_ATTRIBUTES)
|
||||
// check for `rustfmt_skip` and `rustfmt::skip`
|
||||
&& let Some(skip_item) = &items[1].meta_item()
|
||||
&& (skip_item.has_name(sym!(rustfmt_skip))
|
||||
|| skip_item
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
.expect("empty path in attribute")
|
||||
.ident
|
||||
.name
|
||||
== sym::skip)
|
||||
// Only lint outer attributes, because custom inner attributes are unstable
|
||||
// Tracking issue: https://github.com/rust-lang/rust/issues/54726
|
||||
&& attr.style == AttrStyle::Outer
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DEPRECATED_CFG_ATTR,
|
||||
attr.span,
|
||||
"`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
|
||||
"use",
|
||||
"#[rustfmt::skip]".to_string(),
|
||||
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
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
|
||||
for item in items {
|
||||
if let NestedMetaItem::MetaItem(meta) = item {
|
||||
|
@ -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::ty::implements_trait;
|
||||
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 rustc_errors::Applicability;
|
||||
use rustc_hir::{BlockCheckMode, Expr, ExprKind, MatchSource};
|
||||
@ -13,7 +13,7 @@ use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### 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.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
@ -25,6 +25,11 @@ declare_clippy_lint! {
|
||||
/// if { true } { /* ... */ }
|
||||
///
|
||||
/// if { let x = somefunc(); x } { /* ... */ }
|
||||
///
|
||||
/// match { let e = somefunc(); e } {
|
||||
/// // ...
|
||||
/// # _ => {}
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
@ -34,6 +39,12 @@ declare_clippy_lint! {
|
||||
///
|
||||
/// let res = { let x = somefunc(); x };
|
||||
/// if res { /* ... */ }
|
||||
///
|
||||
/// let res = { let e = somefunc(); e };
|
||||
/// match res {
|
||||
/// // ...
|
||||
/// # _ => {}
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.45.0"]
|
||||
pub BLOCKS_IN_CONDITIONS,
|
||||
@ -94,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInConditions {
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
// move block higher
|
||||
|
@ -85,7 +85,117 @@ impl<'tcx> LateLintPass<'tcx> for NonminimalBool {
|
||||
) {
|
||||
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> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::macros::macro_backtrace;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
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_hir::def::Res;
|
||||
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_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::print::with_forced_trimmed_paths;
|
||||
@ -41,13 +42,24 @@ declare_lint_pass!(BoxDefault => [BOX_DEFAULT]);
|
||||
|
||||
impl LateLintPass<'_> for BoxDefault {
|
||||
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
|
||||
// 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::Call(arg_path, ..) = arg.kind
|
||||
&& !in_external_macro(cx.sess(), expr.span)
|
||||
&& (expr.span.eq_ctxt(arg.span) || is_local_vec_expn(cx, arg, expr))
|
||||
// And that method is `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())
|
||||
// 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)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
@ -59,7 +71,17 @@ impl LateLintPass<'_> for BoxDefault {
|
||||
if is_plain_default(cx, arg_path) || given_type(cx, expr) {
|
||||
"Box::default()".into()
|
||||
} else if let Some(arg_ty) = cx.typeck_results().expr_ty(arg).make_suggestable(cx.tcx, true) {
|
||||
with_forced_trimmed_paths!(format!("Box::<{arg_ty}>::default()"))
|
||||
// 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()"))
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
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)
|
||||
@ -100,26 +136,23 @@ impl<'tcx> Visitor<'tcx> for InferVisitor {
|
||||
}
|
||||
|
||||
fn given_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
match get_parent_node(cx.tcx, expr.hir_id) {
|
||||
Some(Node::Local(Local { ty: Some(ty), .. })) => {
|
||||
match cx.tcx.parent_hir_node(expr.hir_id) {
|
||||
Node::Local(Local { ty: Some(ty), .. }) => {
|
||||
let mut v = InferVisitor::default();
|
||||
v.visit_ty(ty);
|
||||
!v.0
|
||||
},
|
||||
Some(
|
||||
Node::Expr(Expr {
|
||||
Node::Expr(Expr {
|
||||
kind: ExprKind::Call(path, args),
|
||||
..
|
||||
})
|
||||
| Node::Block(Block {
|
||||
expr: Some(Expr {
|
||||
kind: ExprKind::Call(path, args),
|
||||
..
|
||||
})
|
||||
| Node::Block(Block {
|
||||
expr:
|
||||
Some(Expr {
|
||||
kind: ExprKind::Call(path, args),
|
||||
..
|
||||
}),
|
||||
..
|
||||
}),
|
||||
) => {
|
||||
..
|
||||
}) => {
|
||||
if let Some(index) = args.iter().position(|arg| arg.hir_id == expr.hir_id)
|
||||
&& let Some(sig) = expr_sig(cx, path)
|
||||
&& let Some(input) = sig.input(index)
|
||||
|
@ -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 suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) {
|
||||
(false, false) if from_nbits > to_nbits => "",
|
||||
(true, false) if from_nbits > to_nbits => "",
|
||||
(_, false) if from_nbits > to_nbits => "",
|
||||
(false, true) if from_nbits > 64 => "",
|
||||
(false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers",
|
||||
_ => return,
|
||||
|
@ -1,15 +1,47 @@
|
||||
use std::convert::Infallible;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
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_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty, UintTy};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
|
||||
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) {
|
||||
span_lint(
|
||||
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()) {
|
||||
(true, true) => {
|
||||
if !cast_from.is_signed() || cast_to.is_signed() {
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let (mut uncertain_count, mut negative_count) = (0, 0);
|
||||
// Peel off possible binary expressions, e.g. x * x * y => [x, x, y]
|
||||
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 => (),
|
||||
};
|
||||
if let Sign::ZeroOrPositive = expr_muldiv_sign(cx, cast_op) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lint if there are odd number of uncertain or negative results
|
||||
uncertain_count % 2 == 1 || negative_count % 2 == 1
|
||||
if let Sign::ZeroOrPositive = expr_add_sign(cx, cast_op) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
},
|
||||
|
||||
(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)?
|
||||
&& let ty::Int(ity) = *ty.kind()
|
||||
{
|
||||
@ -66,29 +96,52 @@ fn get_const_int_eval(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Opti
|
||||
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 {
|
||||
ZeroOrPositive,
|
||||
Negative,
|
||||
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
|
||||
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 };
|
||||
}
|
||||
if let Some(_val) = get_const_unsigned_int_eval(cx, expr, None) {
|
||||
return Sign::ZeroOrPositive;
|
||||
}
|
||||
|
||||
// Calling on methods that always return non-negative values.
|
||||
if let ExprKind::MethodCall(path, caller, args, ..) = expr.kind {
|
||||
let mut method_name = path.ident.name.as_str();
|
||||
|
||||
if method_name == "unwrap"
|
||||
&& let Some(arglist) = method_chain_args(expr, &["unwrap"])
|
||||
// Peel unwrap(), expect(), etc.
|
||||
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
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
|
||||
if method_name == "pow"
|
||||
if METHODS_POW.iter().any(|&name| method_name == name)
|
||||
&& let [arg] = args
|
||||
{
|
||||
return pow_call_result_sign(cx, caller, arg);
|
||||
@ -100,53 +153,182 @@ fn expr_sign(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Sign {
|
||||
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 `power_of` is a even number, the result is always positive as well,
|
||||
/// Otherwise a [`Sign::Uncertain`] will be returned.
|
||||
fn pow_call_result_sign(cx: &LateContext<'_>, caller: &Expr<'_>, power_of: &Expr<'_>) -> Sign {
|
||||
let caller_ty = cx.typeck_results().expr_ty(caller);
|
||||
if let Some(caller_val) = get_const_int_eval(cx, caller, caller_ty)
|
||||
&& caller_val >= 0
|
||||
{
|
||||
return Sign::ZeroOrPositive;
|
||||
/// If the base is positive, the result is always positive.
|
||||
/// If the exponent is a even number, the result is always positive,
|
||||
/// Otherwise, if the base is negative, and the exponent is an odd number, the result is always
|
||||
/// negative.
|
||||
///
|
||||
/// Otherwise, returns [`Sign::Uncertain`].
|
||||
fn pow_call_result_sign(cx: &LateContext<'_>, base: &Expr<'_>, exponent: &Expr<'_>) -> Sign {
|
||||
let base_sign = expr_sign(cx, base, None);
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Peels binary operators such as [`BinOpKind::Mul`] or [`BinOpKind::Rem`],
|
||||
/// where the result could always be positive. See [`exprs_with_muldiv_binop_peeled()`] for details.
|
||||
///
|
||||
/// 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 => (),
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(Constant::Int(n)) = constant(cx, cx.typeck_results(), power_of)
|
||||
&& clip(cx.tcx, n, UintTy::U32) % 2 == 0
|
||||
{
|
||||
return 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,
|
||||
};
|
||||
}
|
||||
|
||||
Sign::Uncertain
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
/// 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`
|
||||
fn exprs_with_selected_binop_peeled<'a>(expr: &'a Expr<'_>) -> Option<Vec<&'a Expr<'a>>> {
|
||||
#[inline]
|
||||
fn collect_operands<'a>(expr: &'a Expr<'a>, operands: &mut Vec<&'a Expr<'a>>) -> Option<()> {
|
||||
match expr.kind {
|
||||
ExprKind::Binary(op, lhs, rhs) => {
|
||||
if matches!(op.node, BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem) {
|
||||
collect_operands(lhs, operands);
|
||||
operands.push(rhs);
|
||||
} else {
|
||||
// Things are complicated when there are other binary ops exist,
|
||||
// abort checking by returning `None` for now.
|
||||
return None;
|
||||
}
|
||||
},
|
||||
_ => operands.push(expr),
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Expressions using other operators are preserved, so we can try to evaluate them later.
|
||||
fn exprs_with_muldiv_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
|
||||
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 mul/div/rem methods here, but we could.
|
||||
if let ExprKind::Binary(op, lhs, _rhs) = sub_expr.kind {
|
||||
if matches!(op.node, BinOpKind::Mul | BinOpKind::Div) {
|
||||
// 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 {
|
||||
// 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
|
||||
}
|
||||
|
||||
/// 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![];
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
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::sugg::Sugg;
|
||||
use clippy_utils::{expr_use_ctxt, is_no_std_crate, ExprUseNode};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, Mutability, Ty, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
@ -9,7 +9,12 @@ use rustc_middle::ty::{self, TypeAndMut};
|
||||
|
||||
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) = (
|
||||
cx.typeck_results().expr_ty(cast_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(..))
|
||||
&& 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 fn_name = match to_mutbl {
|
||||
|
@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::numeric_literal::NumericLiteral;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::visitors::{for_each_expr, Visitable};
|
||||
use clippy_utils::{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_errors::Applicability;
|
||||
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
|
||||
} else if let Res::Local(hir_id) = res
|
||||
&& let Some(parent) = get_parent_node(cx.tcx, hir_id)
|
||||
&& let Node::Local(l) = parent
|
||||
&& let Node::Local(l) = cx.tcx.parent_hir_node(hir_id)
|
||||
{
|
||||
if let Some(e) = l.init
|
||||
&& is_cast_from_ty_alias(cx, e, cast_from)
|
||||
|
@ -1,7 +1,7 @@
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
|
||||
use clippy_utils::visitors::for_each_expr_with_closures;
|
||||
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 rustc_hir::{Block, ExprKind, HirId, LangItem, Local, Node, PatKind};
|
||||
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 = ...; // 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
|
||||
&& 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
|
||||
// traits (identified as local, based on the orphan rule), pessimistically assume that they might
|
||||
// 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
|
||||
&& path_to_local_id(receiver, 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:
|
||||
//
|
||||
// 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(());
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
||||
crate::attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON_INFO,
|
||||
crate::attrs::BLANKET_CLIPPY_RESTRICTION_LINTS_INFO,
|
||||
crate::attrs::DEPRECATED_CFG_ATTR_INFO,
|
||||
crate::attrs::DEPRECATED_CLIPPY_CFG_ATTR_INFO,
|
||||
crate::attrs::DEPRECATED_SEMVER_INFO,
|
||||
crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_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::NON_MINIMAL_CFG_INFO,
|
||||
crate::attrs::SHOULD_PANIC_WITHOUT_EXPECT_INFO,
|
||||
crate::attrs::UNNECESSARY_CLIPPY_CFG_INFO,
|
||||
crate::attrs::USELESS_ATTRIBUTE_INFO,
|
||||
crate::await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE_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::doc::DOC_LINK_WITH_QUOTES_INFO,
|
||||
crate::doc::DOC_MARKDOWN_INFO,
|
||||
crate::doc::EMPTY_DOCS_INFO,
|
||||
crate::doc::MISSING_ERRORS_DOC_INFO,
|
||||
crate::doc::MISSING_PANICS_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_FIND_MAP_INFO,
|
||||
crate::methods::UNNECESSARY_FOLD_INFO,
|
||||
crate::methods::UNNECESSARY_GET_THEN_CHECK_INFO,
|
||||
crate::methods::UNNECESSARY_JOIN_INFO,
|
||||
crate::methods::UNNECESSARY_LAZY_EVALUATIONS_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::SELF_NAMED_MODULE_FILES_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::mut_key::MUTABLE_KEY_TYPE_INFO,
|
||||
crate::mut_mut::MUT_MUT_INFO,
|
||||
|
@ -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::sugg::has_enclosing_paren;
|
||||
use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs};
|
||||
use clippy_utils::{
|
||||
expr_use_ctxt, get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode,
|
||||
};
|
||||
use clippy_utils::{expr_use_ctxt, get_parent_expr, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode};
|
||||
use core::mem;
|
||||
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
@ -1008,8 +1006,8 @@ fn report<'tcx>(
|
||||
data.first_expr.span,
|
||||
state.msg,
|
||||
|diag| {
|
||||
let (precedence, calls_field) = match get_parent_node(cx.tcx, data.first_expr.hir_id) {
|
||||
Some(Node::Expr(e)) => match e.kind {
|
||||
let (precedence, calls_field) = match cx.tcx.parent_hir_node(data.first_expr.hir_id) {
|
||||
Node::Expr(e) => match e.kind {
|
||||
ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => (0, false),
|
||||
ExprKind::Call(..) => (PREC_POSTFIX, matches!(expr.kind, ExprKind::Field(..))),
|
||||
_ => (e.precedence().order(), false),
|
||||
|
@ -1,13 +1,16 @@
|
||||
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 rustc_ast::Attribute;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::DiagnosticBuilder;
|
||||
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_session::impl_lint_pass;
|
||||
use rustc_span::{ExpnId, Span};
|
||||
use rustc_span::{ExpnId, MacroKind, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -57,6 +60,10 @@ pub struct DisallowedMacros {
|
||||
conf_disallowed: Vec<DisallowedPath>,
|
||||
disallowed: DefIdMap<usize>,
|
||||
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 {
|
||||
@ -65,10 +72,11 @@ impl DisallowedMacros {
|
||||
conf_disallowed,
|
||||
disallowed: DefIdMap::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() {
|
||||
return;
|
||||
}
|
||||
@ -80,18 +88,26 @@ impl DisallowedMacros {
|
||||
|
||||
if let Some(&index) = self.disallowed.get(&mac.def_id) {
|
||||
let conf = &self.conf_disallowed[index];
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DISALLOWED_MACROS,
|
||||
mac.span,
|
||||
&format!("use of a disallowed macro `{}`", conf.path()),
|
||||
|diag| {
|
||||
if let Some(reason) = conf.reason() {
|
||||
diag.note(reason);
|
||||
}
|
||||
},
|
||||
);
|
||||
let msg = format!("use of a disallowed macro `{}`", conf.path());
|
||||
let add_note = |diag: &mut DiagnosticBuilder<'_, _>| {
|
||||
if let Some(reason) = conf.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<'_>) {
|
||||
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
|
||||
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<'_>) {
|
||||
self.check(cx, stmt.span);
|
||||
self.check(cx, stmt.span, None);
|
||||
}
|
||||
|
||||
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<'_>) {
|
||||
self.check(cx, pat.span);
|
||||
self.check(cx, pat.span, None);
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
self.check(cx, item.span);
|
||||
self.check(cx, item.vis_span);
|
||||
self.check(cx, item.span, self.derive_src);
|
||||
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<'_>) {
|
||||
self.check(cx, item.span);
|
||||
self.check(cx, item.vis_span);
|
||||
self.check(cx, item.span, None);
|
||||
self.check(cx, item.vis_span, None);
|
||||
}
|
||||
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) {
|
||||
self.check(cx, item.span);
|
||||
self.check(cx, item.vis_span);
|
||||
self.check(cx, item.span, None);
|
||||
self.check(cx, item.vis_span, None);
|
||||
}
|
||||
|
||||
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) {
|
||||
self.check(cx, path.span);
|
||||
self.check(cx, path.span, None);
|
||||
}
|
||||
|
||||
fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) {
|
||||
self.check(cx, attr.span);
|
||||
self.check(cx, attr.span, self.derive_src);
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,8 @@ use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty;
|
||||
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_span::edition::Edition;
|
||||
@ -338,6 +339,30 @@ declare_clippy_lint! {
|
||||
"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)]
|
||||
pub struct Documentation {
|
||||
valid_idents: FxHashSet<String>,
|
||||
@ -364,7 +389,8 @@ impl_lint_pass!(Documentation => [
|
||||
NEEDLESS_DOCTEST_MAIN,
|
||||
TEST_ATTR_IN_DOCTEST,
|
||||
UNNECESSARY_SAFETY_DOC,
|
||||
SUSPICIOUS_DOC_COMMENTS
|
||||
SUSPICIOUS_DOC_COMMENTS,
|
||||
EMPTY_DOCS,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Documentation {
|
||||
@ -373,11 +399,22 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
|
||||
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<'_>) {
|
||||
let attrs = cx.tcx.hir().attrs(item.hir_id());
|
||||
let Some(headers) = check_attrs(cx, &self.valid_idents, attrs) else {
|
||||
return;
|
||||
};
|
||||
|
||||
match item.kind {
|
||||
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)) {
|
||||
@ -502,13 +539,23 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
|
||||
suspicious_doc_comments::check(cx, attrs);
|
||||
|
||||
let (fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true);
|
||||
let mut doc = String::new();
|
||||
for fragment in &fragments {
|
||||
add_doc_fragment(&mut doc, fragment);
|
||||
}
|
||||
let mut doc = fragments.iter().fold(String::new(), |mut acc, fragment| {
|
||||
add_doc_fragment(&mut acc, fragment);
|
||||
acc
|
||||
});
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
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::{get_parent_node, is_must_use_func_call};
|
||||
use rustc_hir::{Arm, Expr, ExprKind, LangItem, Node};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
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 {
|
||||
if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) {
|
||||
let parent_node = get_parent_node(cx.tcx, drop_expr.hir_id);
|
||||
if let Some(Node::Arm(Arm { body, .. })) = &parent_node {
|
||||
if let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id) {
|
||||
return body.hir_id == drop_expr.hir_id;
|
||||
}
|
||||
}
|
||||
|
@ -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::macros::{
|
||||
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::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::Ty;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::edition::Edition::Edition2021;
|
||||
use rustc_span::{sym, Span, Symbol};
|
||||
|
||||
@ -189,32 +188,18 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
|
||||
&& is_format_macro(cx, macro_call.def_id)
|
||||
&& let Some(format_args) = find_format_args(cx, expr, macro_call.expn)
|
||||
{
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
&& let Some(arg) = format_args.arguments.all_args().get(index)
|
||||
{
|
||||
let arg_expr = find_format_arg_expr(expr, arg);
|
||||
let linter = FormatArgsExpr {
|
||||
cx,
|
||||
expr,
|
||||
macro_call: ¯o_call,
|
||||
format_args: &format_args,
|
||||
ignore_mixed: self.ignore_mixed,
|
||||
};
|
||||
|
||||
check_unused_format_specifier(cx, placeholder, arg_expr);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
linter.check_templates();
|
||||
|
||||
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,255 +207,279 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
|
||||
extract_msrv_attr!(LateContext);
|
||||
}
|
||||
|
||||
fn check_unused_format_specifier(
|
||||
cx: &LateContext<'_>,
|
||||
placeholder: &FormatPlaceholder,
|
||||
arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>,
|
||||
) {
|
||||
let ty_or_ast_expr = arg_expr.map(|expr| cx.typeck_results().expr_ty(expr).peel_refs());
|
||||
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,
|
||||
}
|
||||
|
||||
let is_format_args = match ty_or_ast_expr {
|
||||
Ok(ty) => is_type_lang_item(cx, ty, LangItem::FormatArguments),
|
||||
Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)),
|
||||
};
|
||||
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);
|
||||
|
||||
let options = &placeholder.format_options;
|
||||
self.check_unused_format_specifier(placeholder, arg_expr);
|
||||
|
||||
let arg_span = match arg_expr {
|
||||
Ok(expr) => expr.span,
|
||||
Err(expr) => expr.span,
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(placeholder_span) = placeholder.span
|
||||
&& is_format_args
|
||||
&& *options != FormatOptions::default()
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
UNUSED_FORMAT_SPECS,
|
||||
placeholder_span,
|
||||
"format specifiers have no effect on `format_args!()`",
|
||||
|diag| {
|
||||
let mut suggest_format = |spec| {
|
||||
let message = format!("for the {spec} to apply consider using `format!()`");
|
||||
fn check_unused_format_specifier(
|
||||
&self,
|
||||
placeholder: &FormatPlaceholder,
|
||||
arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>,
|
||||
) {
|
||||
let ty_or_ast_expr = arg_expr.map(|expr| self.cx.typeck_results().expr_ty(expr).peel_refs());
|
||||
|
||||
if let Some(mac_call) = root_macro_call(arg_span)
|
||||
&& cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id)
|
||||
{
|
||||
diag.span_suggestion(
|
||||
cx.sess().source_map().span_until_char(mac_call.span, '!'),
|
||||
message,
|
||||
"format",
|
||||
let is_format_args = match ty_or_ast_expr {
|
||||
Ok(ty) => is_type_lang_item(self.cx, ty, LangItem::FormatArguments),
|
||||
Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)),
|
||||
};
|
||||
|
||||
let options = &placeholder.format_options;
|
||||
|
||||
let arg_span = match arg_expr {
|
||||
Ok(expr) => expr.span,
|
||||
Err(expr) => expr.span,
|
||||
};
|
||||
|
||||
if let Some(placeholder_span) = placeholder.span
|
||||
&& is_format_args
|
||||
&& *options != FormatOptions::default()
|
||||
{
|
||||
span_lint_and_then(
|
||||
self.cx,
|
||||
UNUSED_FORMAT_SPECS,
|
||||
placeholder_span,
|
||||
"format specifiers have no effect on `format_args!()`",
|
||||
|diag| {
|
||||
let mut suggest_format = |spec| {
|
||||
let message = format!("for the {spec} to apply consider using `format!()`");
|
||||
|
||||
if let Some(mac_call) = root_macro_call(arg_span)
|
||||
&& self.cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id)
|
||||
{
|
||||
diag.span_suggestion(
|
||||
self.cx.sess().source_map().span_until_char(mac_call.span, '!'),
|
||||
message,
|
||||
"format",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
diag.help(message);
|
||||
}
|
||||
};
|
||||
|
||||
if options.width.is_some() {
|
||||
suggest_format("width");
|
||||
}
|
||||
|
||||
if options.precision.is_some() {
|
||||
suggest_format("precision");
|
||||
}
|
||||
|
||||
if let Some(format_span) = format_placeholder_format_span(placeholder) {
|
||||
diag.span_suggestion_verbose(
|
||||
format_span,
|
||||
"if the current behavior is intentional, remove the format specifiers",
|
||||
"",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
diag.help(message);
|
||||
}
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if options.width.is_some() {
|
||||
suggest_format("width");
|
||||
}
|
||||
fn check_uninlined_args(&self) {
|
||||
if self.format_args.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
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
|
||||
// non-format
|
||||
return;
|
||||
}
|
||||
|
||||
if options.precision.is_some() {
|
||||
suggest_format("precision");
|
||||
}
|
||||
let mut fixes = Vec::new();
|
||||
// If any of the arguments are referenced by an index number,
|
||||
// and that argument is not a simple variable and cannot be inlined,
|
||||
// we cannot remove any other arguments in the format string,
|
||||
// because the index numbers might be wrong after inlining.
|
||||
// Example of an un-inlinable format: print!("{}{1}", foo, 2)
|
||||
for (pos, usage) in self.format_arg_positions() {
|
||||
if !self.check_one_arg(pos, usage, &mut fixes) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(format_span) = format_placeholder_format_span(placeholder) {
|
||||
diag.span_suggestion_verbose(
|
||||
format_span,
|
||||
"if the current behavior is intentional, remove the format specifiers",
|
||||
"",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
if fixes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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
|
||||
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)`.
|
||||
fixes.sort_unstable_by_key(|(span, _)| *span);
|
||||
fixes.dedup_by_key(|(span, _)| *span);
|
||||
|
||||
span_lint_and_then(
|
||||
self.cx,
|
||||
UNINLINED_FORMAT_ARGS,
|
||||
self.macro_call.span,
|
||||
"variables can be used directly in the `format!` string",
|
||||
|diag| {
|
||||
diag.multipart_suggestion_with_style(
|
||||
"change this to",
|
||||
fixes,
|
||||
Applicability::MachineApplicable,
|
||||
if multiline_fix { CompletelyHidden } else { ShowCode },
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_uninlined_args(
|
||||
cx: &LateContext<'_>,
|
||||
args: &rustc_ast::FormatArgs,
|
||||
call_site: Span,
|
||||
def_id: DefId,
|
||||
ignore_mixed: bool,
|
||||
) {
|
||||
if args.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
if call_site.edition() < Edition2021 && (is_panic(cx, def_id) || is_assert_macro(cx, def_id)) {
|
||||
// panic!, assert!, and debug_assert! before 2021 edition considers a single string argument as
|
||||
// non-format
|
||||
return;
|
||||
fn check_one_arg(&self, pos: &FormatArgPosition, usage: FormatParamUsage, fixes: &mut Vec<(Span, String)>) -> bool {
|
||||
let index = pos.index.unwrap();
|
||||
let arg = &self.format_args.arguments.all_args()[index];
|
||||
|
||||
if !matches!(arg.kind, FormatArgumentKind::Captured(_))
|
||||
&& let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind
|
||||
&& let [segment] = path.segments.as_slice()
|
||||
&& segment.args.is_none()
|
||||
&& let Some(arg_span) = format_arg_removal_span(self.format_args, index)
|
||||
&& let Some(pos_span) = pos.span
|
||||
{
|
||||
let replacement = match usage {
|
||||
FormatParamUsage::Argument => segment.ident.name.to_string(),
|
||||
FormatParamUsage::Width => format!("{}$", segment.ident.name),
|
||||
FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
|
||||
};
|
||||
fixes.push((pos_span, replacement));
|
||||
fixes.push((arg_span, String::new()));
|
||||
true // successful inlining, continue checking
|
||||
} else {
|
||||
// Do not continue inlining (return false) in case
|
||||
// * 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
|
||||
pos.kind != FormatArgPositionKind::Number
|
||||
&& (!self.ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_)))
|
||||
}
|
||||
}
|
||||
|
||||
let mut fixes = Vec::new();
|
||||
// If any of the arguments are referenced by an index number,
|
||||
// and that argument is not a simple variable and cannot be inlined,
|
||||
// we cannot remove any other arguments in the format string,
|
||||
// because the index numbers might be wrong after inlining.
|
||||
// Example of an un-inlinable format: print!("{}{1}", foo, 2)
|
||||
for (pos, usage) in format_arg_positions(args) {
|
||||
if !check_one_arg(args, pos, usage, &mut fixes, ignore_mixed) {
|
||||
fn check_format_in_format_args(&self, name: Symbol, arg: &Expr<'_>) {
|
||||
let expn_data = arg.span.ctxt().outer_expn_data();
|
||||
if expn_data.call_site.from_expansion() {
|
||||
return;
|
||||
}
|
||||
let Some(mac_id) = expn_data.macro_def_id else { return };
|
||||
if !self.cx.tcx.is_diagnostic_item(sym::format_macro, mac_id) {
|
||||
return;
|
||||
}
|
||||
span_lint_and_then(
|
||||
self.cx,
|
||||
FORMAT_IN_FORMAT_ARGS,
|
||||
self.macro_call.span,
|
||||
&format!("`format!` in `{name}!` args"),
|
||||
|diag| {
|
||||
diag.help(format!(
|
||||
"combine the `format!(..)` arguments with the outer `{name}!(..)` call"
|
||||
));
|
||||
diag.help("or consider changing `format!` to `format_args!`");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if fixes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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
|
||||
let multiline_fix = fixes.iter().any(|(span, _)| cx.sess().source_map().is_multiline(*span));
|
||||
|
||||
// Suggest removing each argument only once, for example in `format!("{0} {0}", arg)`.
|
||||
fixes.sort_unstable_by_key(|(span, _)| *span);
|
||||
fixes.dedup_by_key(|(span, _)| *span);
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
UNINLINED_FORMAT_ARGS,
|
||||
call_site,
|
||||
"variables can be used directly in the `format!` string",
|
||||
|diag| {
|
||||
diag.multipart_suggestion_with_style(
|
||||
"change this to",
|
||||
fixes,
|
||||
Applicability::MachineApplicable,
|
||||
if multiline_fix { CompletelyHidden } else { ShowCode },
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn check_one_arg(
|
||||
args: &rustc_ast::FormatArgs,
|
||||
pos: &FormatArgPosition,
|
||||
usage: FormatParamUsage,
|
||||
fixes: &mut Vec<(Span, String)>,
|
||||
ignore_mixed: bool,
|
||||
) -> bool {
|
||||
let index = pos.index.unwrap();
|
||||
let arg = &args.arguments.all_args()[index];
|
||||
|
||||
if !matches!(arg.kind, FormatArgumentKind::Captured(_))
|
||||
&& let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind
|
||||
&& let [segment] = path.segments.as_slice()
|
||||
&& segment.args.is_none()
|
||||
&& let Some(arg_span) = format_arg_removal_span(args, index)
|
||||
&& let Some(pos_span) = pos.span
|
||||
{
|
||||
let replacement = match usage {
|
||||
FormatParamUsage::Argument => segment.ident.name.to_string(),
|
||||
FormatParamUsage::Width => format!("{}$", segment.ident.name),
|
||||
FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
|
||||
};
|
||||
fixes.push((pos_span, replacement));
|
||||
fixes.push((arg_span, String::new()));
|
||||
true // successful inlining, continue checking
|
||||
} else {
|
||||
// Do not continue inlining (return false) in case
|
||||
// * 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
|
||||
pos.kind != FormatArgPositionKind::Number
|
||||
&& (!ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_)))
|
||||
}
|
||||
}
|
||||
|
||||
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &Expr<'_>) {
|
||||
let expn_data = arg.span.ctxt().outer_expn_data();
|
||||
if expn_data.call_site.from_expansion() {
|
||||
return;
|
||||
}
|
||||
let Some(mac_id) = expn_data.macro_def_id else { return };
|
||||
if !cx.tcx.is_diagnostic_item(sym::format_macro, mac_id) {
|
||||
return;
|
||||
}
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
FORMAT_IN_FORMAT_ARGS,
|
||||
call_site,
|
||||
&format!("`format!` in `{name}!` args"),
|
||||
|diag| {
|
||||
diag.help(format!(
|
||||
"combine the `format!(..)` arguments with the outer `{name}!(..)` call"
|
||||
));
|
||||
diag.help("or consider changing `format!` to `format_args!`");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Expr<'_>) {
|
||||
if !value.span.from_expansion()
|
||||
&& let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind
|
||||
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
|
||||
&& is_diag_trait_item(cx, method_def_id, sym::ToString)
|
||||
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
|
||||
&& let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display)
|
||||
&& let (n_needed_derefs, target) =
|
||||
count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter())
|
||||
&& implements_trait(cx, target, display_trait_id, &[])
|
||||
&& let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait()
|
||||
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
|
||||
{
|
||||
let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
|
||||
if n_needed_derefs == 0 && !needs_ref {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
to_string_span.with_lo(receiver.span.hi()),
|
||||
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
|
||||
"remove this",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
value.span,
|
||||
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
|
||||
"use this",
|
||||
format!(
|
||||
"{}{:*>n_needed_derefs$}{receiver_snippet}",
|
||||
if needs_ref { "&" } else { "" },
|
||||
""
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
fn check_to_string_in_format_args(&self, name: Symbol, value: &Expr<'_>) {
|
||||
let cx = self.cx;
|
||||
if !value.span.from_expansion()
|
||||
&& let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind
|
||||
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
|
||||
&& is_diag_trait_item(cx, method_def_id, sym::ToString)
|
||||
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
|
||||
&& let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display)
|
||||
&& let (n_needed_derefs, target) =
|
||||
count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter())
|
||||
&& implements_trait(cx, target, display_trait_id, &[])
|
||||
&& let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait()
|
||||
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
|
||||
{
|
||||
let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
|
||||
if n_needed_derefs == 0 && !needs_ref {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
to_string_span.with_lo(receiver.span.hi()),
|
||||
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
|
||||
"remove this",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
value.span,
|
||||
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
|
||||
"use this",
|
||||
format!(
|
||||
"{}{:*>n_needed_derefs$}{receiver_snippet}",
|
||||
if needs_ref { "&" } else { "" },
|
||||
""
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_arg_positions(
|
||||
format_args: &rustc_ast::FormatArgs,
|
||||
) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> {
|
||||
format_args.template.iter().flat_map(|piece| match piece {
|
||||
FormatArgsPiece::Placeholder(placeholder) => {
|
||||
let mut positions = ArrayVec::<_, 3>::new();
|
||||
fn format_arg_positions(&self) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> {
|
||||
self.format_args.template.iter().flat_map(|piece| match piece {
|
||||
FormatArgsPiece::Placeholder(placeholder) => {
|
||||
let mut positions = ArrayVec::<_, 3>::new();
|
||||
|
||||
positions.push((&placeholder.argument, FormatParamUsage::Argument));
|
||||
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width {
|
||||
positions.push((position, FormatParamUsage::Width));
|
||||
}
|
||||
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision {
|
||||
positions.push((position, FormatParamUsage::Precision));
|
||||
}
|
||||
positions.push((&placeholder.argument, FormatParamUsage::Argument));
|
||||
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width {
|
||||
positions.push((position, FormatParamUsage::Width));
|
||||
}
|
||||
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision {
|
||||
positions.push((position, FormatParamUsage::Precision));
|
||||
}
|
||||
|
||||
positions
|
||||
},
|
||||
FormatArgsPiece::Literal(_) => ArrayVec::new(),
|
||||
})
|
||||
}
|
||||
positions
|
||||
},
|
||||
FormatArgsPiece::Literal(_) => ArrayVec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
format_arg_positions(format_args)
|
||||
.filter(|(position, _)| position.index == Ok(index))
|
||||
.at_most_one()
|
||||
.is_err()
|
||||
/// Returns true if the format argument at `index` is referred to by multiple format params
|
||||
fn is_aliased(&self, index: usize) -> bool {
|
||||
self.format_arg_positions()
|
||||
.filter(|(position, _)| position.index == Ok(index))
|
||||
.at_most_one()
|
||||
.is_err()
|
||||
}
|
||||
}
|
||||
|
||||
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
|
||||
|
@ -7,7 +7,7 @@ use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::symbol::kw;
|
||||
use rustc_span::{sym, Span, Symbol};
|
||||
use rustc_span::{sym, Symbol};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -119,123 +119,132 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl {
|
||||
}
|
||||
|
||||
fn check_impl_item_post(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
|
||||
// Assume no nested Impl of Debug and Display within eachother
|
||||
// Assume no nested Impl of Debug and Display within each other
|
||||
if is_format_trait_impl(cx, impl_item).is_some() {
|
||||
self.format_trait_impl = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let Some(format_trait_impl) = self.format_trait_impl else {
|
||||
return;
|
||||
};
|
||||
|
||||
if format_trait_impl.name == sym::Display {
|
||||
check_to_string_in_display(cx, expr);
|
||||
if let Some(format_trait_impl) = self.format_trait_impl {
|
||||
let linter = FormatImplExpr {
|
||||
cx,
|
||||
expr,
|
||||
format_trait_impl,
|
||||
};
|
||||
linter.check_to_string_in_display();
|
||||
linter.check_self_in_format_args();
|
||||
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<'_>) {
|
||||
if let ExprKind::MethodCall(path, self_arg, ..) = expr.kind
|
||||
// Get the hir_id of the object we are calling the method on
|
||||
// Is the method to_string() ?
|
||||
&& path.ident.name == sym::to_string
|
||||
// Is the method a part of the ToString trait? (i.e. not to_string() implemented
|
||||
// separately)
|
||||
&& let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& is_diag_trait_item(cx, expr_def_id, sym::ToString)
|
||||
// Is the method is called on self
|
||||
&& let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind
|
||||
&& let [segment] = path.segments
|
||||
&& segment.ident.name == kw::SelfLower
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
expr.span,
|
||||
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
|
||||
);
|
||||
}
|
||||
struct FormatImplExpr<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
format_trait_impl: FormatTraitNames,
|
||||
}
|
||||
|
||||
fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTraitNames) {
|
||||
// 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)
|
||||
&& let macro_def_id = outer_macro.def_id
|
||||
&& is_format_macro(cx, macro_def_id)
|
||||
&& let Some(format_args) = find_format_args(cx, expr, outer_macro.expn)
|
||||
{
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& let trait_name = match placeholder.format_trait {
|
||||
FormatTrait::Display => sym::Display,
|
||||
FormatTrait::Debug => sym::Debug,
|
||||
FormatTrait::LowerExp => sym!(LowerExp),
|
||||
FormatTrait::UpperExp => sym!(UpperExp),
|
||||
FormatTrait::Octal => sym!(Octal),
|
||||
FormatTrait::Pointer => sym::Pointer,
|
||||
FormatTrait::Binary => sym!(Binary),
|
||||
FormatTrait::LowerHex => sym!(LowerHex),
|
||||
FormatTrait::UpperHex => sym!(UpperHex),
|
||||
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
|
||||
// Is the method to_string() ?
|
||||
&& path.ident.name == sym::to_string
|
||||
// Is the method a part of the ToString trait? (i.e. not to_string() implemented
|
||||
// separately)
|
||||
&& let Some(expr_def_id) = self.cx.typeck_results().type_dependent_def_id(self.expr.hir_id)
|
||||
&& is_diag_trait_item(self.cx, expr_def_id, sym::ToString)
|
||||
// Is the method is called on self
|
||||
&& let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind
|
||||
&& let [segment] = path.segments
|
||||
&& segment.ident.name == kw::SelfLower
|
||||
{
|
||||
span_lint(
|
||||
self.cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
self.expr.span,
|
||||
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_self_in_format_args(&self) {
|
||||
// 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(self.cx, self.expr)
|
||||
&& let macro_def_id = outer_macro.def_id
|
||||
&& is_format_macro(self.cx, macro_def_id)
|
||||
&& let Some(format_args) = find_format_args(self.cx, self.expr, outer_macro.expn)
|
||||
{
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& let trait_name = match placeholder.format_trait {
|
||||
FormatTrait::Display => sym::Display,
|
||||
FormatTrait::Debug => sym::Debug,
|
||||
FormatTrait::LowerExp => sym!(LowerExp),
|
||||
FormatTrait::UpperExp => sym!(UpperExp),
|
||||
FormatTrait::Octal => sym!(Octal),
|
||||
FormatTrait::Pointer => sym::Pointer,
|
||||
FormatTrait::Binary => sym!(Binary),
|
||||
FormatTrait::LowerHex => sym!(LowerHex),
|
||||
FormatTrait::UpperHex => sym!(UpperHex),
|
||||
}
|
||||
&& trait_name == self.format_trait_impl.name
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
&& let Some(arg) = format_args.arguments.all_args().get(index)
|
||||
&& let Ok(arg_expr) = find_format_arg_expr(self.expr, arg)
|
||||
{
|
||||
self.check_format_arg_self(arg_expr);
|
||||
}
|
||||
&& trait_name == impl_trait.name
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
&& let Some(arg) = format_args.arguments.all_args().get(index)
|
||||
&& let Ok(arg_expr) = find_format_arg_expr(expr, arg)
|
||||
{
|
||||
check_format_arg_self(cx, expr.span, arg_expr, impl_trait);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_format_arg_self(cx: &LateContext<'_>, span: Span, arg: &Expr<'_>, impl_trait: FormatTraitNames) {
|
||||
// Handle multiple dereferencing of references e.g. &&self
|
||||
// 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
|
||||
let reference = peel_ref_operators(cx, arg);
|
||||
let map = cx.tcx.hir();
|
||||
// Is the reference self?
|
||||
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
|
||||
let FormatTraitNames { name, .. } = impl_trait;
|
||||
span_lint(
|
||||
cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
span,
|
||||
&format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
|
||||
);
|
||||
fn check_format_arg_self(&self, arg: &Expr<'_>) {
|
||||
// Handle multiple dereferencing of references e.g. &&self
|
||||
// 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
|
||||
let reference = peel_ref_operators(self.cx, arg);
|
||||
let map = self.cx.tcx.hir();
|
||||
// Is the reference self?
|
||||
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
|
||||
let FormatTraitNames { name, .. } = self.format_trait_impl;
|
||||
span_lint(
|
||||
self.cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
self.expr.span,
|
||||
&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) {
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
|
||||
&& let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id)
|
||||
{
|
||||
let replacement = match name {
|
||||
sym::print_macro | sym::eprint_macro => "write",
|
||||
sym::println_macro | sym::eprintln_macro => "writeln",
|
||||
_ => return,
|
||||
};
|
||||
fn check_print_in_format_impl(&self) {
|
||||
if let Some(macro_call) = root_macro_call_first_node(self.cx, self.expr)
|
||||
&& let Some(name) = self.cx.tcx.get_diagnostic_name(macro_call.def_id)
|
||||
{
|
||||
let replacement = match name {
|
||||
sym::print_macro | sym::eprint_macro => "write",
|
||||
sym::println_macro | sym::eprintln_macro => "writeln",
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let name = name.as_str().strip_suffix("_macro").unwrap();
|
||||
let name = name.as_str().strip_suffix("_macro").unwrap();
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
PRINT_IN_FORMAT_IMPL,
|
||||
macro_call.span,
|
||||
&format!("use of `{name}!` in `{}` impl", impl_trait.name),
|
||||
"replace with",
|
||||
if let Some(formatter_name) = impl_trait.formatter_name {
|
||||
format!("{replacement}!({formatter_name}, ..)")
|
||||
} else {
|
||||
format!("{replacement}!(..)")
|
||||
},
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
self.cx,
|
||||
PRINT_IN_FORMAT_IMPL,
|
||||
macro_call.span,
|
||||
&format!("use of `{name}!` in `{}` impl", self.format_trait_impl.name),
|
||||
"replace with",
|
||||
if let Some(formatter_name) = self.format_trait_impl.formatter_name {
|
||||
format!("{replacement}!({formatter_name}, ..)")
|
||||
} else {
|
||||
format!("{replacement}!(..)")
|
||||
},
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet;
|
||||
use rustc_errors::{Applicability, SuggestionStyle};
|
||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{
|
||||
Body, FnDecl, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, ItemKind, TraitBoundModifier, TraitItem,
|
||||
TraitItemKind, TyKind,
|
||||
GenericArg, GenericBound, GenericBounds, ItemKind, PredicateOrigin, TraitBoundModifier, TyKind, TypeBinding,
|
||||
WherePredicate,
|
||||
};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@ -50,20 +49,17 @@ declare_clippy_lint! {
|
||||
}
|
||||
declare_lint_pass!(ImpliedBoundsInImpls => [IMPLIED_BOUNDS_IN_IMPLS]);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn emit_lint(
|
||||
cx: &LateContext<'_>,
|
||||
poly_trait: &rustc_hir::PolyTraitRef<'_>,
|
||||
opaque_ty: &rustc_hir::OpaqueTy<'_>,
|
||||
bounds: GenericBounds<'_>,
|
||||
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<'_>],
|
||||
// The original bindings that `implied_bindings` are implied from
|
||||
implied_by_bindings: &[rustc_hir::TypeBinding<'_>],
|
||||
implied_by_args: &[GenericArg<'_>],
|
||||
implied_by_span: Span,
|
||||
bound: &ImplTraitBound<'_>,
|
||||
) {
|
||||
let implied_by = snippet(cx, implied_by_span, "..");
|
||||
let implied_by = snippet(cx, bound.span, "..");
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
@ -75,10 +71,10 @@ fn emit_lint(
|
||||
// to include the `+` token that is ahead or behind,
|
||||
// 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())
|
||||
} 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())
|
||||
} 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`
|
||||
let omitted_assoc_tys: Vec<_> = implied_bindings
|
||||
.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();
|
||||
|
||||
if !omitted_assoc_tys.is_empty() {
|
||||
// `<>` 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 insert_span = match (implied_by_args, implied_by_bindings) {
|
||||
let needs_angle_brackets = bound.args.is_empty() && bound.bindings.is_empty();
|
||||
let insert_span = match (bound.args, bound.bindings) {
|
||||
([.., arg], [.., binding]) => arg.span().max(binding.span).shrink_to_hi(),
|
||||
([.., arg], []) => arg.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 {
|
||||
@ -223,111 +219,135 @@ fn is_same_generics<'tcx>(
|
||||
})
|
||||
}
|
||||
|
||||
fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
|
||||
if let FnRetTy::Return(ty) = decl.output
|
||||
&&let TyKind::OpaqueDef(item_id, ..) = ty.kind
|
||||
&& let item = cx.tcx.hir().item(item_id)
|
||||
&& let ItemKind::OpaqueTy(opaque_ty) = item.kind
|
||||
// Very often there is only a single bound, e.g. `impl Deref<..>`, in which case
|
||||
// we can avoid doing a bunch of stuff unnecessarily.
|
||||
&& opaque_ty.bounds.len() > 1
|
||||
{
|
||||
// Get all the (implied) trait predicates in the bounds.
|
||||
// For `impl Deref + DerefMut` this will contain [`Deref`].
|
||||
// The implied `Deref` comes from `DerefMut` because `trait DerefMut: Deref {}`.
|
||||
// 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.
|
||||
// `DerefMut::Target` needs to match `Deref::Target`.
|
||||
let implied_bounds: Vec<_> = opaque_ty
|
||||
.bounds
|
||||
.iter()
|
||||
.filter_map(|bound| {
|
||||
if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound
|
||||
&& let [.., path] = poly_trait.trait_ref.path.segments
|
||||
&& poly_trait.bound_generic_params.is_empty()
|
||||
&& let Some(trait_def_id) = path.res.opt_def_id()
|
||||
&& let predicates = cx.tcx.super_predicates_of(trait_def_id).predicates
|
||||
&& !predicates.is_empty()
|
||||
// If the trait has no supertrait, there is nothing to add.
|
||||
{
|
||||
Some((bound.span(), path, predicates, trait_def_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
struct ImplTraitBound<'tcx> {
|
||||
/// The span of the bound in the `impl Trait` type
|
||||
span: Span,
|
||||
/// The predicates defined in the trait referenced by this bound. This also contains the actual
|
||||
/// supertrait bounds
|
||||
predicates: &'tcx [(ty::Clause<'tcx>, Span)],
|
||||
/// The `DefId` of the trait being referenced by this bound
|
||||
trait_def_id: DefId,
|
||||
/// The generic arguments on the `impl Trait` bound
|
||||
args: &'tcx [GenericArg<'tcx>],
|
||||
/// The associated types on this bound
|
||||
bindings: &'tcx [TypeBinding<'tcx>],
|
||||
}
|
||||
|
||||
// Lint all bounds in the `impl Trait` type that are also in the `implied_bounds` vec.
|
||||
// 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.
|
||||
for (index, bound) in opaque_ty.bounds.iter().enumerate() {
|
||||
/// Given an `impl Trait` type, gets all the supertraits from each bound ("implied bounds").
|
||||
///
|
||||
/// For `impl Deref + DerefMut + Eq` this returns `[Deref, PartialEq]`.
|
||||
/// 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()
|
||||
.filter_map(|bound| {
|
||||
if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound
|
||||
&& let [.., path] = poly_trait.trait_ref.path.segments
|
||||
&& 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 Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id()
|
||||
&& let Some((implied_by_span, implied_by_args, implied_by_bindings)) =
|
||||
implied_bounds
|
||||
.iter()
|
||||
.find_map(|&(span, implied_by_path, preds, implied_by_def_id)| {
|
||||
let implied_by_args = implied_by_path.args.map_or([].as_slice(), |a| a.args);
|
||||
let implied_by_bindings = implied_by_path.args.map_or([].as_slice(), |a| a.bindings);
|
||||
|
||||
preds.iter().find_map(|(clause, _)| {
|
||||
if let ClauseKind::Trait(tr) = clause.kind().skip_binder()
|
||||
&& 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
|
||||
}
|
||||
})
|
||||
})
|
||||
&& poly_trait.bound_generic_params.is_empty()
|
||||
&& let Some(trait_def_id) = path.res.opt_def_id()
|
||||
&& 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()
|
||||
{
|
||||
emit_lint(
|
||||
cx,
|
||||
poly_trait,
|
||||
opaque_ty,
|
||||
index,
|
||||
implied_bindings,
|
||||
implied_by_bindings,
|
||||
implied_by_args,
|
||||
implied_by_span,
|
||||
);
|
||||
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 {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 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
|
||||
// simply comparing trait `DefId`s won't be enough. We also need to compare the generics.
|
||||
for (index, bound) in bounds.iter().enumerate() {
|
||||
if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound
|
||||
&& let [.., path] = poly_trait.trait_ref.path.segments
|
||||
&& 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 Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id()
|
||||
&& let Some(bound) = find_bound_in_supertraits(cx, def_id, implied_args, &supertraits)
|
||||
// If the implied bound has a type binding that also exists in the implied-by trait,
|
||||
// then we shouldn't lint. See #11880 for an example.
|
||||
&& let assocs = cx.tcx.associated_items(bound.trait_def_id)
|
||||
&& !implied_bindings.iter().any(|binding| {
|
||||
assocs
|
||||
.filter_by_name_unhygienic(binding.ident.name)
|
||||
.next()
|
||||
.is_some_and(|assoc| assoc.kind == ty::AssocKind::Type)
|
||||
})
|
||||
{
|
||||
emit_lint(cx, poly_trait, bounds, index, implied_bindings, bound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ImpliedBoundsInImpls {
|
||||
fn check_generics(&mut self, cx: &LateContext<'tcx>, generics: &rustc_hir::Generics<'tcx>) {
|
||||
for predicate in generics.predicates {
|
||||
if let WherePredicate::BoundPredicate(predicate) = predicate
|
||||
// In theory, the origin doesn't really matter,
|
||||
// we *could* also lint on explicit where clauses written out by the user,
|
||||
// not just impl trait desugared ones, but that contradicts with the lint name...
|
||||
&& let PredicateOrigin::ImplTrait = predicate.origin
|
||||
{
|
||||
check(cx, predicate.bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LateLintPass<'_> for ImpliedBoundsInImpls {
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'_>,
|
||||
_: FnKind<'_>,
|
||||
decl: &FnDecl<'_>,
|
||||
_: &Body<'_>,
|
||||
_: Span,
|
||||
_: LocalDefId,
|
||||
) {
|
||||
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_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) {
|
||||
if let ImplItemKind::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)
|
||||
&& let ItemKind::OpaqueTy(opaque_ty) = item.kind
|
||||
{
|
||||
check(cx, opaque_ty.bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
use clippy_config::msrvs::Msrv;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::is_in_test_function;
|
||||
use rustc_attr::{StabilityLevel, StableSince};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_hir::{Expr, ExprKind, HirId};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::{ExpnKind, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -81,13 +82,18 @@ impl IncompatibleMsrv {
|
||||
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() {
|
||||
// We don't check local items since their MSRV is supposed to always be valid.
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
self.emit_lint_for(cx, span, version);
|
||||
@ -117,14 +123,14 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
|
||||
match expr.kind {
|
||||
ExprKind::MethodCall(_, _, _, span) => {
|
||||
if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
|
||||
self.emit_lint_if_under_msrv(cx, method_did, span);
|
||||
self.emit_lint_if_under_msrv(cx, method_did, expr.hir_id, span);
|
||||
}
|
||||
},
|
||||
ExprKind::Call(call, [_]) => {
|
||||
if let ExprKind::Path(qpath) = call.kind
|
||||
&& 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);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
|
@ -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::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;
|
||||
}
|
||||
|
||||
|
@ -174,6 +174,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
|
||||
// only `usize` index is legal in rust array index
|
||||
// leave other type to rustc
|
||||
if let Constant::Int(off) = constant
|
||||
&& off <= usize::MAX as u128
|
||||
&& let ty::Uint(utype) = cx.typeck_results().expr_ty(index).kind()
|
||||
&& *utype == ty::UintTy::Usize
|
||||
&& let ty::Array(_, s) = ty.kind()
|
||||
|
@ -385,7 +385,6 @@ impl LateLintPass<'_> for ItemNameRepetitions {
|
||||
assert!(last.is_some());
|
||||
}
|
||||
|
||||
#[expect(clippy::similar_names)]
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
let item_name = item.ident.name.as_str();
|
||||
let item_camel = to_camel_case(item_name);
|
||||
|
@ -1,5 +1,4 @@
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::get_parent_node;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
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();
|
||||
if matches!(name, "iter" | "iter_mut")
|
||||
&& !matches!(
|
||||
get_parent_node(cx.tcx, item.hir_id()),
|
||||
Some(Node::Item(Item { kind: ItemKind::Impl(i), .. })) if i.of_trait.is_some()
|
||||
cx.tcx.parent_hir_node(item.hir_id()),
|
||||
Node::Item(Item { kind: ItemKind::Impl(i), .. }) if i.of_trait.is_some()
|
||||
)
|
||||
{
|
||||
if let ImplItemKind::Fn(fn_sig, _) = &item.kind {
|
||||
|
@ -14,7 +14,7 @@
|
||||
clippy::missing_docs_in_private_items,
|
||||
clippy::must_use_candidate,
|
||||
rustc::diagnostic_outside_of_impl,
|
||||
rustc::untranslatable_diagnostic,
|
||||
rustc::untranslatable_diagnostic
|
||||
)]
|
||||
#![warn(trivial_casts, trivial_numeric_casts)]
|
||||
// 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 module_style;
|
||||
mod multi_assignments;
|
||||
mod multiple_bound_locations;
|
||||
mod multiple_unsafe_ops_per_block;
|
||||
mod mut_key;
|
||||
mod mut_mut;
|
||||
@ -1067,7 +1068,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(single_call_fn::SingleCallFn {
|
||||
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 || {
|
||||
@ -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(|_| 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`
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,18 @@
|
||||
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::{Expr, ExprKind, FnRetTy, FnSig, Node};
|
||||
use rustc_ast::Label;
|
||||
use rustc_errors::Applicability;
|
||||
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;
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &Expr<'_>,
|
||||
expr: &Expr<'tcx>,
|
||||
loop_block: &'tcx hir::Block<'_>,
|
||||
label: Option<Label>,
|
||||
) {
|
||||
@ -34,6 +35,10 @@ pub(super) fn check<'tcx>(
|
||||
return;
|
||||
}
|
||||
|
||||
if in_external_macro(cx.sess(), expr.span) || is_from_proc_macro(cx, expr) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut loop_visitor = LoopVisitor {
|
||||
cx,
|
||||
label,
|
||||
|
@ -74,7 +74,7 @@ enum CharRange {
|
||||
LowerChar,
|
||||
/// 'A'..='Z' | b'A'..=b'Z'
|
||||
UpperChar,
|
||||
/// AsciiLower | AsciiUpper
|
||||
/// `AsciiLower` | `AsciiUpper`
|
||||
FullChar,
|
||||
/// '0..=9'
|
||||
Digit,
|
||||
|
@ -64,38 +64,50 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
|
||||
let min_index = usize::min(lindex, rindex);
|
||||
let max_index = usize::max(lindex, rindex);
|
||||
|
||||
let mut local_map: HirIdMap<HirId> = HirIdMap::default();
|
||||
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
|
||||
if let Some(a_id) = path_to_local(a)
|
||||
&& let Some(b_id) = path_to_local(b)
|
||||
&& let entry = match local_map.entry(a_id) {
|
||||
HirIdMapEntry::Vacant(entry) => entry,
|
||||
// check if using the same bindings as before
|
||||
HirIdMapEntry::Occupied(entry) => return *entry.get() == b_id,
|
||||
}
|
||||
let check_eq_with_pat = |expr_a: &Expr<'_>, expr_b: &Expr<'_>| {
|
||||
let mut local_map: HirIdMap<HirId> = HirIdMap::default();
|
||||
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
|
||||
if let Some(a_id) = path_to_local(a)
|
||||
&& let Some(b_id) = path_to_local(b)
|
||||
&& let entry = match local_map.entry(a_id) {
|
||||
HirIdMapEntry::Vacant(entry) => entry,
|
||||
// check if using the same bindings as before
|
||||
HirIdMapEntry::Occupied(entry) => return *entry.get() == b_id,
|
||||
}
|
||||
// the names technically don't have to match; this makes the lint more conservative
|
||||
&& cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id)
|
||||
&& cx.typeck_results().expr_ty(a) == cx.typeck_results().expr_ty(b)
|
||||
&& pat_contains_local(lhs.pat, a_id)
|
||||
&& pat_contains_local(rhs.pat, b_id)
|
||||
{
|
||||
entry.insert(b_id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
// Arms with a guard are ignored, those can’t 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)
|
||||
&& lhs.guard.is_none()
|
||||
&& rhs.guard.is_none()
|
||||
&& SpanlessEq::new(cx)
|
||||
.expr_fallback(eq_fallback)
|
||||
.eq_expr(lhs.body, rhs.body)
|
||||
&& cx.typeck_results().expr_ty(a) == cx.typeck_results().expr_ty(b)
|
||||
&& pat_contains_local(lhs.pat, a_id)
|
||||
&& pat_contains_local(rhs.pat, b_id)
|
||||
{
|
||||
entry.insert(b_id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
SpanlessEq::new(cx)
|
||||
.expr_fallback(eq_fallback)
|
||||
.eq_expr(expr_a, expr_b)
|
||||
// these checks could be removed to allow unused bindings
|
||||
&& bindings_eq(lhs.pat, local_map.keys().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 can’t 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();
|
||||
|
@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
|
||||
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,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
@ -123,37 +123,35 @@ 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
|
||||
/// 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 {
|
||||
if let Some(p_node) = get_parent_node(cx.tcx, p_expr.hir_id) {
|
||||
match p_node {
|
||||
// Compare match_expr ty with local in `let local = match match_expr {..}`
|
||||
Node::Local(local) => {
|
||||
let results = cx.typeck_results();
|
||||
return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr));
|
||||
},
|
||||
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
|
||||
Node::Item(item) => {
|
||||
if let ItemKind::Fn(..) = item.kind {
|
||||
let output = cx
|
||||
.tcx
|
||||
.fn_sig(item.owner_id)
|
||||
.instantiate_identity()
|
||||
.output()
|
||||
.skip_binder();
|
||||
return same_type_and_consts(output, cx.typeck_results().expr_ty(expr));
|
||||
}
|
||||
},
|
||||
// check the parent expr for this whole block `{ match match_expr {..} }`
|
||||
Node::Block(block) => {
|
||||
if let Some(block_parent_expr) = get_parent_expr_for_hir(cx, block.hir_id) {
|
||||
return expr_ty_matches_p_ty(cx, expr, block_parent_expr);
|
||||
}
|
||||
},
|
||||
// recursively call on `if xxx {..}` etc.
|
||||
Node::Expr(p_expr) => {
|
||||
return expr_ty_matches_p_ty(cx, expr, p_expr);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
match cx.tcx.parent_hir_node(p_expr.hir_id) {
|
||||
// Compare match_expr ty with local in `let local = match match_expr {..}`
|
||||
Node::Local(local) => {
|
||||
let results = cx.typeck_results();
|
||||
return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr));
|
||||
},
|
||||
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
|
||||
Node::Item(item) => {
|
||||
if let ItemKind::Fn(..) = item.kind {
|
||||
let output = cx
|
||||
.tcx
|
||||
.fn_sig(item.owner_id)
|
||||
.instantiate_identity()
|
||||
.output()
|
||||
.skip_binder();
|
||||
return same_type_and_consts(output, cx.typeck_results().expr_ty(expr));
|
||||
}
|
||||
},
|
||||
// check the parent expr for this whole block `{ match match_expr {..} }`
|
||||
Node::Block(block) => {
|
||||
if let Some(block_parent_expr) = get_parent_expr_for_hir(cx, block.hir_id) {
|
||||
return expr_ty_matches_p_ty(cx, expr, block_parent_expr);
|
||||
}
|
||||
},
|
||||
// recursively call on `if xxx {..}` etc.
|
||||
Node::Expr(p_expr) => {
|
||||
return expr_ty_matches_p_ty(cx, expr, p_expr);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
false
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::path_to_local;
|
||||
use clippy_utils::source::snippet;
|
||||
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_errors::Applicability;
|
||||
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_span::symbol::Ident;
|
||||
use rustc_span::{Span, Symbol};
|
||||
@ -123,7 +123,7 @@ fn check_method_calls<'tcx>(
|
||||
// `s if s.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()
|
||||
} else if slice_like {
|
||||
"[]".into()
|
||||
@ -269,7 +269,11 @@ fn expr_can_be_pat(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
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,
|
||||
_ => false,
|
||||
} {
|
||||
|
@ -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 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.
|
||||
|
@ -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::sugg::Sugg;
|
||||
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_hir::LangItem::OptionNone;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
@ -232,7 +234,7 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace {
|
||||
// Check that second argument is `Option::None`
|
||||
if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
|
||||
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_uninit(cx, src, dest, expr.span);
|
||||
|
@ -38,6 +38,7 @@ pub(super) fn check<'tcx>(
|
||||
&& 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_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()
|
||||
&& (recv_ty.is_str() || is_type_lang_item(cx, recv_ty, LangItem::String))
|
||||
{
|
||||
|
@ -1,5 +1,4 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::get_parent_node;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use rustc_errors::Applicability;
|
||||
@ -48,8 +47,8 @@ pub(super) fn check(
|
||||
}
|
||||
|
||||
if is_copy(cx, ty) {
|
||||
let parent_is_suffix_expr = match get_parent_node(cx.tcx, expr.hir_id) {
|
||||
Some(Node::Expr(parent)) => match parent.kind {
|
||||
let parent_is_suffix_expr = match cx.tcx.parent_hir_node(expr.hir_id) {
|
||||
Node::Expr(parent) => match parent.kind {
|
||||
// &*x is a nop, &x.clone() is not
|
||||
ExprKind::AddrOf(..) => return,
|
||||
// (*x).func() is useless, x.clone().func() can work in case func borrows self
|
||||
@ -70,7 +69,7 @@ pub(super) fn check(
|
||||
_ => false,
|
||||
},
|
||||
// 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;
|
||||
},
|
||||
_ => false,
|
||||
|
@ -75,7 +75,7 @@ enum OffendingFilterExpr<'tcx> {
|
||||
},
|
||||
/// `.filter(|enum| matches!(enum, Enum::A(_)))`
|
||||
Matches {
|
||||
/// The DefId of the variant being matched
|
||||
/// The `DefId` of the variant being matched
|
||||
variant_def_id: hir::def_id::DefId,
|
||||
},
|
||||
}
|
||||
|
@ -110,6 +110,7 @@ mod unit_hash;
|
||||
mod unnecessary_fallible_conversions;
|
||||
mod unnecessary_filter_map;
|
||||
mod unnecessary_fold;
|
||||
mod unnecessary_get_then_check;
|
||||
mod unnecessary_iter_cloned;
|
||||
mod unnecessary_join;
|
||||
mod unnecessary_lazy_eval;
|
||||
@ -3419,11 +3420,12 @@ declare_clippy_lint! {
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// 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
|
||||
/// always fail because the string has a trailing newline in it.
|
||||
/// into a string, then later attempting to use that string for an operation that will never
|
||||
/// work for strings with a trailing newline character in it (e.g. parsing into a `i32`).
|
||||
///
|
||||
/// ### 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
|
||||
/// ```rust,ignore
|
||||
@ -4011,6 +4013,35 @@ declare_clippy_lint! {
|
||||
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 {
|
||||
avoid_breaking_exported_api: bool,
|
||||
msrv: Msrv,
|
||||
@ -4171,6 +4202,7 @@ impl_lint_pass!(Methods => [
|
||||
OPTION_AS_REF_CLONED,
|
||||
UNNECESSARY_RESULT_MAP_OR_ELSE,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
UNNECESSARY_GET_THEN_CHECK,
|
||||
]);
|
||||
|
||||
/// 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_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_some", []) => check_is_some_is_none(cx, expr, recv, true),
|
||||
("is_none", []) => check_is_some_is_none(cx, expr, recv, call_span, false),
|
||||
("is_some", []) => check_is_some_is_none(cx, expr, recv, call_span, true),
|
||||
("iter" | "iter_mut" | "into_iter", []) => {
|
||||
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) {
|
||||
if let Some((name @ ("find" | "position" | "rposition"), f_recv, [arg], span, _)) = method_call(recv) {
|
||||
search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span);
|
||||
fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, call_span: Span, is_some: bool) {
|
||||
match method_call(recv) {
|
||||
Some((name @ ("find" | "position" | "rposition"), f_recv, [arg], span, _)) => {
|
||||
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);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,8 +4,8 @@ use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, make_normalized_projection, make_projection};
|
||||
use clippy_utils::{
|
||||
can_move_expr_to_closure, fn_def_id, get_enclosing_block, get_parent_node, higher, is_trait_method, path_to_local,
|
||||
path_to_local_id, CaptureKind,
|
||||
can_move_expr_to_closure, fn_def_id, get_enclosing_block, higher, is_trait_method, path_to_local, path_to_local_id,
|
||||
CaptureKind,
|
||||
};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_errors::{Applicability, MultiSpan};
|
||||
@ -28,104 +28,102 @@ pub(super) fn check<'tcx>(
|
||||
iter_expr: &'tcx Expr<'tcx>,
|
||||
call_span: Span,
|
||||
) {
|
||||
if let Some(parent) = get_parent_node(cx.tcx, collect_expr.hir_id) {
|
||||
match parent {
|
||||
Node::Expr(parent) => {
|
||||
check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr);
|
||||
match cx.tcx.parent_hir_node(collect_expr.hir_id) {
|
||||
Node::Expr(parent) => {
|
||||
check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr);
|
||||
|
||||
if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let name = name.ident.as_str();
|
||||
let collect_ty = cx.typeck_results().expr_ty(collect_expr);
|
||||
if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let name = name.ident.as_str();
|
||||
let collect_ty = cx.typeck_results().expr_ty(collect_expr);
|
||||
|
||||
let sugg: String = match name {
|
||||
"len" => {
|
||||
if let Some(adt) = collect_ty.ty_adt_def()
|
||||
&& matches!(
|
||||
cx.tcx.get_diagnostic_name(adt.did()),
|
||||
Some(sym::Vec | sym::VecDeque | sym::LinkedList | sym::BinaryHeap)
|
||||
)
|
||||
{
|
||||
"count()".into()
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
"is_empty"
|
||||
if is_is_empty_sig(cx, parent.hir_id)
|
||||
&& iterates_same_ty(cx, cx.typeck_results().expr_ty(iter_expr), collect_ty) =>
|
||||
let sugg: String = match name {
|
||||
"len" => {
|
||||
if let Some(adt) = collect_ty.ty_adt_def()
|
||||
&& matches!(
|
||||
cx.tcx.get_diagnostic_name(adt.did()),
|
||||
Some(sym::Vec | sym::VecDeque | sym::LinkedList | sym::BinaryHeap)
|
||||
)
|
||||
{
|
||||
"next().is_none()".into()
|
||||
},
|
||||
"contains" => {
|
||||
if is_contains_sig(cx, parent.hir_id, iter_expr)
|
||||
&& let Some(arg) = args.first()
|
||||
{
|
||||
let (span, prefix) = if let ExprKind::AddrOf(_, _, arg) = arg.kind {
|
||||
(arg.span, "")
|
||||
} else {
|
||||
(arg.span, "*")
|
||||
};
|
||||
let snip = snippet_with_applicability(cx, span, "??", &mut app);
|
||||
format!("any(|x| x == {prefix}{snip})")
|
||||
"count()".into()
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
"is_empty"
|
||||
if is_is_empty_sig(cx, parent.hir_id)
|
||||
&& iterates_same_ty(cx, cx.typeck_results().expr_ty(iter_expr), collect_ty) =>
|
||||
{
|
||||
"next().is_none()".into()
|
||||
},
|
||||
"contains" => {
|
||||
if is_contains_sig(cx, parent.hir_id, iter_expr)
|
||||
&& let Some(arg) = args.first()
|
||||
{
|
||||
let (span, prefix) = if let ExprKind::AddrOf(_, _, arg) = arg.kind {
|
||||
(arg.span, "")
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
(arg.span, "*")
|
||||
};
|
||||
let snip = snippet_with_applicability(cx, span, "??", &mut app);
|
||||
format!("any(|x| x == {prefix}{snip})")
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_COLLECT,
|
||||
call_span.with_hi(parent.span.hi()),
|
||||
NEEDLESS_COLLECT_MSG,
|
||||
"replace with",
|
||||
sugg,
|
||||
app,
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_COLLECT,
|
||||
call_span.with_hi(parent.span.hi()),
|
||||
NEEDLESS_COLLECT_MSG,
|
||||
"replace with",
|
||||
sugg,
|
||||
app,
|
||||
);
|
||||
}
|
||||
},
|
||||
Node::Local(l) => {
|
||||
if let PatKind::Binding(BindingAnnotation::NONE | BindingAnnotation::MUT, id, _, None) = l.pat.kind
|
||||
&& let ty = cx.typeck_results().expr_ty(collect_expr)
|
||||
&& [sym::Vec, sym::VecDeque, sym::BinaryHeap, sym::LinkedList]
|
||||
.into_iter()
|
||||
.any(|item| is_type_diagnostic_item(cx, ty, item))
|
||||
&& let iter_ty = cx.typeck_results().expr_ty(iter_expr)
|
||||
&& let Some(block) = get_enclosing_block(cx, l.hir_id)
|
||||
&& let Some(iter_calls) = detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty))
|
||||
&& let [iter_call] = &*iter_calls
|
||||
{
|
||||
let mut used_count_visitor = UsedCountVisitor { cx, id, count: 0 };
|
||||
walk_block(&mut used_count_visitor, block);
|
||||
if used_count_visitor.count > 1 {
|
||||
return;
|
||||
}
|
||||
},
|
||||
Node::Local(l) => {
|
||||
if let PatKind::Binding(BindingAnnotation::NONE | BindingAnnotation::MUT, id, _, None) = l.pat.kind
|
||||
&& let ty = cx.typeck_results().expr_ty(collect_expr)
|
||||
&& [sym::Vec, sym::VecDeque, sym::BinaryHeap, sym::LinkedList]
|
||||
.into_iter()
|
||||
.any(|item| is_type_diagnostic_item(cx, ty, item))
|
||||
&& let iter_ty = cx.typeck_results().expr_ty(iter_expr)
|
||||
&& let Some(block) = get_enclosing_block(cx, l.hir_id)
|
||||
&& let Some(iter_calls) = detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty))
|
||||
&& let [iter_call] = &*iter_calls
|
||||
{
|
||||
let mut used_count_visitor = UsedCountVisitor { cx, id, count: 0 };
|
||||
walk_block(&mut used_count_visitor, block);
|
||||
if used_count_visitor.count > 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Suggest replacing iter_call with iter_replacement, and removing stmt
|
||||
let mut span = MultiSpan::from_span(name_span);
|
||||
span.push_span_label(iter_call.span, "the iterator could be used here instead");
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
super::NEEDLESS_COLLECT,
|
||||
collect_expr.hir_id,
|
||||
span,
|
||||
NEEDLESS_COLLECT_MSG,
|
||||
|diag| {
|
||||
let iter_replacement =
|
||||
format!("{}{}", Sugg::hir(cx, iter_expr, ".."), iter_call.get_iter_method(cx));
|
||||
diag.multipart_suggestion(
|
||||
iter_call.get_suggestion_text(),
|
||||
vec![(l.span, String::new()), (iter_call.span, iter_replacement)],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
// Suggest replacing iter_call with iter_replacement, and removing stmt
|
||||
let mut span = MultiSpan::from_span(name_span);
|
||||
span.push_span_label(iter_call.span, "the iterator could be used here instead");
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
super::NEEDLESS_COLLECT,
|
||||
collect_expr.hir_id,
|
||||
span,
|
||||
NEEDLESS_COLLECT_MSG,
|
||||
|diag| {
|
||||
let iter_replacement =
|
||||
format!("{}{}", Sugg::hir(cx, iter_expr, ".."), iter_call.get_iter_method(cx));
|
||||
diag.multipart_suggestion(
|
||||
iter_call.get_suggestion_text(),
|
||||
vec![(l.span, String::new()), (iter_call.span, iter_replacement)],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,15 +5,26 @@ use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::visitors::for_each_local_use_after_expr;
|
||||
use clippy_utils::{get_parent_expr, match_def_path};
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{Expr, ExprKind, QPath};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::sym;
|
||||
|
||||
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?
|
||||
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
|
||||
@ -27,30 +38,66 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<
|
||||
&& let Res::Local(local_id) = path.res
|
||||
{
|
||||
// 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
|
||||
// parsed into a type that will always fail if it has a trailing newline.
|
||||
// now let's check if the first use of the string passed to `::read_line()`
|
||||
// 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| {
|
||||
if let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let ExprKind::MethodCall(segment, .., span) = parent.kind
|
||||
&& segment.ident.name == sym!(parse)
|
||||
&& let parse_result_ty = cx.typeck_results().expr_ty(parent)
|
||||
&& is_type_diagnostic_item(cx, parse_result_ty, sym::Result)
|
||||
&& let ty::Adt(_, args) = parse_result_ty.kind()
|
||||
&& let Some(ok_ty) = args[0].as_type()
|
||||
&& parse_fails_on_trailing_newline(ok_ty)
|
||||
{
|
||||
let local_snippet = snippet(cx, expr.span, "<expr>");
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
READ_LINE_WITHOUT_TRIM,
|
||||
span,
|
||||
"calling `.parse()` without trimming the trailing newline character",
|
||||
|diag| {
|
||||
if let Some(parent) = get_parent_expr(cx, expr) {
|
||||
let data = if let ExprKind::MethodCall(segment, recv, args, span) = parent.kind {
|
||||
if segment.ident.name == sym!(parse)
|
||||
&& let parse_result_ty = cx.typeck_results().expr_ty(parent)
|
||||
&& is_type_diagnostic_item(cx, parse_result_ty, sym::Result)
|
||||
&& let ty::Adt(_, substs) = parse_result_ty.kind()
|
||||
&& let Some(ok_ty) = substs[0].as_type()
|
||||
&& parse_fails_on_trailing_newline(ok_ty)
|
||||
{
|
||||
// Called `s.parse::<T>()` where `T` is a type we know for certain will fail
|
||||
// if the input has a trailing newline
|
||||
Some((
|
||||
span,
|
||||
"calling `.parse()` on a string without trimming the trailing newline character",
|
||||
"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(
|
||||
call.span,
|
||||
"call to `.read_line()` here, \
|
||||
which leaves a trailing newline character in the buffer, \
|
||||
which in turn will cause `.parse()` to fail",
|
||||
format!(
|
||||
"call to `.read_line()` here, \
|
||||
which leaves a trailing newline character in the buffer, \
|
||||
which in turn will cause the {operation} to always fail"
|
||||
),
|
||||
);
|
||||
|
||||
diag.span_suggestion(
|
||||
@ -59,8 +106,8 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<
|
||||
format!("{local_snippet}.trim_end()"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// only consider the first use to prevent this scenario:
|
||||
|
@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_hir::{Expr, ExprKind, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::print::with_forced_trimmed_paths;
|
||||
@ -10,17 +10,71 @@ use rustc_span::{sym, Span};
|
||||
|
||||
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
|
||||
/// call
|
||||
#[derive(Copy, Clone)]
|
||||
#[expect(clippy::enum_variant_names)]
|
||||
enum FunctionKind {
|
||||
/// `T::try_from(U)`
|
||||
TryFromFunction,
|
||||
TryFromFunction(Option<SpansKind>),
|
||||
/// `t.try_into()`
|
||||
TryIntoMethod,
|
||||
/// `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>(
|
||||
@ -35,8 +89,8 @@ fn check<'tcx>(
|
||||
&& self_ty != other_ty
|
||||
&& let Some(self_ty) = self_ty.as_type()
|
||||
&& let Some(from_into_trait) = cx.tcx.get_diagnostic_item(match kind {
|
||||
FunctionKind::TryFromFunction => sym::From,
|
||||
FunctionKind::TryIntoMethod | FunctionKind::TryIntoFunction => sym::Into,
|
||||
FunctionKind::TryFromFunction(_) => sym::From,
|
||||
FunctionKind::TryIntoMethod | FunctionKind::TryIntoFunction(_) => sym::Into,
|
||||
})
|
||||
// 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
|
||||
@ -45,49 +99,37 @@ fn check<'tcx>(
|
||||
&& implements_trait(cx, self_ty, from_into_trait, &[other_ty])
|
||||
&& let Some(other_ty) = other_ty.as_type()
|
||||
{
|
||||
// Extend the span to include the unwrap/expect call:
|
||||
// `foo.try_into().expect("..")`
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
//
|
||||
// `try_into().unwrap()` specifically can be trivially replaced with just `into()`,
|
||||
// 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
|
||||
{
|
||||
Some(span)
|
||||
// include `.` before `unwrap`/`expect`
|
||||
Some(span.with_lo(expr.span.hi()))
|
||||
} 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:
|
||||
// `foo.try_into().expect("..")`
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
//
|
||||
// `try_into().unwrap()` specifically can be trivially replaced with just `into()`,
|
||||
// so that can be machine-applicable
|
||||
|
||||
(
|
||||
self_ty,
|
||||
other_ty,
|
||||
"into()",
|
||||
primary_span.with_hi(unwrap_span.hi()),
|
||||
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),
|
||||
// If there is an unwrap/expect call, extend the span to include the call
|
||||
let span = if let Some(unwrap_call) = parent_unwrap_call {
|
||||
primary_span.with_hi(unwrap_call.hi())
|
||||
} else {
|
||||
primary_span
|
||||
};
|
||||
|
||||
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(
|
||||
cx,
|
||||
UNNECESSARY_FALLIBLE_CONVERSIONS,
|
||||
@ -97,7 +139,7 @@ fn check<'tcx>(
|
||||
with_forced_trimmed_paths!({
|
||||
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(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(
|
||||
cx,
|
||||
expr,
|
||||
cx.typeck_results().node_args(callee.hir_id),
|
||||
match cx.tcx.get_diagnostic_name(trait_def_id) {
|
||||
Some(sym::TryFrom) => FunctionKind::TryFromFunction,
|
||||
Some(sym::TryInto) => FunctionKind::TryIntoFunction,
|
||||
Some(sym::TryFrom) => FunctionKind::TryFromFunction(qpath_spans),
|
||||
Some(sym::TryInto) => FunctionKind::TryIntoFunction(qpath_spans),
|
||||
_ => return,
|
||||
},
|
||||
callee.span,
|
||||
|
85
clippy_lints/src/methods/unnecessary_get_then_check.rs
Normal file
85
clippy_lints/src/methods/unnecessary_get_then_check.rs
Normal 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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,9 @@ use super::unnecessary_iter_cloned::{self, is_into_iter};
|
||||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
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::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty};
|
||||
use rustc_errors::Applicability;
|
||||
@ -16,7 +18,8 @@ use rustc_lint::LateContext;
|
||||
use rustc_middle::mir::Mutability;
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
|
||||
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_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);
|
||||
}
|
||||
} else {
|
||||
check_borrow_predicate(cx, expr);
|
||||
}
|
||||
}
|
||||
|
||||
@ -590,3 +595,92 @@ fn is_to_string_on_string_like<'a>(
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::is_from_proc_macro;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::intravisit::{walk_item, Visitor};
|
||||
use rustc_hir::{GenericParamKind, HirId, Item, ItemKind, ItemLocalId, Node, Pat, PatKind};
|
||||
use rustc_hir::intravisit::{walk_item, walk_trait_item, Visitor};
|
||||
use rustc_hir::{GenericParamKind, HirId, Item, ItemKind, ItemLocalId, Node, Pat, PatKind, TraitItem, UsePath};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::impl_lint_pass;
|
||||
@ -53,7 +53,7 @@ impl MinIdentChars {
|
||||
&& str.len() <= self.min_ident_chars_threshold as usize
|
||||
&& !str.starts_with('_')
|
||||
&& !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);
|
||||
}
|
||||
|
||||
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`. :/
|
||||
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
|
||||
if let PatKind::Binding(_, _, ident, ..) = pat.kind
|
||||
@ -105,11 +113,26 @@ impl Visitor<'_> for IdentVisitor<'_, '_> {
|
||||
|
||||
let str = ident.as_str();
|
||||
if conf.is_ident_too_short(cx, str, ident.span) {
|
||||
if let Node::Item(item) = node
|
||||
&& let ItemKind::Use(..) = item.kind
|
||||
// Check whether the node is part of a `use` statement. We don't want to emit a warning if the user
|
||||
// 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;
|
||||
}
|
||||
|
||||
// `struct Awa<T>(T)`
|
||||
// ^
|
||||
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);
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
84
clippy_lints/src/multiple_bound_locations.rs
Normal file
84
clippy_lints/src/multiple_bound_locations.rs
Normal 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",
|
||||
);
|
||||
}
|
@ -6,8 +6,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{
|
||||
get_parent_node, higher, is_else_clause, is_expn_of, peel_blocks, peel_blocks_with_stmt, span_extract_comment,
|
||||
SpanlessEq,
|
||||
higher, is_else_clause, is_expn_of, peel_blocks, peel_blocks_with_stmt, span_extract_comment, SpanlessEq,
|
||||
};
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
@ -138,8 +137,8 @@ fn condition_needs_parentheses(e: &Expr<'_>) -> bool {
|
||||
|
||||
fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool {
|
||||
matches!(
|
||||
get_parent_node(cx.tcx, id),
|
||||
Some(Node::Stmt(..) | Node::Block(Block { stmts: &[], .. }))
|
||||
cx.tcx.parent_hir_node(id),
|
||||
Node::Stmt(..) | Node::Block(Block { stmts: &[], .. })
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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::source::snippet;
|
||||
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_errors::Applicability;
|
||||
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>) {
|
||||
// #11182; do not lint if mutability is required elsewhere
|
||||
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 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
|
||||
&& call.hir_id == expr.hir_id
|
||||
{
|
||||
|
@ -75,10 +75,6 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
|
||||
if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind {
|
||||
let name = impl_item.ident.name;
|
||||
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 {
|
||||
// can't be implemented for unsafe new
|
||||
return;
|
||||
|
@ -1,12 +1,12 @@
|
||||
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
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_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{
|
||||
is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, HirId, HirIdMap, ItemKind, Node, PatKind, Stmt,
|
||||
StmtKind, UnsafeSource,
|
||||
is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, HirId, HirIdMap, ItemKind, LocalSource, Node, PatKind,
|
||||
Stmt, StmtKind, UnsafeSource,
|
||||
};
|
||||
use rustc_infer::infer::TyCtxtInferExt as _;
|
||||
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
|
||||
/// 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
|
||||
/// ```rust,ignore
|
||||
/// let _i_serve_no_purpose = 1;
|
||||
@ -144,7 +140,7 @@ impl NoEffect {
|
||||
for parent in cx.tcx.hir().parent_iter(stmt.hir_id) {
|
||||
if let Node::Item(item) = parent.1
|
||||
&& 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
|
||||
&& final_stmt.hir_id == stmt.hir_id
|
||||
{
|
||||
@ -180,6 +176,7 @@ impl NoEffect {
|
||||
}
|
||||
} else if let StmtKind::Local(local) = stmt.kind {
|
||||
if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id)
|
||||
&& !matches!(local.source, LocalSource::AsyncFn)
|
||||
&& let Some(init) = local.init
|
||||
&& local.els.is_none()
|
||||
&& !local.pat.span.from_expansion()
|
||||
|
@ -1,6 +1,6 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
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_hir::def_id::LocalDefId;
|
||||
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 {
|
||||
#[expect(clippy::too_many_lines)]
|
||||
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;
|
||||
};
|
||||
let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) else {
|
||||
|
@ -119,7 +119,6 @@ impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> {
|
||||
|
||||
// this list contains lists of names that are allowed to be similar
|
||||
// the assumption is that no name is ever contained in multiple lists.
|
||||
#[rustfmt::skip]
|
||||
const ALLOWED_TO_BE_SIMILAR: &[&[&str]] = &[
|
||||
&["parsed", "parser"],
|
||||
&["lhs", "rhs"],
|
||||
@ -132,6 +131,14 @@ const ALLOWED_TO_BE_SIMILAR: &[&[&str]] = &[
|
||||
&["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>);
|
||||
|
||||
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) {
|
||||
let interned_name = ident.name.as_str();
|
||||
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) {
|
||||
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 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
|
||||
{
|
||||
continue;
|
||||
}
|
||||
} 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)
|
||||
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;
|
||||
}
|
||||
}
|
||||
},
|
||||
let existing_str = existing_name.interned.as_str();
|
||||
|
||||
// The first char being different is usually enough to set identifiers apart, as long
|
||||
// as the characters aren't too similar.
|
||||
if !chars_are_similar(
|
||||
interned_name.chars().next().expect("len >= 1"),
|
||||
existing_str.chars().next().expect("len >= 1"),
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dissimilar = match existing_name.len.cmp(&count) {
|
||||
Ordering::Greater => existing_name.len - count != 1 || levenstein_not_1(interned_name, existing_str),
|
||||
Ordering::Less => count - existing_name.len != 1 || levenstein_not_1(existing_str, interned_name),
|
||||
Ordering::Equal => Self::equal_length_strs_not_similar(interned_name, existing_str),
|
||||
};
|
||||
|
||||
if dissimilar {
|
||||
continue;
|
||||
}
|
||||
|
||||
span_lint_and_then(
|
||||
self.0.cx,
|
||||
SIMILAR_NAMES,
|
||||
@ -302,6 +265,57 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
|
||||
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> {
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 rustc_data_structures::fx::FxHashMap;
|
||||
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.
|
||||
// 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) {
|
||||
Some(Node::Item(i)) => (i.owner_id.to_def_id(), FnKind::Fn, 0),
|
||||
Some(Node::TraitItem(&TraitItem {
|
||||
let (fn_id, fn_kind, skip_params) = match cx.tcx.parent_hir_node(body.value.hir_id) {
|
||||
Node::Item(i) => (i.owner_id.to_def_id(), FnKind::Fn, 0),
|
||||
Node::TraitItem(&TraitItem {
|
||||
kind: TraitItemKind::Fn(ref sig, _),
|
||||
owner_id,
|
||||
..
|
||||
})) => (
|
||||
}) => (
|
||||
owner_id.to_def_id(),
|
||||
FnKind::TraitFn,
|
||||
usize::from(sig.decl.implicit_self.has_implicit_self()),
|
||||
),
|
||||
Some(Node::ImplItem(&ImplItem {
|
||||
Node::ImplItem(&ImplItem {
|
||||
kind: ImplItemKind::Fn(ref sig, _),
|
||||
owner_id,
|
||||
..
|
||||
})) => {
|
||||
}) => {
|
||||
#[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
|
||||
.tcx
|
||||
.impl_trait_ref(item.owner_id)
|
||||
|
@ -8,7 +8,6 @@ use rustc_span::Span;
|
||||
|
||||
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) {
|
||||
let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) {
|
||||
(ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => {
|
||||
|
@ -11,7 +11,7 @@ use rustc_middle::ty::{self, Ty};
|
||||
|
||||
use super::OP_REF;
|
||||
|
||||
#[expect(clippy::similar_names, clippy::too_many_lines)]
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub(crate) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &'tcx Expr<'_>,
|
||||
|
@ -1,6 +1,6 @@
|
||||
use clippy_config::types::PubUnderscoreFieldsBehaviour;
|
||||
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 rustc_hir::{FieldDef, Item, ItemKind, LangItem};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@ -69,13 +69,15 @@ impl<'tcx> LateLintPass<'tcx> for PubUnderscoreFields {
|
||||
// We ignore fields that are `PhantomData`.
|
||||
&& !is_path_lang_item(cx, field.ty, LangItem::PhantomData)
|
||||
{
|
||||
span_lint_and_help(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
PUB_UNDERSCORE_FIELDS,
|
||||
field.hir_id,
|
||||
field.vis_span.to(field.ident.span),
|
||||
"field marked as public but also inferred as unused because it's prefixed with `_`",
|
||||
None,
|
||||
"consider removing the underscore, or making the field private",
|
||||
|diag| {
|
||||
diag.help("consider removing the underscore, or making the field private");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{
|
||||
eq_expr_value, get_parent_node, higher, in_constant, is_else_clause, is_lint_allowed, is_path_lang_item,
|
||||
is_res_lang_ctor, pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks,
|
||||
peel_blocks_with_stmt, span_contains_comment,
|
||||
eq_expr_value, higher, in_constant, is_else_clause, is_lint_allowed, is_path_lang_item, is_res_lang_ctor,
|
||||
pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
|
||||
span_contains_comment,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
@ -74,12 +74,12 @@ impl QuestionMark {
|
||||
enum IfBlockType<'hir> {
|
||||
/// 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>),
|
||||
/// 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),
|
||||
/// if_else (d)
|
||||
/// Contains: `let_pat_qpath (Xxx), let_pat_type, let_pat_sym (a), let_expr (b), if_then (c),
|
||||
/// if_else (d)`
|
||||
IfLet(
|
||||
Res,
|
||||
Ty<'hir>,
|
||||
@ -289,7 +289,7 @@ impl QuestionMark {
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
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!(
|
||||
"{receiver_str}{}?{}",
|
||||
if by_ref == ByRef::Yes { ".as_ref()" } else { "" },
|
||||
|
@ -200,11 +200,11 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
|
||||
hint = hint.asyncify();
|
||||
}
|
||||
|
||||
let is_in_fn_call_arg =
|
||||
clippy_utils::get_parent_node(cx.tcx, expr.hir_id).is_some_and(|x| match x {
|
||||
Node::Expr(expr) => matches!(expr.kind, hir::ExprKind::Call(_, _)),
|
||||
_ => false,
|
||||
});
|
||||
let is_in_fn_call_arg = if let Node::Expr(expr) = cx.tcx.parent_hir_node(expr.hir_id) {
|
||||
matches!(expr.kind, hir::ExprKind::Call(_, _))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// avoid clippy::double_parens
|
||||
if !is_in_fn_call_arg {
|
||||
|
@ -47,7 +47,6 @@ struct ExistingName {
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
|
||||
let mut map = FxHashMap::<Res, ExistingName>::default();
|
||||
|
||||
@ -75,24 +74,23 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
||||
|
||||
match of_trait {
|
||||
Some(trait_ref) => {
|
||||
let mut methods_in_trait: BTreeSet<Symbol> =
|
||||
if let Node::TraitRef(TraitRef { path, .. }) =
|
||||
cx.tcx.hir_node(trait_ref.hir_ref_id)
|
||||
&& let Res::Def(DefKind::Trait, did) = path.res
|
||||
{
|
||||
// FIXME: if
|
||||
// `rustc_middle::ty::assoc::AssocItems::items` is public,
|
||||
// we can iterate its keys instead of `in_definition_order`,
|
||||
// which's more efficient
|
||||
cx.tcx
|
||||
.associated_items(did)
|
||||
.in_definition_order()
|
||||
.filter(|assoc_item| matches!(assoc_item.kind, AssocKind::Fn))
|
||||
.map(|assoc_item| assoc_item.name)
|
||||
.collect()
|
||||
} else {
|
||||
BTreeSet::new()
|
||||
};
|
||||
let mut methods_in_trait: BTreeSet<Symbol> = if let Node::TraitRef(TraitRef { path, .. }) =
|
||||
cx.tcx.hir_node(trait_ref.hir_ref_id)
|
||||
&& let Res::Def(DefKind::Trait, did) = path.res
|
||||
{
|
||||
// FIXME: if
|
||||
// `rustc_middle::ty::assoc::AssocItems::items` is public,
|
||||
// we can iterate its keys instead of `in_definition_order`,
|
||||
// which's more efficient
|
||||
cx.tcx
|
||||
.associated_items(did)
|
||||
.in_definition_order()
|
||||
.filter(|assoc_item| matches!(assoc_item.kind, AssocKind::Fn))
|
||||
.map(|assoc_item| assoc_item.name)
|
||||
.collect()
|
||||
} else {
|
||||
BTreeSet::new()
|
||||
};
|
||||
|
||||
let mut check_trait_method = |method_name: Symbol, trait_method_span: Span| {
|
||||
if let Some((impl_span, hir_id)) = existing_name.impl_methods.get(&method_name) {
|
||||
|
@ -1,11 +1,10 @@
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
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::intravisit::{walk_expr, FnKind, Visitor};
|
||||
use rustc_hir::{Body, Expr, ExprKind, FnDecl};
|
||||
use rustc_hir::{Expr, ExprKind, HirId, Node};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::hir::nested_filter::OnlyBodies;
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
@ -54,82 +53,93 @@ declare_clippy_lint! {
|
||||
}
|
||||
impl_lint_pass!(SingleCallFn => [SINGLE_CALL_FN]);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CallState {
|
||||
Once { call_site: Span },
|
||||
Multiple,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SingleCallFn {
|
||||
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 {
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
kind: FnKind<'tcx>,
|
||||
_: &'tcx FnDecl<'_>,
|
||||
body: &'tcx Body<'_>,
|
||||
span: Span,
|
||||
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))
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) {
|
||||
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 DefKind::Fn | DefKind::AssocFn = cx.tcx.def_kind(def_id)
|
||||
&& is_valid_item_kind(cx, def_id)
|
||||
{
|
||||
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>) {
|
||||
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 {
|
||||
let single_call_fn_span = usage.0;
|
||||
if let [caller_span] = *usage.1 {
|
||||
if let CallState::Once { call_site } = *usage
|
||||
&& 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(
|
||||
cx,
|
||||
SINGLE_CALL_FN,
|
||||
cx.tcx.local_def_id_to_hir_id(def_id),
|
||||
single_call_fn_span,
|
||||
fn_hir_id,
|
||||
fn_span,
|
||||
"this function is only used once",
|
||||
|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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
/// `vec = Vec::with_capacity(0)`
|
||||
struct VecAllocation<'tcx> {
|
||||
/// HirId of the variable
|
||||
/// `HirId` of the variable
|
||||
local_id: HirId,
|
||||
|
||||
/// Reference to the expression which allocates the vector
|
||||
|
@ -1,8 +1,8 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::match_libc_symbol;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
|
||||
use clippy_utils::visitors::is_expr_unsafe;
|
||||
use clippy_utils::{get_parent_node, match_libc_symbol};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, LangItem, Node, UnsafeSource};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@ -50,12 +50,12 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings {
|
||||
&& path.ident.name == sym::as_ptr
|
||||
{
|
||||
let ctxt = expr.span.ctxt();
|
||||
let span = match get_parent_node(cx.tcx, expr.hir_id) {
|
||||
Some(Node::Block(&Block {
|
||||
let span = match cx.tcx.parent_hir_node(expr.hir_id) {
|
||||
Node::Block(&Block {
|
||||
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
|
||||
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,
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use rustc_hir::{Impl, Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
@ -53,6 +54,8 @@ impl<'tcx> LateLintPass<'tcx> for ToStringTraitImpl {
|
||||
}) = it.kind
|
||||
&& let Some(trait_did) = trait_ref.trait_def_id()
|
||||
&& 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(
|
||||
cx,
|
||||
|
@ -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 {
|
||||
return false;
|
||||
};
|
||||
let local_parents = locals
|
||||
.iter()
|
||||
.map(|l| cx.tcx.parent_hir_node(*l))
|
||||
.collect::<Vec<_>>();
|
||||
let local_parents = locals.iter().map(|l| cx.tcx.parent_hir_node(*l)).collect::<Vec<_>>();
|
||||
|
||||
local_parents
|
||||
.iter()
|
||||
|
@ -1,9 +1,9 @@
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::is_lint_allowed;
|
||||
use clippy_utils::source::walk_span_to_context;
|
||||
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
|
||||
use clippy_utils::{get_parent_node, is_lint_allowed};
|
||||
use hir::HirId;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_hir as hir;
|
||||
@ -340,47 +340,43 @@ fn block_parents_have_safety_comment(
|
||||
cx: &LateContext<'_>,
|
||||
id: hir::HirId,
|
||||
) -> bool {
|
||||
if let Some(node) = get_parent_node(cx.tcx, id) {
|
||||
let (span, hir_id) = match node {
|
||||
Node::Expr(expr) => match get_parent_node(cx.tcx, expr.hir_id) {
|
||||
Some(Node::Local(hir::Local { span, hir_id, .. })) => (*span, *hir_id),
|
||||
Some(Node::Item(hir::Item {
|
||||
kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
|
||||
span,
|
||||
owner_id,
|
||||
..
|
||||
})) => (*span, cx.tcx.local_def_id_to_hir_id(owner_id.def_id)),
|
||||
_ => {
|
||||
if is_branchy(expr) {
|
||||
return false;
|
||||
}
|
||||
(expr.span, expr.hir_id)
|
||||
},
|
||||
},
|
||||
Node::Stmt(hir::Stmt {
|
||||
kind:
|
||||
hir::StmtKind::Local(hir::Local { span, hir_id, .. })
|
||||
| hir::StmtKind::Expr(hir::Expr { span, hir_id, .. })
|
||||
| hir::StmtKind::Semi(hir::Expr { span, hir_id, .. }),
|
||||
..
|
||||
})
|
||||
| Node::Local(hir::Local { span, hir_id, .. }) => (*span, *hir_id),
|
||||
let (span, hir_id) = match cx.tcx.parent_hir_node(id) {
|
||||
Node::Expr(expr) => match cx.tcx.parent_hir_node(expr.hir_id) {
|
||||
Node::Local(hir::Local { span, hir_id, .. }) => (*span, *hir_id),
|
||||
Node::Item(hir::Item {
|
||||
kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
|
||||
span,
|
||||
owner_id,
|
||||
..
|
||||
}) => (*span, cx.tcx.local_def_id_to_hir_id(owner_id.def_id)),
|
||||
_ => return false,
|
||||
};
|
||||
// if unsafe block is part of a let/const/static statement,
|
||||
// and accept_comment_above_statement is set to true
|
||||
// we accept the safety comment in the line the precedes this statement.
|
||||
accept_comment_above_statement
|
||||
&& span_with_attrs_has_safety_comment(cx, span, hir_id, accept_comment_above_attributes)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
_ => {
|
||||
if is_branchy(expr) {
|
||||
return false;
|
||||
}
|
||||
(expr.span, expr.hir_id)
|
||||
},
|
||||
},
|
||||
Node::Stmt(hir::Stmt {
|
||||
kind:
|
||||
hir::StmtKind::Local(hir::Local { span, hir_id, .. })
|
||||
| hir::StmtKind::Expr(hir::Expr { span, hir_id, .. })
|
||||
| hir::StmtKind::Semi(hir::Expr { span, hir_id, .. }),
|
||||
..
|
||||
})
|
||||
| Node::Local(hir::Local { span, hir_id, .. }) => (*span, *hir_id),
|
||||
Node::Item(hir::Item {
|
||||
kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
|
||||
span,
|
||||
owner_id,
|
||||
..
|
||||
}) => (*span, cx.tcx.local_def_id_to_hir_id(owner_id.def_id)),
|
||||
_ => return false,
|
||||
};
|
||||
// if unsafe block is part of a let/const/static statement,
|
||||
// and accept_comment_above_statement is set to true
|
||||
// we accept the safety comment in the line the precedes this statement.
|
||||
accept_comment_above_statement
|
||||
&& span_with_attrs_has_safety_comment(cx, span, hir_id, accept_comment_above_attributes)
|
||||
}
|
||||
|
||||
/// Extends `span` to also include its attributes, then checks if that span has a safety comment.
|
||||
@ -449,53 +445,49 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
|
||||
if item.span.ctxt() != SyntaxContext::root() {
|
||||
return HasSafetyComment::No;
|
||||
}
|
||||
if let Some(parent_node) = get_parent_node(cx.tcx, 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::Item(parent_item) => {
|
||||
if let ItemKind::Mod(parent_mod) = &parent_item.kind {
|
||||
comment_start_before_item_in_mod(cx, parent_mod, parent_item.span, item)
|
||||
} else {
|
||||
// Doesn't support impls in this position. Pretend a comment was found.
|
||||
return HasSafetyComment::Maybe;
|
||||
}
|
||||
},
|
||||
Node::Stmt(stmt) => {
|
||||
if let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id) {
|
||||
walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo)
|
||||
} else {
|
||||
// Problem getting the parent node. Pretend a comment was found.
|
||||
return HasSafetyComment::Maybe;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let comment_start = match cx.tcx.parent_hir_node(item.hir_id()) {
|
||||
Node::Crate(parent_mod) => comment_start_before_item_in_mod(cx, parent_mod, parent_mod.spans.inner_span, item),
|
||||
Node::Item(parent_item) => {
|
||||
if let ItemKind::Mod(parent_mod) = &parent_item.kind {
|
||||
comment_start_before_item_in_mod(cx, parent_mod, parent_item.span, item)
|
||||
} else {
|
||||
// Doesn't support impls in this position. Pretend a comment was found.
|
||||
return HasSafetyComment::Maybe;
|
||||
},
|
||||
};
|
||||
|
||||
let source_map = cx.sess().source_map();
|
||||
if let Some(comment_start) = comment_start
|
||||
&& let Ok(unsafe_line) = source_map.lookup_line(item.span.lo())
|
||||
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
|
||||
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
|
||||
&& let Some(src) = unsafe_line.sf.src.as_deref()
|
||||
{
|
||||
return if comment_start_line.line >= unsafe_line.line {
|
||||
HasSafetyComment::No
|
||||
}
|
||||
},
|
||||
Node::Stmt(stmt) => {
|
||||
if let Node::Block(block) = cx.tcx.parent_hir_node(stmt.hir_id) {
|
||||
walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo)
|
||||
} else {
|
||||
match text_has_safety_comment(
|
||||
src,
|
||||
&unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line],
|
||||
unsafe_line.sf.start_pos,
|
||||
) {
|
||||
Some(b) => HasSafetyComment::Yes(b),
|
||||
None => HasSafetyComment::No,
|
||||
}
|
||||
};
|
||||
}
|
||||
// Problem getting the parent node. Pretend a comment was found.
|
||||
return HasSafetyComment::Maybe;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// Doesn't support impls in this position. Pretend a comment was found.
|
||||
return HasSafetyComment::Maybe;
|
||||
},
|
||||
};
|
||||
|
||||
let source_map = cx.sess().source_map();
|
||||
if let Some(comment_start) = comment_start
|
||||
&& let Ok(unsafe_line) = source_map.lookup_line(item.span.lo())
|
||||
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
|
||||
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
|
||||
&& let Some(src) = unsafe_line.sf.src.as_deref()
|
||||
{
|
||||
return if comment_start_line.line >= unsafe_line.line {
|
||||
HasSafetyComment::No
|
||||
} else {
|
||||
match text_has_safety_comment(
|
||||
src,
|
||||
&unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line],
|
||||
unsafe_line.sf.start_pos,
|
||||
) {
|
||||
Some(b) => HasSafetyComment::Yes(b),
|
||||
None => HasSafetyComment::No,
|
||||
}
|
||||
};
|
||||
}
|
||||
HasSafetyComment::Maybe
|
||||
}
|
||||
@ -512,32 +504,30 @@ fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> H
|
||||
return HasSafetyComment::No;
|
||||
}
|
||||
|
||||
if let Some(parent_node) = get_parent_node(cx.tcx, hir_id) {
|
||||
let comment_start = match parent_node {
|
||||
Node::Block(block) => walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo),
|
||||
_ => return HasSafetyComment::Maybe,
|
||||
};
|
||||
let comment_start = match cx.tcx.parent_hir_node(hir_id) {
|
||||
Node::Block(block) => walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo),
|
||||
_ => return HasSafetyComment::Maybe,
|
||||
};
|
||||
|
||||
let source_map = cx.sess().source_map();
|
||||
if let Some(comment_start) = comment_start
|
||||
&& let Ok(unsafe_line) = source_map.lookup_line(span.lo())
|
||||
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
|
||||
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
|
||||
&& let Some(src) = unsafe_line.sf.src.as_deref()
|
||||
{
|
||||
return if comment_start_line.line >= unsafe_line.line {
|
||||
HasSafetyComment::No
|
||||
} else {
|
||||
match text_has_safety_comment(
|
||||
src,
|
||||
&unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line],
|
||||
unsafe_line.sf.start_pos,
|
||||
) {
|
||||
Some(b) => HasSafetyComment::Yes(b),
|
||||
None => HasSafetyComment::No,
|
||||
}
|
||||
};
|
||||
}
|
||||
let source_map = cx.sess().source_map();
|
||||
if let Some(comment_start) = comment_start
|
||||
&& let Ok(unsafe_line) = source_map.lookup_line(span.lo())
|
||||
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
|
||||
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
|
||||
&& let Some(src) = unsafe_line.sf.src.as_deref()
|
||||
{
|
||||
return if comment_start_line.line >= unsafe_line.line {
|
||||
HasSafetyComment::No
|
||||
} else {
|
||||
match text_has_safety_comment(
|
||||
src,
|
||||
&unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line],
|
||||
unsafe_line.sf.start_pos,
|
||||
) {
|
||||
Some(b) => HasSafetyComment::Yes(b),
|
||||
None => HasSafetyComment::No,
|
||||
}
|
||||
};
|
||||
}
|
||||
HasSafetyComment::Maybe
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::get_parent_node;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source};
|
||||
use core::ops::ControlFlow;
|
||||
@ -103,7 +102,7 @@ fn expr_needs_inferred_result<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -
|
||||
return false;
|
||||
}
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ impl EarlyLintPass for UnusedUnit {
|
||||
&& let ctxt = block.span.ctxt()
|
||||
&& stmt.span.ctxt() == ctxt
|
||||
&& expr.span.ctxt() == ctxt
|
||||
&& expr.attrs.is_empty()
|
||||
{
|
||||
let sp = expr.span;
|
||||
span_lint_and_sugg(
|
||||
|
@ -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>> {
|
||||
let map = cx.tcx.hir();
|
||||
|
||||
match cx.tcx.parent_hir_node(hir_id) {
|
||||
hir::Node::Local(local) => Some(local),
|
||||
hir::Node::Pat(pattern) => get_parent_local_hir_id(cx, pattern.hir_id),
|
||||
|
@ -4,12 +4,12 @@ use std::ops::ControlFlow;
|
||||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
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::visitors::for_each_local_use_after_expr;
|
||||
use clippy_utils::{get_parent_expr, higher, is_trait_method};
|
||||
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_middle::ty;
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
@ -52,6 +52,172 @@ declare_clippy_lint! {
|
||||
|
||||
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 {
|
||||
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 check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
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.)
|
||||
(SuggestedType::SliceRef(mutability), expr.span)
|
||||
} else {
|
||||
// `expr` is the `vec![_]` expansion, so suggest `[_]`
|
||||
// and also use the span of the actual `vec![_]` expression
|
||||
(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 suggest_type(expr: &Expr<'_>) -> SuggestedType {
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, mutability, _) = expr.kind {
|
||||
// `expr` is `&vec![_]`, so suggest `&[_]` (or `&mut[_]` resp.)
|
||||
SuggestedType::SliceRef(mutability)
|
||||
} else {
|
||||
// `expr` is the `vec![_]` expansion, so suggest `[_]`
|
||||
SuggestedType::Array
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ publish = false
|
||||
[dependencies]
|
||||
clippy_config = { path = "../clippy_config" }
|
||||
arrayvec = { version = "0.7", default-features = false }
|
||||
itertools = "0.11"
|
||||
itertools = "0.12"
|
||||
rustc-semver = "1.1"
|
||||
|
||||
[features]
|
||||
|
@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! - 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 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)
|
||||
}
|
||||
|
||||
#[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 {
|
||||
use ItemKind::*;
|
||||
match (l, r) {
|
||||
@ -691,7 +691,9 @@ pub fn eq_ty(l: &Ty, r: &Ty) -> bool {
|
||||
match (&l.kind, &r.kind) {
|
||||
(Paren(l), _) => 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),
|
||||
(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),
|
||||
|
@ -40,7 +40,7 @@ fn docs_link(diag: &mut DiagnosticBuilder<'_, ()>, lint: &'static Lint) {
|
||||
///
|
||||
/// ```ignore
|
||||
/// 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);
|
||||
/// | ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -65,7 +65,7 @@ pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<Mult
|
||||
///
|
||||
/// ```text
|
||||
/// 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;
|
||||
/// | ^^^^^^^^^^^^
|
||||
@ -103,14 +103,14 @@ pub fn span_lint_and_help<T: LintContext>(
|
||||
///
|
||||
/// ```text
|
||||
/// 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);
|
||||
/// | ^^^^^^^^^^^^^^^^^^^
|
||||
/// |
|
||||
/// = note: `-D clippy::forget-ref` implied by `-D warnings`
|
||||
/// note: argument has type &SomeStruct
|
||||
/// --> $DIR/drop_forget_ref.rs:10:12
|
||||
/// --> tests/ui/drop_forget_ref.rs:10:12
|
||||
/// |
|
||||
/// 10 | forget(&SomeStruct);
|
||||
/// | ^^^^^^^^^^^
|
||||
@ -186,7 +186,7 @@ pub fn span_lint_hir_and_then(
|
||||
///
|
||||
/// ```text
|
||||
/// 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);
|
||||
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`
|
||||
|
@ -132,7 +132,6 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||
}
|
||||
|
||||
/// Checks whether two blocks are the same.
|
||||
#[expect(clippy::similar_names)]
|
||||
fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
|
||||
use TokenKind::{Semi, Whitespace};
|
||||
if left.stmts.len() != right.stmts.len() {
|
||||
@ -247,7 +246,7 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||
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 {
|
||||
if !self.check_ctxt(left.span.ctxt(), right.span.ctxt()) {
|
||||
return false;
|
||||
@ -463,7 +462,6 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::similar_names)]
|
||||
fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool {
|
||||
match (left, right) {
|
||||
(&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()
|
||||
}
|
||||
|
||||
#[expect(clippy::similar_names)]
|
||||
fn eq_span_tokens(
|
||||
cx: &LateContext<'_>,
|
||||
left: impl SpanRange,
|
||||
|
@ -14,7 +14,7 @@
|
||||
clippy::missing_panics_doc,
|
||||
clippy::must_use_candidate,
|
||||
rustc::diagnostic_outside_of_impl,
|
||||
rustc::untranslatable_diagnostic,
|
||||
rustc::untranslatable_diagnostic
|
||||
)]
|
||||
// warn on the same lints as `clippy_lints`
|
||||
#![warn(trivial_casts, trivial_numeric_casts)]
|
||||
@ -1307,11 +1307,6 @@ pub fn contains_return<'tcx>(expr: impl Visitable<'tcx>) -> bool {
|
||||
.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.
|
||||
pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
|
||||
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
|
||||
/// constraint lints
|
||||
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) {
|
||||
Some(Node::Expr(parent)) => Some(parent),
|
||||
match cx.tcx.parent_hir_node(hir_id) {
|
||||
Node::Expr(parent) => Some(parent),
|
||||
_ => 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.
|
||||
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> {
|
||||
|
@ -11,7 +11,7 @@ pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
|
||||
["rustc_lint_defs", "Applicability", "MaybeIncorrect"],
|
||||
["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 BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"];
|
||||
pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"];
|
||||
|
@ -174,9 +174,8 @@ fn check_rvalue<'tcx>(
|
||||
))
|
||||
}
|
||||
},
|
||||
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::DebugAssertions, _) | Rvalue::ShallowInitBox(_, _) => {
|
||||
Ok(())
|
||||
},
|
||||
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::DebugAssertions, _)
|
||||
| Rvalue::ShallowInitBox(_, _) => Ok(()),
|
||||
Rvalue::UnaryOp(_, operand) => {
|
||||
let ty = operand.ty(body, tcx);
|
||||
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 {
|
||||
// FIXME(effects, fee1-dead) revert to const destruct once it works again
|
||||
#[expect(unused)]
|
||||
|
@ -8,7 +8,7 @@ publish = false
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.11"
|
||||
itertools = "0.12"
|
||||
quote = "1.0.21"
|
||||
syn = "2.0"
|
||||
|
||||
|
@ -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)
|
||||
new_stats_deduped
|
||||
.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)| {
|
||||
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)
|
||||
new_stats_deduped
|
||||
.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)| {
|
||||
let old_val = old_stats_deduped.get::<str>(new_key).unwrap();
|
||||
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)
|
||||
old_stats_deduped
|
||||
.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))
|
||||
.for_each(|(old_key, old_value)| {
|
||||
println!("{old_key} {old_value} => 0");
|
||||
|
@ -1,3 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2024-02-08"
|
||||
channel = "nightly-2024-02-22"
|
||||
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
|
||||
|
@ -272,7 +272,9 @@ pub fn main() {
|
||||
},
|
||||
_ => 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(), "clippy".into()])
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
// We enable Clippy if one of the following conditions is met
|
||||
|
@ -4,6 +4,7 @@
|
||||
#![warn(rust_2018_idioms, unused_lifetimes)]
|
||||
#![allow(unused_extern_crates)]
|
||||
|
||||
use ui_test::spanned::Spanned;
|
||||
use ui_test::{status_emitter, Args, CommandBuilder, Config, Match, Mode, OutputConflictHandling};
|
||||
|
||||
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 mut config = Config {
|
||||
mode: Mode::Yolo {
|
||||
rustfix: ui_test::RustfixMode::Everything,
|
||||
},
|
||||
output_conflict_handling: OutputConflictHandling::Error,
|
||||
filter_files: env::var("TESTNAME")
|
||||
.map(|filters| filters.split(',').map(str::to_string).collect())
|
||||
.unwrap_or_default(),
|
||||
target: None,
|
||||
bless_command: Some("cargo uibless".into()),
|
||||
out_dir: target_dir.join("ui_test"),
|
||||
..Config::rustc(Path::new("tests").join(test_dir))
|
||||
};
|
||||
config.with_args(&args, /* bless by default */ false);
|
||||
if let OutputConflictHandling::Error(err) = &mut config.output_conflict_handling {
|
||||
*err = "cargo uibless".into();
|
||||
}
|
||||
config.comment_defaults.base().mode = Some(Spanned::dummy(Mode::Yolo {
|
||||
rustfix: ui_test::RustfixMode::Everything,
|
||||
}))
|
||||
.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 deps_path = current_exe_path.parent().unwrap();
|
||||
let profile_path = deps_path.parent().unwrap();
|
||||
@ -179,9 +181,7 @@ fn run_internal_tests() {
|
||||
return;
|
||||
}
|
||||
let (mut config, args) = base_config("ui-internal");
|
||||
if let OutputConflictHandling::Error(err) = &mut config.output_conflict_handling {
|
||||
*err = "cargo uitest --features internal -- -- --bless".into();
|
||||
}
|
||||
config.bless_command = Some("cargo uitest --features internal -- -- --bless".into());
|
||||
|
||||
ui_test::run_tests_generic(
|
||||
vec![config],
|
||||
@ -196,8 +196,10 @@ fn run_ui_toml() {
|
||||
let (mut config, args) = base_config("ui-toml");
|
||||
|
||||
config
|
||||
.stderr_filters
|
||||
.push((Match::from(env::current_dir().unwrap().as_path()), b"$DIR"));
|
||||
.comment_defaults
|
||||
.base()
|
||||
.normalize_stderr
|
||||
.push((Match::from(env::current_dir().unwrap().as_path()), b"$DIR".into()));
|
||||
|
||||
ui_test::run_tests_generic(
|
||||
vec![config],
|
||||
@ -213,6 +215,8 @@ fn run_ui_toml() {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Allow `Default::default` as `OptWithSpan` is not nameable
|
||||
#[allow(clippy::default_trait_access)]
|
||||
fn run_ui_cargo() {
|
||||
if IS_RUSTC_TEST_SUITE {
|
||||
return;
|
||||
@ -234,11 +238,13 @@ fn run_ui_cargo() {
|
||||
} else {
|
||||
"cargo-clippy"
|
||||
});
|
||||
config.edition = None;
|
||||
config.comment_defaults.base().edition = Default::default();
|
||||
|
||||
config
|
||||
.stderr_filters
|
||||
.push((Match::from(env::current_dir().unwrap().as_path()), b"$DIR"));
|
||||
.comment_defaults
|
||||
.base()
|
||||
.normalize_stderr
|
||||
.push((Match::from(env::current_dir().unwrap().as_path()), b"$DIR".into()));
|
||||
|
||||
let ignored_32bit = |path: &Path| {
|
||||
// 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(
|
||||
vec![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| {},
|
||||
status_emitter::Text::from(args.format),
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 | | #[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`
|
||||
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)]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
@ -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)
|
||||
|
||||
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 | | #[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)
|
||||
|
||||
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 | | #[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)
|
||||
|
||||
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 | | pub clippy::MISSING_ATTRIBUTE_TWO,
|
||||
|
@ -1,5 +1,5 @@
|
||||
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
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -9,7 +9,7 @@ LL | /// Check for lint formulations that are correct
|
||||
= help: to override `-D warnings` add `#[allow(clippy::almost_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
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 | | 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)`
|
||||
|
|
||||
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)]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
= note: `#[deny(clippy::collapsible_span_lint_calls)]` implied by `#[deny(clippy::internal)]`
|
||||
|
||||
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 | | 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)`
|
||||
|
||||
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 | | 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)`
|
||||
|
||||
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 | | 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)`
|
||||
|
||||
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 | | db.note(note_msg);
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 | | /// ### What it does
|
||||
@ -11,7 +11,7 @@ LL | | }
|
||||
| |_^
|
||||
|
|
||||
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)]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 | | pub clippy::TEST_LINT_DEFAULT,
|
||||
@ -10,7 +10,7 @@ LL | | }
|
||||
| |_^
|
||||
|
|
||||
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)]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
@ -1,5 +1,5 @@
|
||||
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, |_| {});
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -8,7 +8,7 @@ LL | cx.span_lint(lint, span, msg, |_| {});
|
||||
= help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]`
|
||||
|
||||
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, |_| {});
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -1,30 +1,30 @@
|
||||
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");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::f32`
|
||||
|
|
||||
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)]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
= note: `#[deny(clippy::interning_defined_symbol)]` implied by `#[deny(clippy::internal)]`
|
||||
|
||||
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);
|
||||
| ^^^^^^^^^ help: try: `rustc_span::sym::f32`
|
||||
|
||||
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");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::proc_dash_macro`
|
||||
|
||||
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");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::symbol::kw::SelfLower`
|
||||
|
@ -1,11 +1,11 @@
|
||||
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 {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
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)]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
@ -17,7 +17,7 @@ LL + extract_msrv_attr!(LateContext);
|
||||
|
|
||||
|
||||
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 {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -1,5 +1,5 @@
|
||||
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"];
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -8,13 +8,13 @@ LL | pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"
|
||||
= help: to override `-D warnings` add `#[allow(clippy::invalid_paths)]`
|
||||
|
||||
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"];
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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"];
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 | | pub clippy::TEST_LINT,
|
||||
@ -10,7 +10,7 @@ LL | | }
|
||||
| |_^
|
||||
|
|
||||
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)]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
@ -1,11 +1,11 @@
|
||||
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();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `outer_expn_data()`
|
||||
|
|
||||
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)]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user