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

This commit is contained in:
Philipp Krones 2024-02-27 15:25:18 +01:00
parent 5931794375
commit 7be6e2178e
1096 changed files with 14195 additions and 10884 deletions

4
.github/driver.sh vendored
View file

@ -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

View file

@ -5125,6 +5125,7 @@ Released 2018-09-13
[`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access
[`default_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

View file

@ -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" }

View file

@ -33,26 +33,29 @@ disallowed-names = ["bar", ".."] # -> ["bar", "foo", "baz", "quux"]
To deactivate the "for further information visit *lint-link*" message you can define the `CLIPPY_DISABLE_DOCS_LINKS`
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.

View file

@ -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)`

View file

@ -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() {}
| ^^^

View file

@ -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`

View file

@ -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));

View file

@ -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;

View file

@ -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"

View file

@ -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"] }

View file

@ -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);
}
}
}

View file

@ -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 {

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::source::snippet_block_with_applicability;
use clippy_utils::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

View file

@ -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>,
}

View file

@ -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)

View file

@ -131,8 +131,7 @@ pub(super) fn check(
let cast_from_ptr_size = def.repr().int.map_or(true, |ty| matches!(ty, IntegerType::Pointer(_),));
let 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,

View file

@ -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
}

View file

@ -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 {

View file

@ -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)

View file

@ -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(());
}

View file

@ -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,

View file

@ -2,9 +2,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::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),

View file

@ -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);
}
}

View file

@ -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());
}

View file

@ -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;
}
}

View file

@ -4,7 +4,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::is_diag_trait_item;
use clippy_utils::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: &macro_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>)

View file

@ -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,
);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
},
_ => {},

View file

@ -255,7 +255,9 @@ impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> {
&& let hir::Node::Expr(maybe_addrof_expr) = cx.tcx.parent_hir_node(parent_id)
&& let hir::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;
}

View file

@ -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()

View file

@ -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);

View file

@ -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 {

View file

@ -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`
}

View file

@ -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,

View file

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

View file

@ -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 cant always be merged together
// If both arms overlap with an arm in between then these can't be merged either.
!(backwards_blocking_idxs[max_index] > min_index && forwards_blocking_idxs[min_index] < max_index)
&& 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 cant always be merged together
// If both arms overlap with an arm in between then these can't be merged either.
!(backwards_blocking_idxs[max_index] > min_index && forwards_blocking_idxs[min_index] < max_index)
&& check_same_guard()
&& check_same_body()
};
let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();

View file

@ -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
}

View file

@ -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,
} {

View file

@ -37,7 +37,12 @@ pub(super) fn check<'tcx>(
}
}
fn set_diagnostic<'tcx>(diag: &mut DiagnosticBuilder<'_, ()>, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) {
fn set_diagnostic<'tcx>(
diag: &mut DiagnosticBuilder<'_, ()>,
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
found: FoundSigDrop,
) {
if found.lint_suggestion == LintSuggestion::MoveAndClone {
// If 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.

View file

@ -3,7 +3,9 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lin
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::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);

View file

@ -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))
{

View file

@ -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,

View file

@ -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,
},
}

View file

@ -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);
},
_ => {},
}
}

View file

@ -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,
);
},
);
}
},
_ => (),
}
}

View file

@ -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:

View file

@ -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,

View file

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

View file

@ -3,7 +3,9 @@ use super::unnecessary_iter_cloned::{self, is_into_iter};
use clippy_config::msrvs::{self, Msrv};
use clippy_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);
}
}

View file

@ -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
}
}

View file

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

View file

@ -6,8 +6,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::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: &[], .. })
)
}

View file

@ -2,7 +2,7 @@ use super::needless_pass_by_value::requires_exact_signature;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::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
{

View file

@ -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;

View file

@ -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()

View file

@ -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 {

View file

@ -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> {

View file

@ -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)

View file

@ -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)) => {

View file

@ -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<'_>,

View file

@ -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");
},
);
}
}

View file

@ -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 { "" },

View file

@ -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 {

View file

@ -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) {

View file

@ -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);
}
}

View file

@ -62,7 +62,7 @@ declare_lint_pass!(SlowVectorInit => [SLOW_VECTOR_INITIALIZATION]);
/// assigned to a variable. For example, `let mut vec = Vec::with_capacity(0)` or
/// `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

View file

@ -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,
};

View file

@ -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,

View file

@ -153,10 +153,7 @@ fn all_bindings_are_for_conv<'tcx>(
let Some(locals) = locals.iter().map(|e| path_to_local(e)).collect::<Option<Vec<_>>>() else {
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()

View file

@ -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
}

View file

@ -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;
}

View file

@ -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(

View file

@ -1005,8 +1005,6 @@ fn get_parent_local<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -
}
fn get_parent_local_hir_id<'hir>(cx: &LateContext<'hir>, hir_id: hir::HirId) -> Option<&'hir hir::Local<'hir>> {
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),

View file

@ -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())
}

View file

@ -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]

View file

@ -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),

View file

@ -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)`

View file

@ -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,

View file

@ -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> {

View file

@ -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"];

View file

@ -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)]

View file

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

View file

@ -749,7 +749,7 @@ fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, us
// list all new counts (key is in new stats but not in old stats)
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");

View file

@ -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"]

View file

@ -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

View file

@ -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),

View file

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

View file

@ -1,5 +1,5 @@
error: this item has an invalid `clippy::version` attribute
--> $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,

View file

@ -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
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -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);

View file

@ -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)]
| ^^^^^^^^^^^^^^^^

View file

@ -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)]
| ^^^^^^^^^^^^^^^^

View file

@ -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, |_| {});
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -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`

View file

@ -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 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -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"];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -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)]
| ^^^^^^^^^^^^^^^^

View file

@ -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