Auto merge of #115790 - flip1995:clippyup, r=Manishearth

Update Clippy

r? `@Manishearth`
This commit is contained in:
bors 2023-09-13 20:28:02 +00:00
commit 0698591527
174 changed files with 4682 additions and 1635 deletions

2
.github/driver.sh vendored
View file

@ -30,7 +30,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/d" double_neg.stderr >normalized.stderr
sed -e "s,tests/ui,\$DIR," -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

@ -52,24 +52,14 @@ jobs:
needs: changelog
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
host: [x86_64-unknown-linux-gnu, i686-unknown-linux-gnu, x86_64-apple-darwin, x86_64-pc-windows-msvc]
exclude:
include:
- os: ubuntu-latest
host: x86_64-apple-darwin
host: x86_64-unknown-linux-gnu
- os: ubuntu-latest
host: x86_64-pc-windows-msvc
- os: macos-latest
host: x86_64-unknown-linux-gnu
- os: macos-latest
host: i686-unknown-linux-gnu
- os: macos-latest
host: x86_64-pc-windows-msvc
- os: windows-latest
host: x86_64-unknown-linux-gnu
- os: windows-latest
host: i686-unknown-linux-gnu
- os: windows-latest
host: x86_64-pc-windows-msvc
- os: macos-latest
host: x86_64-apple-darwin
runs-on: ${{ matrix.os }}
@ -84,8 +74,17 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Install i686 dependencies
if: matrix.host == 'i686-unknown-linux-gnu'
run: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install gcc-multilib zlib1g-dev:i386
- name: Install toolchain
run: rustup show active-toolchain
run: |
rustup set default-host ${{ matrix.host }}
rustup show active-toolchain
# Run
- name: Set LD_LIBRARY_PATH (Linux)
@ -109,11 +108,11 @@ jobs:
run: cargo build --tests --features deny-warnings,internal
- name: Test
if: runner.os == 'Linux'
if: matrix.host == 'x86_64-unknown-linux-gnu'
run: cargo test --features deny-warnings,internal
- name: Test
if: runner.os != 'Linux'
if: matrix.host != 'x86_64-unknown-linux-gnu'
run: cargo test --features deny-warnings,internal -- --skip dogfood
- name: Test clippy_lints

View file

@ -5031,6 +5031,7 @@ Released 2018-09-13
[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
[`iter_on_empty_collections`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_empty_collections
[`iter_on_single_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_single_items
[`iter_out_of_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_out_of_bounds
[`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned
[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
[`iter_skip_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_zero
@ -5131,6 +5132,7 @@ Released 2018-09-13
[`misnamed_getters`]: https://rust-lang.github.io/rust-clippy/master/index.html#misnamed_getters
[`misrefactored_assign_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#misrefactored_assign_op
[`missing_assert_message`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_assert_message
[`missing_asserts_for_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_asserts_for_indexing
[`missing_const_for_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn
[`missing_docs_in_private_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items
[`missing_enforced_import_renames`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_enforced_import_renames
@ -5204,6 +5206,8 @@ Released 2018-09-13
[`no_effect_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_underscore_binding
[`no_mangle_with_rust_abi`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_mangle_with_rust_abi
[`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal
[`non_canonical_clone_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_canonical_clone_impl
[`non_canonical_partial_ord_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_canonical_partial_ord_impl
[`non_minimal_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_minimal_cfg
[`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions
[`non_send_fields_in_send_ty`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_send_fields_in_send_ty
@ -5570,4 +5574,5 @@ Released 2018-09-13
[`allow-one-hash-in-raw-strings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-strings
[`absolute-paths-max-segments`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-max-segments
[`absolute-paths-allowed-crates`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-allowed-crates
[`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow
<!-- end autogenerated links to configuration documentation -->

View file

@ -27,7 +27,7 @@ tempfile = { version = "3.2", optional = true }
termize = "0.1"
[dev-dependencies]
ui_test = "0.18.1"
ui_test = "0.20"
tester = "0.9"
regex = "1.5"
toml = "0.7.3"

View file

@ -14,8 +14,11 @@
- [Basics](development/basics.md)
- [Adding Lints](development/adding_lints.md)
- [Defining Lints](development/defining_lints.md)
- [Writing tests](development/writing_tests.md)
- [Lint Passes](development/lint_passes.md)
- [Emitting lints](development/emitting_lints.md)
- [Type Checking](development/type_checking.md)
- [Trait Checking](development/trait_checking.md)
- [Method Checking](development/method_checking.md)
- [Macro Expansions](development/macro_expansions.md)
- [Common Tools](development/common_tools_writing_lints.md)

View file

@ -0,0 +1,217 @@
# Emitting a lint
Once we have [defined a lint](defining_lints.md), written [UI
tests](writing_tests.md) and chosen [the lint pass](lint_passes.md) for the lint,
we can begin the implementation of the lint logic so that we can emit it and
gradually work towards a lint that behaves as expected.
Note that we will not go into concrete implementation of a lint logic in this
chapter. We will go into details in later chapters as well as in two examples of
real Clippy lints.
To emit a lint, we must implement a pass (see [Lint Passes](lint_passes.md)) for
the lint that we have declared. In this example we'll implement a "late" lint,
so take a look at the [LateLintPass][late_lint_pass] documentation, which
provides an abundance of methods that we can implement for our lint.
```rust
pub trait LateLintPass<'tcx>: LintPass {
// Trait methods
}
```
By far the most common method used for Clippy lints is [`check_expr`
method][late_check_expr], this is because Rust is an expression language and,
more often than not, the lint we want to work on must examine expressions.
> _Note:_ If you don't fully understand what expressions are in Rust, take a
> look at the official documentation on [expressions][rust_expressions]
Other common ones include the [`check_fn` method][late_check_fn] and the
[`check_item` method][late_check_item].
### Emitting a lint
Inside the trait method that we implement, we can write down the lint logic and
emit the lint with suggestions.
Clippy's [diagnostics] provides quite a few diagnostic functions that we can use
to emit lints. Take a look at the documentation to pick one that suits your
lint's needs the best. Some common ones you will encounter in the Clippy
repository includes:
- [`span_lint`]: Emits a lint without providing any other information
- [`span_lint_and_note`]: Emits a lint and adds a note
- [`span_lint_and_help`]: Emits a lint and provides a helpful message
- [`span_lint_and_sugg`]: Emits a lint and provides a suggestion to fix the code
- [`span_lint_and_then`]: Like `span_lint`, but allows for a lot of output
customization.
```rust
impl<'tcx> LateLintPass<'tcx> for LintName {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Imagine that `some_lint_expr_logic` checks for requirements for emitting the lint
if some_lint_expr_logic(expr) {
span_lint_and_help(
cx, // < The context
LINT_NAME, // < The name of the lint in ALL CAPS
expr.span, // < The span to lint
"message on why the lint is emitted",
None, // < An optional help span (to highlight something in the lint)
"message that provides a helpful suggestion",
);
}
}
}
```
> Note: The message should be matter of fact and avoid capitalization and
> punctuation. If multiple sentences are needed, the messages should probably be
> split up into an error + a help / note / suggestion message.
## Suggestions: Automatic fixes
Some lints know what to change in order to fix the code. For example, the lint
[`range_plus_one`][range_plus_one] warns for ranges where the user wrote `x..y +
1` instead of using an [inclusive range][inclusive_range] (`x..=y`). The fix to
this code would be changing the `x..y + 1` expression to `x..=y`. **This is
where suggestions come in**.
A suggestion is a change that the lint provides to fix the issue it is linting.
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
|
LL | for _ in 1..1 + 1 {}
| ^^^^^^^^ help: use: `1..=1`
```
**Not all suggestions are always right**, some of them require human
supervision, that's why we have [Applicability][applicability].
Applicability indicates confidence in the correctness of the suggestion, some
are always right (`Applicability::MachineApplicable`), but we use
`Applicability::MaybeIncorrect` and others when talking about a suggestion that
may be incorrect.
### Example
The same lint `LINT_NAME` but that emits a suggestion would look something like this:
```rust
impl<'tcx> LateLintPass<'tcx> for LintName {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Imagine that `some_lint_expr_logic` checks for requirements for emitting the lint
if some_lint_expr_logic(expr) {
span_lint_and_sugg( // < Note this change
cx,
LINT_NAME,
span,
"message on why the lint is emitted",
"use",
format!("foo + {} * bar", snippet(cx, expr.span, "<default>")), // < Suggestion
Applicability::MachineApplicable,
);
}
}
}
```
Suggestions generally use the [`format!`][format_macro] macro to interpolate the
old values with the new ones. To get code snippets, use one of the `snippet*`
functions from `clippy_utils::source`.
## How to choose between notes, help messages and suggestions
Notes are presented separately from the main lint message, they provide useful
information that the user needs to understand why the lint was activated. They
are the most helpful when attached to a span.
Examples:
### Notes
```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
|
10 | forget(&SomeStruct);
| ^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::forget-ref` implied by `-D warnings`
note: argument has type &SomeStruct
--> $DIR/drop_forget_ref.rs:10:12
|
10 | forget(&SomeStruct);
| ^^^^^^^^^^^
```
### Help Messages
Help messages are specifically to help the user. These are used in situation
where you can't provide a specific machine applicable suggestion. They can also
be attached to a span.
Example:
```text
error: constant division of 0.0 with 0.0 will always result in NaN
--> $DIR/zero_div_zero.rs:6:25
|
6 | let other_f64_nan = 0.0f64 / 0.0;
| ^^^^^^^^^^^^
|
= help: consider using `f64::NAN` if you would like a constant representing NaN
```
### Suggestions
Suggestions are the most helpful, they are changes to the source code to fix the
error. The magic in suggestions is that tools like `rustfix` can detect them and
automatically fix your code.
Example:
```text
error: This `.fold` can be more succinctly expressed as `.any`
--> $DIR/methods.rs:390:13
|
390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`
|
```
### Snippets
Snippets are pieces of the source code (as a string), they are extracted
generally using the [`snippet`][snippet_fn] function.
For example, if you want to know how an item looks (and you know the item's
span), you could use `snippet(cx, span, "..")`.
## Final: Run UI Tests to Emit the Lint
Now, if we run our [UI test](writing_tests.md), we should see that Clippy now
produces output that contains the lint message we designed.
The next step is to implement the logic properly, which is a detail that we will
cover in the next chapters.
[diagnostics]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/diagnostics/index.html
[late_check_expr]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_expr
[late_check_fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_fn
[late_check_item]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_item
[late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html
[rust_expressions]: https://doc.rust-lang.org/reference/expressions.html
[`span_lint`]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/diagnostics/fn.span_lint.html
[`span_lint_and_note`]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/diagnostics/fn.span_lint_and_note.html
[`span_lint_and_help`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/diagnostics/fn.span_lint_and_help.html
[`span_lint_and_sugg`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/diagnostics/fn.span_lint_and_sugg.html
[`span_lint_and_then`]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/diagnostics/fn.span_lint_and_then.html
[range_plus_one]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one
[inclusive_range]: https://doc.rust-lang.org/std/ops/struct.RangeInclusive.html
[applicability]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_errors/enum.Applicability.html
[snippet_fn]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/source/fn.snippet.html
[format_macro]: https://doc.rust-lang.org/std/macro.format.html

View file

@ -0,0 +1,105 @@
# Trait Checking
Besides [type checking](type_checking.md), we might want to examine if
a specific type `Ty` implements certain trait when implementing a lint.
There are three approaches to achieve this, depending on if the target trait
that we want to examine has a [diagnostic item][diagnostic_items],
[lang item][lang_items], or neither.
## Using Diagnostic Items
As explained in the [Rust Compiler Development Guide][rustc_dev_guide], diagnostic items
are introduced for identifying types via [Symbols][symbol].
For instance, if we want to examine whether an expression implements
the `Iterator` trait, we could simply write the following code,
providing the `LateContext` (`cx`), our expression at hand, and
the symbol of the trait in question:
```rust
use clippy_utils::is_trait_method;
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass};
use rustc_span::symbol::sym;
impl LateLintPass<'_> for CheckIteratorTraitLint {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
let implements_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| {
implements_trait(cx, cx.typeck_results().expr_ty(arg), id, &[])
});
if implements_iterator {
// [...]
}
}
}
```
> **Note**: Refer to [this index][symbol_index] for all the defined `Symbol`s.
## Using Lang Items
Besides diagnostic items, we can also use [`lang_items`][lang_items].
Take a look at the documentation to find that `LanguageItems` contains
all language items defined in the compiler.
Using one of its `*_trait` method, we could obtain the [DefId] of any
specific item, such as `Clone`, `Copy`, `Drop`, `Eq`, which are familiar
to many Rustaceans.
For instance, if we want to examine whether an expression `expr` implements
`Drop` trait, we could access `LanguageItems` via our `LateContext`'s
[TyCtxt], which provides a `lang_items` method that will return the id of
`Drop` trait to us. Then, by calling Clippy utils function `implements_trait`
we can check that the `Ty` of the `expr` implements the trait:
```rust
use clippy_utils::implements_trait;
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass};
impl LateLintPass<'_> for CheckDropTraitLint {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
let ty = cx.typeck_results().expr_ty(expr);
if cx.tcx.lang_items()
.drop_trait()
.map_or(false, |id| implements_trait(cx, ty, id, &[])) {
println!("`expr` implements `Drop` trait!");
}
}
}
```
## Using Type Path
If neither diagnostic item nor a language item is available, we can use
[`clippy_utils::paths`][paths] with the `match_trait_method` to determine trait
implementation.
> **Note**: This approach should be avoided if possible, the best thing to do would be to make a PR to [`rust-lang/rust`][rust] adding a diagnostic item.
Below, we check if the given `expr` implements the `Iterator`'s trait method `cloned` :
```rust
use clippy_utils::{match_trait_method, paths};
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass};
impl LateLintPass<'_> for CheckTokioAsyncReadExtTrait {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if match_trait_method(cx, expr, &paths::CORE_ITER_CLONED) {
println!("`expr` implements `CORE_ITER_CLONED` trait!");
}
}
}
```
[DefId]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/def_id/struct.DefId.html
[diagnostic_items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html
[lang_items]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/lang_items/struct.LanguageItems.html
[paths]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/paths.rs
[rustc_dev_guide]: https://rustc-dev-guide.rust-lang.org/
[symbol]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Symbol.html
[symbol_index]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_span/symbol/sym/index.html
[TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html
[rust]: https://github.com/rust-lang/rust

View file

@ -0,0 +1,218 @@
# Testing
Developing lints for Clippy is a Test-Driven Development (TDD) process because
our first task before implementing any logic for a new lint is to write some test cases.
## Develop Lints with Tests
When we develop Clippy, we enter a complex and chaotic realm full of
programmatic issues, stylistic errors, illogical code and non-adherence to convention.
Tests are the first layer of order we can leverage to define when and where
we want a new lint to trigger or not.
Moreover, writing tests first help Clippy developers to find a balance for
the first iteration of and further enhancements for a lint.
With test cases on our side, we will not have to worry about over-engineering
a lint on its first version nor missing out some obvious edge cases of the lint.
This approach empowers us to iteratively enhance each lint.
## Clippy UI Tests
We use **UI tests** for testing in Clippy. These UI tests check that the output
of Clippy is exactly as we expect it to be. Each test is just a plain Rust file
that contains the code we want to check.
The output of Clippy is compared against a `.stderr` file. Note that you don't
have to create this file yourself. We'll get to generating the `.stderr` files
with the command [`cargo bless`](#cargo-bless) (seen later on).
### Write Test Cases
Let us now think about some tests for our imaginary `foo_functions` lint. We
start by opening the test file `tests/ui/foo_functions.rs` that was created by
`cargo dev new_lint`.
Update the file with some examples to get started:
```rust
#![warn(clippy::foo_functions)] // < Add this, so the lint is guaranteed to be enabled in this file
// Impl methods
struct A;
impl A {
pub fn fo(&self) {}
pub fn foo(&self) {} //~ ERROR: function called "foo"
pub fn food(&self) {}
}
// Default trait methods
trait B {
fn fo(&self) {}
fn foo(&self) {} //~ ERROR: function called "foo"
fn food(&self) {}
}
// Plain functions
fn fo() {}
fn foo() {} //~ ERROR: function called "foo"
fn food() {}
fn main() {
// We also don't want to lint method calls
foo();
let a = A;
a.foo();
}
```
Without actual lint logic to emit the lint when we see a `foo` function name,
this test will just pass, because no lint will be emitted. However, we can now
run the test with the following command:
```sh
$ TESTNAME=foo_functions cargo uitest
```
Clippy will compile and it will conclude with an `ok` for the tests:
```
...Clippy warnings and test outputs...
test compile_test ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.48s
```
This is normal. After all, we wrote a bunch of Rust code but we haven't really
implemented any logic for Clippy to detect `foo` functions and emit a lint.
As we gradually implement our lint logic, we will keep running this UI test command.
Clippy will begin outputting information that allows us to check if the output is
turning into what we want it to be.
### Example output
As our `foo_functions` lint is tested, the output would look something like this:
```
failures:
---- compile_test stdout ----
normalized stderr:
error: function called "foo"
--> $DIR/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
|
LL | fn foo(&self) {}
| ^^^
error: function called "foo"
--> $DIR/foo_functions.rs:19:4
|
LL | fn foo() {}
| ^^^
error: aborting due to 3 previous errors
```
Note the *failures* label at the top of the fragment, we'll get rid of it
(saving this output) in the next section.
> _Note:_ You can run multiple test files by specifying a comma separated list:
> `TESTNAME=foo_functions,bar_methods,baz_structs`.
### `cargo bless`
Once we are satisfied with the output, we need to run this command to
generate or update the `.stderr` file for our lint:
```sh
$ TESTNAME=foo_functions cargo uibless
```
This writes the emitted lint suggestions and fixes to the `.stderr` file, with
the reason for the lint, suggested fixes, and line numbers, etc.
Running `TESTNAME=foo_functions cargo uitest` should pass then. When we commit
our lint, we need to commit the generated `.stderr` files, too.
In general, you should only commit files changed by `cargo bless` for the
specific lint you are creating/editing.
> _Note:_ If the generated `.stderr`, and `.fixed` files are empty,
> they should be removed.
## `toml` Tests
Some lints can be configured through a `clippy.toml` file. Those configuration
values are tested in `tests/ui-toml`.
To add a new test there, create a new directory and add the files:
- `clippy.toml`: Put here the configuration value you want to test.
- `lint_name.rs`: A test file where you put the testing code, that should see a
different lint behavior according to the configuration set in the
`clippy.toml` file.
The potential `.stderr` and `.fixed` files can again be generated with `cargo
bless`.
## Cargo Lints
The process of testing is different for Cargo lints in that now we are
interested in the `Cargo.toml` manifest file. In this case, we also need a
minimal crate associated with that manifest. Those tests are generated in
`tests/ui-cargo`.
Imagine we have a new example lint that is named `foo_categories`, we can run:
```sh
$ cargo dev new_lint --name=foo_categories --pass=late --category=cargo
```
After running `cargo dev new_lint` we will find by default two new crates,
each with its manifest file:
* `tests/ui-cargo/foo_categories/fail/Cargo.toml`: this file should cause the
new lint to raise an error.
* `tests/ui-cargo/foo_categories/pass/Cargo.toml`: this file should not trigger
the lint.
If you need more cases, you can copy one of those crates (under
`foo_categories`) and rename it.
The process of generating the `.stderr` file is the same as for other lints
and prepending the `TESTNAME` variable to `cargo uitest` works for Cargo lints too.
## Rustfix Tests
If the lint you are working on is making use of structured suggestions,
[`rustfix`] will apply the suggestions from the lint to the test file code and
compare that to the contents of a `.fixed` file.
Structured suggestions tell a user how to fix or re-write certain code that has
been linted with [`span_lint_and_sugg`].
Should `span_lint_and_sugg` be used to generate a suggestion, but not all
suggestions lead to valid code, you can use the `//@no-rustfix` comment on top
of the test file, to not run `rustfix` on that file.
We'll talk about suggestions more in depth in a [later chapter](emitting_lints.md).
Use `cargo bless` to automatically generate the `.fixed` file after running
the tests.
[`rustfix`]: https://github.com/rust-lang/rustfix
[`span_lint_and_sugg`]: https://doc.rust-lang.org/beta/nightly-rustc/clippy_utils/diagnostics/fn.span_lint_and_sugg.html
## Testing Manually
Manually testing against an example file can be useful if you have added some
`println!`s and the test suite output becomes unreadable.
To try Clippy with your local modifications, run from the working copy root.
```sh
$ cargo dev lint input.rs
```

View file

@ -751,3 +751,27 @@ Which crates to allow absolute paths from
* [`absolute_paths`](https://rust-lang.github.io/rust-clippy/master/index.html#absolute_paths)
## `enforce-iter-loop-reborrow`
#### Example
```
let mut vec = vec![1, 2, 3];
let rmvec = &mut vec;
for _ in rmvec.iter() {}
for _ in rmvec.iter_mut() {}
```
Use instead:
```
let mut vec = vec![1, 2, 3];
let rmvec = &mut vec;
for _ in &*rmvec {}
for _ in &mut *rmvec {}
```
**Default Value:** `false` (`bool`)
---
**Affected lints:**
* [`explicit_iter_loop`](https://rust-lang.github.io/rust-clippy/master/index.html#explicit_iter_loop)

View file

@ -211,8 +211,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO,
crate::implied_bounds_in_impls::IMPLIED_BOUNDS_IN_IMPLS_INFO,
crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO,
crate::incorrect_impls::INCORRECT_CLONE_IMPL_ON_COPY_TYPE_INFO,
crate::incorrect_impls::INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE_INFO,
crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO,
crate::indexing_slicing::INDEXING_SLICING_INFO,
crate::indexing_slicing::OUT_OF_BOUNDS_INDEXING_INFO,
@ -365,6 +363,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::ITER_NTH_ZERO_INFO,
crate::methods::ITER_ON_EMPTY_COLLECTIONS_INFO,
crate::methods::ITER_ON_SINGLE_ITEMS_INFO,
crate::methods::ITER_OUT_OF_BOUNDS_INFO,
crate::methods::ITER_OVEREAGER_CLONED_INFO,
crate::methods::ITER_SKIP_NEXT_INFO,
crate::methods::ITER_SKIP_ZERO_INFO,
@ -456,6 +455,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::misc_early::ZERO_PREFIXED_LITERAL_INFO,
crate::mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER_INFO,
crate::missing_assert_message::MISSING_ASSERT_MESSAGE_INFO,
crate::missing_asserts_for_indexing::MISSING_ASSERTS_FOR_INDEXING_INFO,
crate::missing_const_for_fn::MISSING_CONST_FOR_FN_INFO,
crate::missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS_INFO,
crate::missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES_INFO,
@ -496,6 +496,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::no_effect::NO_EFFECT_UNDERSCORE_BINDING_INFO,
crate::no_effect::UNNECESSARY_OPERATION_INFO,
crate::no_mangle_with_rust_abi::NO_MANGLE_WITH_RUST_ABI_INFO,
crate::non_canonical_impls::NON_CANONICAL_CLONE_IMPL_INFO,
crate::non_canonical_impls::NON_CANONICAL_PARTIAL_ORD_IMPL_INFO,
crate::non_copy_const::BORROW_INTERIOR_MUTABLE_CONST_INFO,
crate::non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST_INFO,
crate::non_expressive_names::JUST_UNDERSCORES_AND_DIGITS_INFO,

View file

@ -69,6 +69,9 @@ impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation {
}
/// Returns true if the given item is a union with at least two non-ZST fields.
/// (ZST fields having an arbitrary offset is completely inconsequential, and
/// if there is only one field left after ignoring ZST fields then the offset
/// of that field does not matter either.)
fn is_union_with_two_non_zst_fields(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
if let ItemKind::Union(data, _) = &item.kind {
data.fields().iter().filter(|f| !is_zst(cx, f.ty)).count() >= 2

View file

@ -3,7 +3,7 @@ use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exact
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::{is_copy, peel_mid_ty_refs};
use clippy_utils::ty::{implements_trait, is_copy, peel_mid_ty_refs};
use clippy_utils::{
expr_use_ctxt, get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode,
};
@ -33,7 +33,6 @@ use rustc_middle::ty::{
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol};
use rustc_trait_selection::infer::InferCtxtExt as _;
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
use rustc_trait_selection::traits::{Obligation, ObligationCause};
use std::collections::VecDeque;
@ -452,13 +451,12 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
// Trait methods taking `self`
arg_ty
} && impl_ty.is_ref()
&& cx.tcx.infer_ctxt().build()
.type_implements_trait(
&& implements_trait(
cx,
impl_ty,
trait_id,
[impl_ty.into()].into_iter().chain(args.iter().copied()),
cx.param_env,
&args[..cx.tcx.generics_of(trait_id).params.len() - 1],
)
.must_apply_modulo_regions()
{
false
} else {
@ -609,12 +607,14 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
adjusted_ty,
},
));
} else if stability.is_deref_stable() {
} else if stability.is_deref_stable()
&& let Some(parent) = get_parent_expr(cx, expr)
{
self.state = Some((
State::ExplicitDeref { mutability: None },
StateData {
span: expr.span,
hir_id: expr.hir_id,
span: parent.span,
hir_id: parent.hir_id,
adjusted_ty,
},
));
@ -1399,6 +1399,13 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
return;
}
if let ExprKind::Field(parent_expr, _) = expr.kind
&& let ty::Adt(adt, _) = cx.typeck_results().expr_ty(parent_expr).kind()
&& adt.is_union()
{
// Auto deref does not apply on union field
return;
}
span_lint_hir_and_then(
cx,
EXPLICIT_AUTO_DEREF,

View file

@ -217,8 +217,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
if let &Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind();
if let attrs = cx.tcx.hir().attrs(item.hir_id());
if !attrs.iter().any(|attr| attr.doc_str().is_some());
if let child_attrs = cx.tcx.hir().attrs(impl_item_hir);
if !child_attrs.iter().any(|attr| attr.doc_str().is_some());
if cx.tcx.hir().attrs(impl_item_hir).is_empty();
then {
if adt_def.is_struct() {

View file

@ -1,8 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::macro_backtrace;
use rustc_ast::Attribute;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{Expr, ForeignItem, HirId, ImplItem, Item, Pat, Path, Stmt, TraitItem, Ty};
use rustc_hir::{Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, Pat, Path, Stmt, TraitItem, Ty};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{ExpnId, Span};
@ -111,6 +112,10 @@ impl LateLintPass<'_> for DisallowedMacros {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
self.check(cx, expr.span);
// `$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);
}
}
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
@ -147,4 +152,8 @@ impl LateLintPass<'_> for DisallowedMacros {
fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) {
self.check(cx, path.span);
}
fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) {
self.check(cx, attr.span);
}
}

View file

@ -472,7 +472,7 @@ struct DocHeaders {
fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[Attribute]) -> Option<DocHeaders> {
/// We don't want the parser to choke on intra doc links. Since we don't
/// actually care about rendering them, just pretend that all broken links are
/// actually care about rendering them, just pretend that all broken links
/// point to a fake address.
#[expect(clippy::unnecessary_wraps)] // we're following a type signature
fn fake_broken_link_callback<'a>(_: BrokenLink<'_>) -> Option<(CowStr<'a>, CowStr<'a>)> {

View file

@ -6,7 +6,7 @@ use clippy_utils::sugg::Sugg;
use clippy_utils::{contains_return, higher, is_else_clause, is_res_lang_ctor, path_res, peel_blocks};
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{Expr, ExprKind, Stmt, StmtKind};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
@ -83,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
&& then_expr.span.ctxt() == ctxt
&& is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome)
&& is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone)
&& !stmts_contains_early_return(then_block.stmts)
&& !contains_return(then_block.stmts)
{
let mut app = Applicability::Unspecified;
let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app).maybe_par().to_string();
@ -116,17 +116,3 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
extract_msrv_attr!(LateContext);
}
fn stmts_contains_early_return(stmts: &[Stmt<'_>]) -> bool {
stmts.iter().any(|stmt| {
let Stmt {
kind: StmtKind::Semi(e),
..
} = stmt
else {
return false;
};
contains_return(e)
})
}

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use hir::PatKind;
use hir::{Node, PatKind};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
@ -37,6 +37,17 @@ declare_lint_pass!(IgnoredUnitPatterns => [IGNORED_UNIT_PATTERNS]);
impl<'tcx> LateLintPass<'tcx> for IgnoredUnitPatterns {
fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx hir::Pat<'tcx>) {
match cx.tcx.hir().get_parent(pat.hir_id) {
Node::Param(param) if matches!(cx.tcx.hir().get_parent(param.hir_id), Node::Item(_)) => {
// Ignore function parameters
return;
},
Node::Local(local) if local.ty.is_some() => {
// Ignore let bindings with explicit type
return;
},
_ => {},
}
if matches!(pat.kind, PatKind::Wild) && cx.typeck_results().pat_ty(pat).is_unit() {
span_lint_and_sugg(
cx,

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir::def_id::LocalDefId;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
Body, FnDecl, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, ItemKind, TraitBoundModifier, TraitItem,
@ -9,7 +9,7 @@ use rustc_hir::{
};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, ClauseKind, TyCtxt};
use rustc_middle::ty::{self, ClauseKind, Generics, Ty, TyCtxt};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
@ -45,52 +45,169 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.73.0"]
pub IMPLIED_BOUNDS_IN_IMPLS,
complexity,
nursery,
"specifying bounds that are implied by other bounds in `impl Trait` type"
}
declare_lint_pass!(ImpliedBoundsInImpls => [IMPLIED_BOUNDS_IN_IMPLS]);
/// This function tries to, for all type parameters in a supertype predicate `GenericTrait<U>`,
/// check if the substituted type in the implied-by bound matches with what's subtituted in the
/// implied type.
#[allow(clippy::too_many_arguments)]
fn emit_lint(
cx: &LateContext<'_>,
poly_trait: &rustc_hir::PolyTraitRef<'_>,
opaque_ty: &rustc_hir::OpaqueTy<'_>,
index: usize,
// The bindings that were implied
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,
) {
let implied_by = snippet(cx, implied_by_span, "..");
span_lint_and_then(
cx,
IMPLIED_BOUNDS_IN_IMPLS,
poly_trait.span,
&format!("this bound is already specified as the supertrait of `{implied_by}`"),
|diag| {
// If we suggest removing a bound, we may also need to extend the span
// 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) {
poly_trait.span.to(next_bound.span().shrink_to_lo())
} else if index > 0
&& let Some(prev_bound) = opaque_ty.bounds.get(index - 1)
{
prev_bound.span().shrink_to_hi().to(poly_trait.span.shrink_to_hi())
} else {
poly_trait.span
};
let mut sugg = vec![(implied_span_extended, String::new())];
// We also might need to include associated type binding that were specified in the implied bound,
// but omitted in the implied-by bound:
// `fn f() -> impl Deref<Target = u8> + DerefMut`
// If we're going to suggest removing `Deref<..>`, we'll need to put `<Target = u8>` on `DerefMut`
let omitted_assoc_tys: Vec<_> = implied_bindings
.iter()
.filter(|binding| !implied_by_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) {
([.., 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(),
};
let mut associated_tys_sugg = if needs_angle_brackets {
"<".to_owned()
} else {
// If angle brackets aren't needed (i.e., there are already generic arguments or bindings),
// we need to add a comma:
// `impl A<B, C >`
// ^ if we insert `Assoc=i32` without a comma here, that'd be invalid syntax:
// `impl A<B, C Assoc=i32>`
", ".to_owned()
};
for (index, binding) in omitted_assoc_tys.into_iter().enumerate() {
if index > 0 {
associated_tys_sugg += ", ";
}
associated_tys_sugg += &snippet(cx, binding.span, "..");
}
if needs_angle_brackets {
associated_tys_sugg += ">";
}
sugg.push((insert_span, associated_tys_sugg));
}
diag.multipart_suggestion_with_style(
"try removing this bound",
sugg,
Applicability::MachineApplicable,
SuggestionStyle::ShowAlways,
);
},
);
}
/// Tries to "resolve" a type.
/// The index passed to this function must start with `Self=0`, i.e. it must be a valid
/// type parameter index.
/// If the index is out of bounds, it means that the generic parameter has a default type.
fn try_resolve_type<'tcx>(
tcx: TyCtxt<'tcx>,
args: &'tcx [GenericArg<'tcx>],
generics: &'tcx Generics,
index: usize,
) -> Option<Ty<'tcx>> {
match args.get(index - 1) {
Some(GenericArg::Type(ty)) => Some(hir_ty_to_ty(tcx, ty)),
Some(_) => None,
None => Some(tcx.type_of(generics.params[index].def_id).skip_binder()),
}
}
/// This function tries to, for all generic type parameters in a supertrait predicate `trait ...<U>:
/// GenericTrait<U>`, check if the substituted type in the implied-by bound matches with what's
/// subtituted in the implied bound.
///
/// Consider this example.
/// ```rust,ignore
/// trait GenericTrait<T> {}
/// trait GenericSubTrait<T, U, V>: GenericTrait<U> {}
/// ^ trait_predicate_args: [Self#0, U#2]
/// ^^^^^^^^^^^^^^^ trait_predicate_args: [Self#0, U#2]
/// (the Self#0 is implicit: `<Self as GenericTrait<U>>`)
/// impl GenericTrait<i32> for () {}
/// impl GenericSubTrait<(), i32, ()> for () {}
/// impl GenericSubTrait<(), [u8; 8], ()> for () {}
/// impl GenericSubTrait<(), i64, ()> for () {}
///
/// fn f() -> impl GenericTrait<i32> + GenericSubTrait<(), [u8; 8], ()> {
/// ^^^ implied_args ^^^^^^^^^^^^^^^ implied_by_args
/// (we are interested in `[u8; 8]` specifically, as that
/// fn f() -> impl GenericTrait<i32> + GenericSubTrait<(), i64, ()> {
/// ^^^ implied_args ^^^^^^^^^^^ implied_by_args
/// (we are interested in `i64` specifically, as that
/// is what `U` in `GenericTrait<U>` is substituted with)
/// ()
/// }
/// ```
/// Here i32 != [u8; 8], so this will return false.
fn is_same_generics(
tcx: TyCtxt<'_>,
trait_predicate_args: &[ty::GenericArg<'_>],
implied_by_args: &[GenericArg<'_>],
implied_args: &[GenericArg<'_>],
/// Here i32 != i64, so this will return false.
fn is_same_generics<'tcx>(
tcx: TyCtxt<'tcx>,
trait_predicate_args: &'tcx [ty::GenericArg<'tcx>],
implied_by_args: &'tcx [GenericArg<'tcx>],
implied_args: &'tcx [GenericArg<'tcx>],
implied_by_def_id: DefId,
implied_def_id: DefId,
) -> bool {
// Get the generics of the two traits to be able to get default generic parameter.
let implied_by_generics = tcx.generics_of(implied_by_def_id);
let implied_generics = tcx.generics_of(implied_def_id);
trait_predicate_args
.iter()
.enumerate()
.skip(1) // skip `Self` implicit arg
.all(|(arg_index, arg)| {
if let Some(ty) = arg.as_type()
&& let &ty::Param(ty::ParamTy{ index, .. }) = ty.kind()
// Since `trait_predicate_args` and type params in traits start with `Self=0`
// and generic argument lists `GenericTrait<i32>` don't have `Self`,
// we need to subtract 1 from the index.
&& let GenericArg::Type(ty_a) = implied_by_args[index as usize - 1]
&& let GenericArg::Type(ty_b) = implied_args[arg_index - 1]
if let Some(ty) = arg.as_type() {
if let &ty::Param(ty::ParamTy { index, .. }) = ty.kind()
// `index == 0` means that it's referring to `Self`,
// in which case we don't try to substitute it
&& index != 0
&& let Some(ty_a) = try_resolve_type(tcx, implied_by_args, implied_by_generics, index as usize)
&& let Some(ty_b) = try_resolve_type(tcx, implied_args, implied_generics, arg_index)
{
hir_ty_to_ty(tcx, ty_a) == hir_ty_to_ty(tcx, ty_b)
ty_a == ty_b
} else if let Some(ty_b) = try_resolve_type(tcx, implied_args, implied_generics, arg_index) {
ty == ty_b
} else {
false
}
} else {
false
}
@ -121,7 +238,7 @@ fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
&& 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.args.map_or([].as_slice(), |a| a.args), predicates))
Some((bound.span(), path, predicates, trait_def_id))
} else {
None
}
@ -134,43 +251,42 @@ fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
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_bounds.iter().find_map(|&(span, implied_by_args, preds)| {
&& 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)
&& is_same_generics(
cx.tcx,
tr.trait_ref.args,
implied_by_args,
implied_args,
implied_by_def_id,
def_id,
)
{
Some(span)
Some((span, implied_by_args, implied_by_bindings))
} else {
None
}
})
})
{
let implied_by = snippet(cx, implied_by_span, "..");
span_lint_and_then(
cx, IMPLIED_BOUNDS_IN_IMPLS,
poly_trait.span,
&format!("this bound is already specified as the supertrait of `{implied_by}`"),
|diag| {
// If we suggest removing a bound, we may also need extend the span
// to include the `+` token, so we don't end up with something like `impl + B`
let implied_span_extended = if let Some(next_bound) = opaque_ty.bounds.get(index + 1) {
poly_trait.span.to(next_bound.span().shrink_to_lo())
} else {
poly_trait.span
};
diag.span_suggestion_with_style(
implied_span_extended,
"try removing this bound",
"",
Applicability::MachineApplicable,
SuggestionStyle::ShowAlways
);
}
emit_lint(
cx,
poly_trait,
opaque_ty,
index,
implied_bindings,
implied_by_bindings,
implied_by_args,
implied_by_span
);
}
}

View file

@ -156,7 +156,6 @@ mod implicit_saturating_add;
mod implicit_saturating_sub;
mod implied_bounds_in_impls;
mod inconsistent_struct_constructor;
mod incorrect_impls;
mod index_refutable_slice;
mod indexing_slicing;
mod infinite_iter;
@ -212,6 +211,7 @@ mod misc;
mod misc_early;
mod mismatching_type_param_order;
mod missing_assert_message;
mod missing_asserts_for_indexing;
mod missing_const_for_fn;
mod missing_doc;
mod missing_enforced_import_rename;
@ -245,6 +245,7 @@ mod neg_multiply;
mod new_without_default;
mod no_effect;
mod no_mangle_with_rust_abi;
mod non_canonical_impls;
mod non_copy_const;
mod non_expressive_names;
mod non_octal_unix_permissions;
@ -697,7 +698,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
});
store.register_late_pass(|_| Box::<shadow::Shadow>::default());
store.register_late_pass(|_| Box::new(unit_types::UnitTypes));
store.register_late_pass(move |_| Box::new(loops::Loops::new(msrv())));
let enforce_iter_loop_reborrow = conf.enforce_iter_loop_reborrow;
store.register_late_pass(move |_| Box::new(loops::Loops::new(msrv(), enforce_iter_loop_reborrow)));
store.register_late_pass(|_| Box::<main_recursion::MainRecursion>::default());
store.register_late_pass(|_| Box::new(lifetimes::Lifetimes));
store.register_late_pass(|_| Box::new(entry::HashMapPass));
@ -1070,7 +1072,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
avoid_breaking_exported_api,
))
});
store.register_late_pass(|_| Box::new(incorrect_impls::IncorrectImpls));
store.register_late_pass(|_| Box::new(non_canonical_impls::NonCanonicalImpls));
store.register_late_pass(move |_| {
Box::new(single_call_fn::SingleCallFn {
avoid_breaking_exported_api,
@ -1101,6 +1103,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(ignored_unit_patterns::IgnoredUnitPatterns));
store.register_late_pass(|_| Box::<reserve_after_initialization::ReserveAfterInitialization>::default());
store.register_late_pass(|_| Box::new(implied_bounds_in_impls::ImpliedBoundsInImpls));
store.register_late_pass(|_| Box::new(missing_asserts_for_indexing::MissingAssertsForIndexing));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -13,8 +13,14 @@ use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMut
use rustc_middle::ty::{self, EarlyBinder, Ty, TypeAndMut};
use rustc_span::sym;
pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<'_>, msrv: &Msrv) {
let Some((adjust, ty)) = is_ref_iterable(cx, self_arg, call_expr) else {
pub(super) fn check(
cx: &LateContext<'_>,
self_arg: &Expr<'_>,
call_expr: &Expr<'_>,
msrv: &Msrv,
enforce_iter_loop_reborrow: bool,
) {
let Some((adjust, ty)) = is_ref_iterable(cx, self_arg, call_expr, enforce_iter_loop_reborrow) else {
return;
};
if let ty::Array(_, count) = *ty.peel_refs().kind() {
@ -102,6 +108,7 @@ fn is_ref_iterable<'tcx>(
cx: &LateContext<'tcx>,
self_arg: &Expr<'_>,
call_expr: &Expr<'_>,
enforce_iter_loop_reborrow: bool,
) -> Option<(AdjustKind, Ty<'tcx>)> {
let typeck = cx.typeck_results();
if let Some(trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
@ -142,7 +149,8 @@ fn is_ref_iterable<'tcx>(
{
return Some((AdjustKind::None, self_ty));
}
} else if let ty::Ref(region, ty, Mutability::Mut) = *self_ty.kind()
} else if enforce_iter_loop_reborrow
&& let ty::Ref(region, ty, Mutability::Mut) = *self_ty.kind()
&& let Some(mutbl) = mutbl
{
// Attempt to reborrow the mutable reference
@ -186,7 +194,8 @@ fn is_ref_iterable<'tcx>(
},
..
] => {
if target != self_ty
if enforce_iter_loop_reborrow
&& target != self_ty
&& implements_trait(cx, target, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [target])

View file

@ -609,10 +609,14 @@ declare_clippy_lint! {
pub struct Loops {
msrv: Msrv,
enforce_iter_loop_reborrow: bool,
}
impl Loops {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
pub fn new(msrv: Msrv, enforce_iter_loop_reborrow: bool) -> Self {
Self {
msrv,
enforce_iter_loop_reborrow,
}
}
}
impl_lint_pass!(Loops => [
@ -719,7 +723,7 @@ impl Loops {
if let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind {
match method.ident.as_str() {
"iter" | "iter_mut" => {
explicit_iter_loop::check(cx, self_arg, arg, &self.msrv);
explicit_iter_loop::check(cx, self_arg, arg, &self.msrv, self.enforce_iter_loop_reborrow);
},
"into_iter" => {
explicit_into_iter_loop::check(cx, self_arg, arg);

View file

@ -1,13 +1,13 @@
use super::utils::make_iterator_snippet;
use super::NEVER_LOOP;
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::ForLoop;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::source::snippet;
use rustc_errors::Applicability;
use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind};
use rustc_lint::LateContext;
use rustc_span::Span;
use rustc_span::{sym, Span};
use std::iter::{once, Iterator};
pub(super) fn check<'tcx>(
@ -18,7 +18,7 @@ pub(super) fn check<'tcx>(
for_loop: Option<&ForLoop<'_>>,
) {
match never_loop_block(cx, block, &mut Vec::new(), loop_id) {
NeverLoopResult::AlwaysBreak => {
NeverLoopResult::Diverging => {
span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
if let Some(ForLoop {
arg: iterator,
@ -39,67 +39,76 @@ pub(super) fn check<'tcx>(
}
});
},
NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (),
NeverLoopResult::IgnoreUntilEnd(_) => unreachable!(),
NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Normal => (),
}
}
/// The `never_loop` analysis keeps track of three things:
///
/// * Has any (reachable) code path hit a `continue` of the main loop?
/// * Is the current code path diverging (that is, the next expression is not reachable)
/// * For each block label `'a` inside the main loop, has any (reachable) code path encountered a
/// `break 'a`?
///
/// The first two bits of information are in this enum, and the last part is in the
/// `local_labels` variable, which contains a list of `(block_id, reachable)` pairs ordered by
/// scope.
#[derive(Copy, Clone)]
enum NeverLoopResult {
// A break/return always get triggered but not necessarily for the main loop.
AlwaysBreak,
// A continue may occur for the main loop.
/// A continue may occur for the main loop.
MayContinueMainLoop,
// Ignore everything until the end of the block with this id
IgnoreUntilEnd(HirId),
Otherwise,
/// We have not encountered any main loop continue,
/// but we are diverging (subsequent control flow is not reachable)
Diverging,
/// We have not encountered any main loop continue,
/// and subsequent control flow is (possibly) reachable
Normal,
}
#[must_use]
fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult {
match arg {
NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise,
NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal,
NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
NeverLoopResult::IgnoreUntilEnd(id) => NeverLoopResult::IgnoreUntilEnd(id),
}
}
// Combine two results for parts that are called in order.
#[must_use]
fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult {
fn combine_seq(first: NeverLoopResult, second: impl FnOnce() -> NeverLoopResult) -> NeverLoopResult {
match first {
NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop | NeverLoopResult::IgnoreUntilEnd(_) => {
first
},
NeverLoopResult::Otherwise => second,
NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop => first,
NeverLoopResult::Normal => second(),
}
}
// Combine an iterator of results for parts that are called in order.
#[must_use]
fn combine_seq_many(iter: impl IntoIterator<Item = NeverLoopResult>) -> NeverLoopResult {
for e in iter {
if let NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop = e {
return e;
}
}
NeverLoopResult::Normal
}
// Combine two results where only one of the part may have been executed.
#[must_use]
fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirId]) -> NeverLoopResult {
fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult {
match (b1, b2) {
(NeverLoopResult::IgnoreUntilEnd(a), NeverLoopResult::IgnoreUntilEnd(b)) => {
if ignore_ids.iter().find(|&e| e == &a || e == &b).unwrap() == &a {
NeverLoopResult::IgnoreUntilEnd(b)
} else {
NeverLoopResult::IgnoreUntilEnd(a)
}
},
(i @ NeverLoopResult::IgnoreUntilEnd(_), NeverLoopResult::AlwaysBreak)
| (NeverLoopResult::AlwaysBreak, i @ NeverLoopResult::IgnoreUntilEnd(_)) => i,
(NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
(NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
NeverLoopResult::MayContinueMainLoop
},
(NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
(NeverLoopResult::Normal, _) | (_, NeverLoopResult::Normal) => NeverLoopResult::Normal,
(NeverLoopResult::Diverging, NeverLoopResult::Diverging) => NeverLoopResult::Diverging,
}
}
fn never_loop_block<'tcx>(
cx: &LateContext<'tcx>,
block: &Block<'tcx>,
ignore_ids: &mut Vec<HirId>,
local_labels: &mut Vec<(HirId, bool)>,
main_loop_id: HirId,
) -> NeverLoopResult {
let iter = block
@ -107,15 +116,21 @@ fn never_loop_block<'tcx>(
.iter()
.filter_map(stmt_to_expr)
.chain(block.expr.map(|expr| (expr, None)));
iter.map(|(e, els)| {
let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
combine_seq_many(iter.map(|(e, els)| {
let e = never_loop_expr(cx, e, local_labels, main_loop_id);
// els is an else block in a let...else binding
els.map_or(e, |els| {
combine_branches(e, never_loop_block(cx, els, ignore_ids, main_loop_id), ignore_ids)
combine_seq(e, || match never_loop_block(cx, els, local_labels, main_loop_id) {
// Returning MayContinueMainLoop here means that
// we will not evaluate the rest of the body
NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
// An else block always diverges, so the Normal case should not happen,
// but the analysis is approximate so it might return Normal anyway.
// Returning Normal here says that nothing more happens on the main path
NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal,
})
})
.fold(NeverLoopResult::Otherwise, combine_seq)
}))
}
fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'tcx Block<'tcx>>)> {
@ -131,76 +146,69 @@ fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'t
fn never_loop_expr<'tcx>(
cx: &LateContext<'tcx>,
expr: &Expr<'tcx>,
ignore_ids: &mut Vec<HirId>,
local_labels: &mut Vec<(HirId, bool)>,
main_loop_id: HirId,
) -> NeverLoopResult {
match expr.kind {
let result = match expr.kind {
ExprKind::Unary(_, e)
| ExprKind::Cast(e, _)
| ExprKind::Type(e, _)
| ExprKind::Field(e, _)
| ExprKind::AddrOf(_, _, e)
| ExprKind::Repeat(e, _)
| ExprKind::DropTemps(e) => never_loop_expr(cx, e, ignore_ids, main_loop_id),
ExprKind::Let(let_expr) => never_loop_expr(cx, let_expr.init, ignore_ids, main_loop_id),
ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(cx, &mut es.iter(), ignore_ids, main_loop_id),
| ExprKind::DropTemps(e) => never_loop_expr(cx, e, local_labels, main_loop_id),
ExprKind::Let(let_expr) => never_loop_expr(cx, let_expr.init, local_labels, main_loop_id),
ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(cx, es.iter(), local_labels, main_loop_id),
ExprKind::MethodCall(_, receiver, es, _) => never_loop_expr_all(
cx,
&mut std::iter::once(receiver).chain(es.iter()),
ignore_ids,
std::iter::once(receiver).chain(es.iter()),
local_labels,
main_loop_id,
),
ExprKind::Struct(_, fields, base) => {
let fields = never_loop_expr_all(cx, &mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id);
let fields = never_loop_expr_all(cx, fields.iter().map(|f| f.expr), local_labels, main_loop_id);
if let Some(base) = base {
combine_seq(fields, never_loop_expr(cx, base, ignore_ids, main_loop_id))
combine_seq(fields, || never_loop_expr(cx, base, local_labels, main_loop_id))
} else {
fields
}
},
ExprKind::Call(e, es) => never_loop_expr_all(cx, &mut once(e).chain(es.iter()), ignore_ids, main_loop_id),
ExprKind::Call(e, es) => never_loop_expr_all(cx, once(e).chain(es.iter()), local_labels, main_loop_id),
ExprKind::Binary(_, e1, e2)
| ExprKind::Assign(e1, e2, _)
| ExprKind::AssignOp(_, e1, e2)
| ExprKind::Index(e1, e2, _) => {
never_loop_expr_all(cx, &mut [e1, e2].iter().copied(), ignore_ids, main_loop_id)
},
| ExprKind::Index(e1, e2, _) => never_loop_expr_all(cx, [e1, e2].iter().copied(), local_labels, main_loop_id),
ExprKind::Loop(b, _, _, _) => {
// Break can come from the inner loop so remove them.
absorb_break(never_loop_block(cx, b, ignore_ids, main_loop_id))
// We don't attempt to track reachability after a loop,
// just assume there may have been a break somewhere
absorb_break(never_loop_block(cx, b, local_labels, main_loop_id))
},
ExprKind::If(e, e2, e3) => {
let e1 = never_loop_expr(cx, e, ignore_ids, main_loop_id);
let e2 = never_loop_expr(cx, e2, ignore_ids, main_loop_id);
// If we know the `if` condition evaluates to `true`, don't check everything past it; it
// should just return whatever's evaluated for `e1` and `e2` since `e3` is unreachable
if let Some(Constant::Bool(true)) = constant(cx, cx.typeck_results(), e) {
return combine_seq(e1, e2);
}
let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
never_loop_expr(cx, e, ignore_ids, main_loop_id)
let e1 = never_loop_expr(cx, e, local_labels, main_loop_id);
combine_seq(e1, || {
let e2 = never_loop_expr(cx, e2, local_labels, main_loop_id);
let e3 = e3.as_ref().map_or(NeverLoopResult::Normal, |e| {
never_loop_expr(cx, e, local_labels, main_loop_id)
});
combine_seq(e1, combine_branches(e2, e3, ignore_ids))
combine_branches(e2, e3)
})
},
ExprKind::Match(e, arms, _) => {
let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
if arms.is_empty() {
e
} else {
let arms = never_loop_expr_branch(cx, &mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id);
combine_seq(e, arms)
}
let e = never_loop_expr(cx, e, local_labels, main_loop_id);
combine_seq(e, || {
arms.iter().fold(NeverLoopResult::Diverging, |a, b| {
combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id))
})
})
},
ExprKind::Block(b, l) => {
if l.is_some() {
ignore_ids.push(b.hir_id);
}
let ret = never_loop_block(cx, b, ignore_ids, main_loop_id);
if l.is_some() {
ignore_ids.pop();
local_labels.push((b.hir_id, false));
}
let ret = never_loop_block(cx, b, local_labels, main_loop_id);
let jumped_to = l.is_some() && local_labels.pop().unwrap().1;
match ret {
NeverLoopResult::IgnoreUntilEnd(a) if a == b.hir_id => NeverLoopResult::Otherwise,
NeverLoopResult::Diverging if jumped_to => NeverLoopResult::Normal,
_ => ret,
}
},
@ -211,74 +219,78 @@ fn never_loop_expr<'tcx>(
if id == main_loop_id {
NeverLoopResult::MayContinueMainLoop
} else {
NeverLoopResult::AlwaysBreak
NeverLoopResult::Diverging
}
},
ExprKind::Break(_, e) | ExprKind::Ret(e) => {
let first = e.as_ref().map_or(NeverLoopResult::Normal, |e| {
never_loop_expr(cx, e, local_labels, main_loop_id)
});
combine_seq(first, || {
// checks if break targets a block instead of a loop
ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
.map_or(NeverLoopResult::IgnoreUntilEnd(t), |e| {
never_loop_expr(cx, e, ignore_ids, main_loop_id)
if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind {
if let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t) {
*reachable = true;
}
}
NeverLoopResult::Diverging
})
},
ExprKind::Become(e) => combine_seq(never_loop_expr(cx, e, local_labels, main_loop_id), || {
NeverLoopResult::Diverging
}),
ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
combine_seq(
never_loop_expr(cx, e, ignore_ids, main_loop_id),
NeverLoopResult::AlwaysBreak,
)
}),
ExprKind::Become(e) => combine_seq(
never_loop_expr(cx, e, ignore_ids, main_loop_id),
NeverLoopResult::AlwaysBreak,
),
ExprKind::InlineAsm(asm) => asm
.operands
.iter()
.map(|(o, _)| match o {
ExprKind::InlineAsm(asm) => combine_seq_many(asm.operands.iter().map(|(o, _)| match o {
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
never_loop_expr(cx, expr, ignore_ids, main_loop_id)
never_loop_expr(cx, expr, local_labels, main_loop_id)
},
InlineAsmOperand::Out { expr, .. } => {
never_loop_expr_all(cx, &mut expr.iter().copied(), ignore_ids, main_loop_id)
never_loop_expr_all(cx, expr.iter().copied(), local_labels, main_loop_id)
},
InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
cx,
&mut once(*in_expr).chain(out_expr.iter().copied()),
ignore_ids,
once(*in_expr).chain(out_expr.iter().copied()),
local_labels,
main_loop_id,
),
InlineAsmOperand::Const { .. }
| InlineAsmOperand::SymFn { .. }
| InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise,
})
.fold(NeverLoopResult::Otherwise, combine_seq),
InlineAsmOperand::Const { .. } | InlineAsmOperand::SymFn { .. } | InlineAsmOperand::SymStatic { .. } => {
NeverLoopResult::Normal
},
})),
ExprKind::OffsetOf(_, _)
| ExprKind::Yield(_, _)
| ExprKind::Closure { .. }
| ExprKind::Path(_)
| ExprKind::ConstBlock(_)
| ExprKind::Lit(_)
| ExprKind::Err(_) => NeverLoopResult::Otherwise,
| ExprKind::Err(_) => NeverLoopResult::Normal,
};
let result = combine_seq(result, || {
if cx.typeck_results().expr_ty(expr).is_never() {
NeverLoopResult::Diverging
} else {
NeverLoopResult::Normal
}
});
if let NeverLoopResult::Diverging = result &&
let Some(macro_call) = root_macro_call_first_node(cx, expr) &&
let Some(sym::todo_macro) = cx.tcx.get_diagnostic_name(macro_call.def_id)
{
// We return MayContinueMainLoop here because we treat `todo!()`
// as potentially containing any code, including a continue of the main loop.
// This effectively silences the lint whenever a loop contains this macro anywhere.
NeverLoopResult::MayContinueMainLoop
} else {
result
}
}
fn never_loop_expr_all<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
cx: &LateContext<'tcx>,
es: &mut T,
ignore_ids: &mut Vec<HirId>,
es: T,
local_labels: &mut Vec<(HirId, bool)>,
main_loop_id: HirId,
) -> NeverLoopResult {
es.map(|e| never_loop_expr(cx, e, ignore_ids, main_loop_id))
.fold(NeverLoopResult::Otherwise, combine_seq)
}
fn never_loop_expr_branch<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
cx: &LateContext<'tcx>,
e: &mut T,
ignore_ids: &mut Vec<HirId>,
main_loop_id: HirId,
) -> NeverLoopResult {
e.fold(NeverLoopResult::AlwaysBreak, |a, b| {
combine_branches(a, never_loop_expr(cx, b, ignore_ids, main_loop_id), ignore_ids)
})
combine_seq_many(es.map(|e| never_loop_expr(cx, e, local_labels, main_loop_id)))
}
fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>) -> String {

View file

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
use rustc_ast::LitKind;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
@ -6,6 +7,7 @@ use rustc_hir::{Expr, ExprKind, PatKind, RangeEnd, UnOp};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{Span, DUMMY_SP};
declare_clippy_lint! {
/// ### What it does
@ -49,6 +51,29 @@ fn expr_as_i128(expr: &Expr<'_>) -> Option<i128> {
}
}
#[derive(Copy, Clone)]
struct Num {
val: i128,
span: Span,
}
impl Num {
fn new(expr: &Expr<'_>) -> Option<Self> {
Some(Self {
val: expr_as_i128(expr)?,
span: expr.span,
})
}
fn dummy(val: i128) -> Self {
Self { val, span: DUMMY_SP }
}
fn min(self, other: Self) -> Self {
if self.val < other.val { self } else { other }
}
}
impl LateLintPass<'_> for ManualRangePatterns {
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &'_ rustc_hir::Pat<'_>) {
if in_external_macro(cx.sess(), pat.span) {
@ -56,41 +81,48 @@ impl LateLintPass<'_> for ManualRangePatterns {
}
// a pattern like 1 | 2 seems fine, lint if there are at least 3 alternatives
// or at least one range
if let PatKind::Or(pats) = pat.kind
&& pats.len() >= 3
&& (pats.len() >= 3 || pats.iter().any(|p| matches!(p.kind, PatKind::Range(..))))
{
let mut min = i128::MAX;
let mut max = i128::MIN;
let mut min = Num::dummy(i128::MAX);
let mut max = Num::dummy(i128::MIN);
let mut range_kind = RangeEnd::Included;
let mut numbers_found = FxHashSet::default();
let mut ranges_found = Vec::new();
for pat in pats {
if let PatKind::Lit(lit) = pat.kind
&& let Some(num) = expr_as_i128(lit)
&& let Some(num) = Num::new(lit)
{
numbers_found.insert(num);
numbers_found.insert(num.val);
min = min.min(num);
max = max.max(num);
if num.val >= max.val {
max = num;
range_kind = RangeEnd::Included;
}
} else if let PatKind::Range(Some(left), Some(right), end) = pat.kind
&& let Some(left) = expr_as_i128(left)
&& let Some(right) = expr_as_i128(right)
&& right >= left
&& let Some(left) = Num::new(left)
&& let Some(mut right) = Num::new(right)
{
if let RangeEnd::Excluded = end {
right.val -= 1;
}
min = min.min(left);
max = max.max(right);
ranges_found.push(left..=match end {
RangeEnd::Included => right,
RangeEnd::Excluded => right - 1,
});
if right.val > max.val {
max = right;
range_kind = end;
}
ranges_found.push(left.val..=right.val);
} else {
return;
}
}
let contains_whole_range = 'contains: {
let mut num = min;
while num <= max {
let mut num = min.val;
while num <= max.val {
if numbers_found.contains(&num) {
num += 1;
}
@ -104,23 +136,28 @@ impl LateLintPass<'_> for ManualRangePatterns {
{
num = range.end() + 1;
} else {
break 'contains false;
return;
}
}
break 'contains true;
};
if contains_whole_range {
span_lint_and_sugg(
span_lint_and_then(
cx,
MANUAL_RANGE_PATTERNS,
pat.span,
"this OR pattern can be rewritten using a range",
|diag| {
if let Some(min) = snippet_opt(cx, min.span)
&& let Some(max) = snippet_opt(cx, max.span)
{
diag.span_suggestion(
pat.span,
"try",
format!("{min}..={max}"),
format!("{min}{range_kind}{max}"),
Applicability::MachineApplicable,
);
}
},
);
}
}
}
}

View file

@ -0,0 +1,106 @@
use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::higher::VecArgs;
use clippy_utils::{expr_or_init, is_trait_method, match_def_path, paths};
use rustc_ast::LitKind;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self};
use rustc_span::sym;
use super::ITER_OUT_OF_BOUNDS;
fn expr_as_u128(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<u128> {
if let ExprKind::Lit(lit) = expr_or_init(cx, e).kind
&& let LitKind::Int(n, _) = lit.node
{
Some(n)
} else {
None
}
}
/// Attempts to extract the length out of an iterator expression.
fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) -> Option<u128> {
let ty::Adt(adt, substs) = cx.typeck_results().expr_ty(iter).kind() else {
return None;
};
let did = adt.did();
if match_def_path(cx, did, &paths::ARRAY_INTO_ITER) {
// For array::IntoIter<T, const N: usize>, the length is the second generic
// parameter.
substs
.const_at(1)
.try_eval_target_usize(cx.tcx, cx.param_env)
.map(u128::from)
} else if match_def_path(cx, did, &paths::SLICE_ITER)
&& let ExprKind::MethodCall(_, recv, ..) = iter.kind
{
if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() {
// For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..)
len.try_eval_target_usize(cx.tcx, cx.param_env).map(u128::from)
} else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) {
match args {
VecArgs::Vec(vec) => vec.len().try_into().ok(),
VecArgs::Repeat(_, len) => expr_as_u128(cx, len),
}
} else {
None
}
} else if match_def_path(cx, did, &paths::ITER_EMPTY) {
Some(0)
} else if match_def_path(cx, did, &paths::ITER_ONCE) {
Some(1)
} else {
None
}
}
fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
recv: &'tcx Expr<'tcx>,
arg: &'tcx Expr<'tcx>,
message: &'static str,
note: &'static str,
) {
if is_trait_method(cx, expr, sym::Iterator)
&& let Some(len) = get_iterator_length(cx, recv)
&& let Some(skipped) = expr_as_u128(cx, arg)
&& skipped > len
{
span_lint_and_note(cx, ITER_OUT_OF_BOUNDS, expr.span, message, None, note);
}
}
pub(super) fn check_skip<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
recv: &'tcx Expr<'tcx>,
arg: &'tcx Expr<'tcx>,
) {
check(
cx,
expr,
recv,
arg,
"this `.skip()` call skips more items than the iterator will produce",
"this operation is useless and will create an empty iterator",
);
}
pub(super) fn check_take<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
recv: &'tcx Expr<'tcx>,
arg: &'tcx Expr<'tcx>,
) {
check(
cx,
expr,
recv,
arg,
"this `.take()` call takes more items than the iterator will produce",
"this operation is useless and the returned iterator will simply yield the same items",
);
}

View file

@ -21,7 +21,7 @@ pub(super) enum Op<'a> {
RmCloned,
// rm `.cloned()`
// e.g. `map` `for_each`
// e.g. `map` `for_each` `all` `any`
NeedlessMove(&'a str, &'a Expr<'a>),
// later `.cloned()`

View file

@ -43,6 +43,7 @@ mod iter_next_slice;
mod iter_nth;
mod iter_nth_zero;
mod iter_on_single_or_empty_collections;
mod iter_out_of_bounds;
mod iter_overeager_cloned;
mod iter_skip_next;
mod iter_skip_zero;
@ -3054,12 +3055,12 @@ declare_clippy_lint! {
///
/// ### Example
/// ```rust
/// vec!(1, 2, 3, 4, 5).resize(0, 5)
/// vec![1, 2, 3, 4, 5].resize(0, 5)
/// ```
///
/// Use instead:
/// ```rust
/// vec!(1, 2, 3, 4, 5).clear()
/// vec![1, 2, 3, 4, 5].clear()
/// ```
#[clippy::version = "1.46.0"]
pub VEC_RESIZE_TO_ZERO,
@ -3538,6 +3539,30 @@ declare_clippy_lint! {
"acquiring a write lock when a read lock would work"
}
declare_clippy_lint! {
/// ### What it does
/// Looks for iterator combinator calls such as `.take(x)` or `.skip(x)`
/// where `x` is greater than the amount of items that an iterator will produce.
///
/// ### Why is this bad?
/// Taking or skipping more items than there are in an iterator either creates an iterator
/// with all items from the original iterator or an iterator with no items at all.
/// This is most likely not what the user intended to do.
///
/// ### Example
/// ```rust
/// for _ in [1, 2, 3].iter().take(4) {}
/// ```
/// Use instead:
/// ```rust
/// for _ in [1, 2, 3].iter() {}
/// ```
#[clippy::version = "1.74.0"]
pub ITER_OUT_OF_BOUNDS,
suspicious,
"calls to `.take()` or `.skip()` that are out of bounds"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
@ -3676,7 +3701,8 @@ impl_lint_pass!(Methods => [
STRING_LIT_CHARS_ANY,
ITER_SKIP_ZERO,
FILTER_MAP_BOOL_THEN,
READONLY_WRITE_LOCK
READONLY_WRITE_LOCK,
ITER_OUT_OF_BOUNDS,
]);
/// Extracts a method call name, args, and `Span` of the method name.
@ -3873,6 +3899,12 @@ impl Methods {
("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => {
zst_offset::check(cx, expr, recv);
},
("all", [arg]) => {
if let Some(("cloned", recv2, [], _, _)) = method_call(recv) {
iter_overeager_cloned::check(cx, expr, recv, recv2,
iter_overeager_cloned::Op::NeedlessMove(name, arg), false);
}
}
("and_then", [arg]) => {
let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, recv, arg);
let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, recv, arg);
@ -3880,13 +3912,17 @@ impl Methods {
unnecessary_lazy_eval::check(cx, expr, recv, arg, "and");
}
},
("any", [arg]) if let ExprKind::Closure(arg) = arg.kind
("any", [arg]) => {
match method_call(recv) {
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::NeedlessMove(name, arg), false),
Some(("chars", recv, _, _, _)) if let ExprKind::Closure(arg) = arg.kind
&& let body = cx.tcx.hir().body(arg.body)
&& let [param] = body.params
&& let Some(("chars", recv, _, _, _)) = method_call(recv) =>
{
&& let [param] = body.params => {
string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
}
_ => {}
}
}
("arg", [arg]) => {
suspicious_command_arg_space::check(cx, recv, arg, span);
}
@ -4136,6 +4172,7 @@ impl Methods {
},
("skip", [arg]) => {
iter_skip_zero::check(cx, expr, arg);
iter_out_of_bounds::check_skip(cx, expr, recv, arg);
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
iter_overeager_cloned::check(cx, expr, recv, recv2,
@ -4163,7 +4200,8 @@ impl Methods {
}
},
("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
("take", [_arg]) => {
("take", [arg]) => {
iter_out_of_bounds::check_take(cx, expr, recv, arg);
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
iter_overeager_cloned::check(cx, expr, recv, recv2,
iter_overeager_cloned::Op::LaterCloned, false);

View file

@ -0,0 +1,391 @@
use std::mem;
use std::ops::ControlFlow;
use clippy_utils::comparisons::{normalize_comparison, Rel};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{eq_expr_value, hash_expr, higher};
use rustc_ast::{LitKind, RangeLimits};
use rustc_data_structures::unhash::UnhashMap;
use rustc_errors::{Applicability, Diagnostic};
use rustc_hir::{BinOp, Block, Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;
use rustc_span::{sym, Span};
declare_clippy_lint! {
/// ### What it does
/// Checks for repeated slice indexing without asserting beforehand that the length
/// is greater than the largest index used to index into the slice.
///
/// ### Why is this bad?
/// In the general case where the compiler does not have a lot of information
/// about the length of a slice, indexing it repeatedly will generate a bounds check
/// for every single index.
///
/// Asserting that the length of the slice is at least as large as the largest value
/// to index beforehand gives the compiler enough information to elide the bounds checks,
/// effectively reducing the number of bounds checks from however many times
/// the slice was indexed to just one (the assert).
///
/// ### Drawbacks
/// False positives. It is, in general, very difficult to predict how well
/// the optimizer will be able to elide bounds checks and it very much depends on
/// the surrounding code. For example, indexing into the slice yielded by the
/// [`slice::chunks_exact`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.chunks_exact)
/// iterator will likely have all of the bounds checks elided even without an assert
/// if the `chunk_size` is a constant.
///
/// Asserts are not tracked across function calls. Asserting the length of a slice
/// in a different function likely gives the optimizer enough information
/// about the length of a slice, but this lint will not detect that.
///
/// ### Example
/// ```rust
/// fn sum(v: &[u8]) -> u8 {
/// // 4 bounds checks
/// v[0] + v[1] + v[2] + v[3]
/// }
/// ```
/// Use instead:
/// ```rust
/// fn sum(v: &[u8]) -> u8 {
/// assert!(v.len() > 4);
/// // no bounds checks
/// v[0] + v[1] + v[2] + v[3]
/// }
/// ```
#[clippy::version = "1.70.0"]
pub MISSING_ASSERTS_FOR_INDEXING,
restriction,
"indexing into a slice multiple times without an `assert`"
}
declare_lint_pass!(MissingAssertsForIndexing => [MISSING_ASSERTS_FOR_INDEXING]);
fn report_lint<F>(cx: &LateContext<'_>, full_span: Span, msg: &str, indexes: &[Span], f: F)
where
F: FnOnce(&mut Diagnostic),
{
span_lint_and_then(cx, MISSING_ASSERTS_FOR_INDEXING, full_span, msg, |diag| {
f(diag);
for span in indexes {
diag.span_note(*span, "slice indexed here");
}
diag.note("asserting the length before indexing will elide bounds checks");
});
}
#[derive(Copy, Clone, Debug)]
enum LengthComparison {
/// `v.len() < 5`
LengthLessThanInt,
/// `5 < v.len()`
IntLessThanLength,
/// `v.len() <= 5`
LengthLessThanOrEqualInt,
/// `5 <= v.len()`
IntLessThanOrEqualLength,
}
/// Extracts parts out of a length comparison expression.
///
/// E.g. for `v.len() > 5` this returns `Some((LengthComparison::IntLessThanLength, 5, `v.len()`))`
fn len_comparison<'hir>(
bin_op: BinOp,
left: &'hir Expr<'hir>,
right: &'hir Expr<'hir>,
) -> Option<(LengthComparison, usize, &'hir Expr<'hir>)> {
macro_rules! int_lit_pat {
($id:ident) => {
ExprKind::Lit(Spanned {
node: LitKind::Int($id, _),
..
})
};
}
// normalize comparison, `v.len() > 4` becomes `4 < v.len()`
// this simplifies the logic a bit
let (op, left, right) = normalize_comparison(bin_op.node, left, right)?;
match (op, &left.kind, &right.kind) {
(Rel::Lt, int_lit_pat!(left), _) => Some((LengthComparison::IntLessThanLength, *left as usize, right)),
(Rel::Lt, _, int_lit_pat!(right)) => Some((LengthComparison::LengthLessThanInt, *right as usize, left)),
(Rel::Le, int_lit_pat!(left), _) => Some((LengthComparison::IntLessThanOrEqualLength, *left as usize, right)),
(Rel::Le, _, int_lit_pat!(right)) => Some((LengthComparison::LengthLessThanOrEqualInt, *right as usize, left)),
_ => None,
}
}
/// Attempts to extract parts out of an `assert!`-like expression
/// in the form `assert!(some_slice.len() > 5)`.
///
/// `assert!` has expanded to an if expression at the HIR, so this
/// actually works not just with `assert!` specifically, but anything
/// that has a never type expression in the `then` block (e.g. `panic!`).
fn assert_len_expr<'hir>(
cx: &LateContext<'_>,
expr: &'hir Expr<'hir>,
) -> Option<(LengthComparison, usize, &'hir Expr<'hir>)> {
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr)
&& let ExprKind::Unary(UnOp::Not, condition) = &cond.kind
&& let ExprKind::Binary(bin_op, left, right) = &condition.kind
&& let Some((cmp, asserted_len, slice_len)) = len_comparison(*bin_op, left, right)
&& let ExprKind::MethodCall(method, recv, ..) = &slice_len.kind
&& cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice()
&& method.ident.name == sym::len
// check if `then` block has a never type expression
&& let ExprKind::Block(Block { expr: Some(then_expr), .. }, _) = then.kind
&& cx.typeck_results().expr_ty(then_expr).is_never()
{
Some((cmp, asserted_len, recv))
} else {
None
}
}
#[derive(Debug)]
enum IndexEntry<'hir> {
/// `assert!` without any indexing (so far)
StrayAssert {
asserted_len: usize,
comparison: LengthComparison,
assert_span: Span,
slice: &'hir Expr<'hir>,
},
/// `assert!` with indexing
///
/// We also store the highest index to be able to check
/// if the `assert!` asserts the right length.
AssertWithIndex {
highest_index: usize,
asserted_len: usize,
assert_span: Span,
slice: &'hir Expr<'hir>,
indexes: Vec<Span>,
comparison: LengthComparison,
},
/// Indexing without an `assert!`
IndexWithoutAssert {
highest_index: usize,
indexes: Vec<Span>,
slice: &'hir Expr<'hir>,
},
}
impl<'hir> IndexEntry<'hir> {
pub fn slice(&self) -> &'hir Expr<'hir> {
match self {
IndexEntry::StrayAssert { slice, .. }
| IndexEntry::AssertWithIndex { slice, .. }
| IndexEntry::IndexWithoutAssert { slice, .. } => slice,
}
}
pub fn index_spans(&self) -> Option<&[Span]> {
match self {
IndexEntry::StrayAssert { .. } => None,
IndexEntry::AssertWithIndex { indexes, .. } | IndexEntry::IndexWithoutAssert { indexes, .. } => {
Some(indexes)
},
}
}
}
/// Extracts the upper index of a slice indexing expression.
///
/// E.g. for `5` this returns `Some(5)`, for `..5` this returns `Some(4)`,
/// for `..=5` this returns `Some(5)`
fn upper_index_expr(expr: &Expr<'_>) -> Option<usize> {
if let ExprKind::Lit(lit) = &expr.kind && let LitKind::Int(index, _) = lit.node {
Some(index as usize)
} else if let Some(higher::Range { end: Some(end), limits, .. }) = higher::Range::hir(expr)
&& let ExprKind::Lit(lit) = &end.kind
&& let LitKind::Int(index @ 1.., _) = lit.node
{
match limits {
RangeLimits::HalfOpen => Some(index as usize - 1),
RangeLimits::Closed => Some(index as usize),
}
} else {
None
}
}
/// Checks if the expression is an index into a slice and adds it to `indexes`
fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnhashMap<u64, Vec<IndexEntry<'hir>>>) {
if let ExprKind::Index(slice, index_lit, _) = expr.kind
&& cx.typeck_results().expr_ty_adjusted(slice).peel_refs().is_slice()
&& let Some(index) = upper_index_expr(index_lit)
{
let hash = hash_expr(cx, slice);
let indexes = map.entry(hash).or_default();
let entry = indexes.iter_mut().find(|entry| eq_expr_value(cx, entry.slice(), slice));
if let Some(entry) = entry {
match entry {
IndexEntry::StrayAssert { asserted_len, comparison, assert_span, slice } => {
*entry = IndexEntry::AssertWithIndex {
highest_index: index,
asserted_len: *asserted_len,
assert_span: *assert_span,
slice,
indexes: vec![expr.span],
comparison: *comparison,
};
},
IndexEntry::IndexWithoutAssert { highest_index, indexes, .. }
| IndexEntry::AssertWithIndex { highest_index, indexes, .. } => {
indexes.push(expr.span);
*highest_index = (*highest_index).max(index);
},
}
} else {
indexes.push(IndexEntry::IndexWithoutAssert {
highest_index: index,
indexes: vec![expr.span],
slice,
});
}
}
}
/// Checks if the expression is an `assert!` expression and adds it to `asserts`
fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnhashMap<u64, Vec<IndexEntry<'hir>>>) {
if let Some((comparison, asserted_len, slice)) = assert_len_expr(cx, expr) {
let hash = hash_expr(cx, slice);
let indexes = map.entry(hash).or_default();
let entry = indexes.iter_mut().find(|entry| eq_expr_value(cx, entry.slice(), slice));
if let Some(entry) = entry {
if let IndexEntry::IndexWithoutAssert {
highest_index,
indexes,
slice,
} = entry
{
*entry = IndexEntry::AssertWithIndex {
highest_index: *highest_index,
indexes: mem::take(indexes),
slice,
assert_span: expr.span,
comparison,
asserted_len,
};
}
} else {
indexes.push(IndexEntry::StrayAssert {
asserted_len,
comparison,
assert_span: expr.span,
slice,
});
}
}
}
/// Inspects indexes and reports lints.
///
/// Called at the end of this lint after all indexing and `assert!` expressions have been collected.
fn report_indexes(cx: &LateContext<'_>, map: &UnhashMap<u64, Vec<IndexEntry<'_>>>) {
for bucket in map.values() {
for entry in bucket {
let Some(full_span) = entry
.index_spans()
.and_then(|spans| spans.first().zip(spans.last()))
.map(|(low, &high)| low.to(high))
else {
continue;
};
match entry {
IndexEntry::AssertWithIndex {
highest_index,
asserted_len,
indexes,
comparison,
assert_span,
slice,
} if indexes.len() > 1 => {
// if we have found an `assert!`, let's also check that it's actually right
// and if it convers the highest index and if not, suggest the correct length
let sugg = match comparison {
// `v.len() < 5` and `v.len() <= 5` does nothing in terms of bounds checks.
// The user probably meant `v.len() > 5`
LengthComparison::LengthLessThanInt | LengthComparison::LengthLessThanOrEqualInt => Some(
format!("assert!({}.len() > {highest_index})", snippet(cx, slice.span, "..")),
),
// `5 < v.len()` == `v.len() > 5`
LengthComparison::IntLessThanLength if asserted_len < highest_index => Some(format!(
"assert!({}.len() > {highest_index})",
snippet(cx, slice.span, "..")
)),
// `5 <= v.len() == `v.len() >= 5`
LengthComparison::IntLessThanOrEqualLength if asserted_len <= highest_index => Some(format!(
"assert!({}.len() > {highest_index})",
snippet(cx, slice.span, "..")
)),
_ => None,
};
if let Some(sugg) = sugg {
report_lint(
cx,
full_span,
"indexing into a slice multiple times with an `assert` that does not cover the highest index",
indexes,
|diag| {
diag.span_suggestion(
*assert_span,
"provide the highest index that is indexed with",
sugg,
Applicability::MachineApplicable,
);
},
);
}
},
IndexEntry::IndexWithoutAssert {
indexes,
highest_index,
slice,
} if indexes.len() > 1 => {
// if there was no `assert!` but more than one index, suggest
// adding an `assert!` that covers the highest index
report_lint(
cx,
full_span,
"indexing into a slice multiple times without an `assert`",
indexes,
|diag| {
diag.help(format!(
"consider asserting the length before indexing: `assert!({}.len() > {highest_index});`",
snippet(cx, slice.span, "..")
));
},
);
},
_ => {},
}
}
}
}
impl LateLintPass<'_> for MissingAssertsForIndexing {
fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) {
let mut map = UnhashMap::default();
for_each_expr(block, |expr| {
check_index(cx, expr, &mut map);
check_assert(cx, expr, &mut map);
ControlFlow::<!, ()>::Continue(())
});
report_indexes(cx, &map);
}
}

View file

@ -13,11 +13,12 @@ use rustc_span::symbol::kw;
declare_clippy_lint! {
/// ### What it does
/// Checks for manual implementations of `Clone` when `Copy` is already implemented.
/// Checks for non-canonical implementations of `Clone` when `Copy` is already implemented.
///
/// ### Why is this bad?
/// If both `Clone` and `Copy` are implemented, they must agree. This is done by dereferencing
/// `self` in `Clone`'s implementation. Anything else is incorrect.
/// If both `Clone` and `Copy` are implemented, they must agree. This can done by dereferencing
/// `self` in `Clone`'s implementation, which will avoid any possibility of the implementations
/// becoming out of sync.
///
/// ### Example
/// ```rust,ignore
@ -46,14 +47,13 @@ declare_clippy_lint! {
/// impl Copy for A {}
/// ```
#[clippy::version = "1.72.0"]
pub INCORRECT_CLONE_IMPL_ON_COPY_TYPE,
correctness,
"manual implementation of `Clone` on a `Copy` type"
pub NON_CANONICAL_CLONE_IMPL,
suspicious,
"non-canonical implementation of `Clone` on a `Copy` type"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for manual implementations of both `PartialOrd` and `Ord` when only `Ord` is
/// necessary.
/// Checks for non-canonical implementations of `PartialOrd` when `Ord` is already implemented.
///
/// ### Why is this bad?
/// If both `PartialOrd` and `Ord` are implemented, they must agree. This is commonly done by
@ -61,11 +61,8 @@ declare_clippy_lint! {
/// introduce an error upon refactoring.
///
/// ### Known issues
/// Code that calls the `.into()` method instead will be flagged as incorrect, despite `.into()`
/// wrapping it in `Some`.
///
/// ### Limitations
/// Will not lint if `Self` and `Rhs` do not have the same type.
/// Code that calls the `.into()` method instead will be flagged, despite `.into()` wrapping it
/// in `Some`.
///
/// ### Example
/// ```rust
@ -107,13 +104,13 @@ declare_clippy_lint! {
/// }
/// ```
#[clippy::version = "1.72.0"]
pub INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE,
correctness,
"manual implementation of `PartialOrd` when `Ord` is already implemented"
pub NON_CANONICAL_PARTIAL_ORD_IMPL,
suspicious,
"non-canonical implementation of `PartialOrd` on an `Ord` type"
}
declare_lint_pass!(IncorrectImpls => [INCORRECT_CLONE_IMPL_ON_COPY_TYPE, INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE]);
declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL_PARTIAL_ORD_IMPL]);
impl LateLintPass<'_> for IncorrectImpls {
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 {
@ -154,9 +151,9 @@ impl LateLintPass<'_> for IncorrectImpls {
{} else {
span_lint_and_sugg(
cx,
INCORRECT_CLONE_IMPL_ON_COPY_TYPE,
NON_CANONICAL_CLONE_IMPL,
block.span,
"incorrect implementation of `clone` on a `Copy` type",
"non-canonical implementation of `clone` on a `Copy` type",
"change this to",
"{ *self }".to_owned(),
Applicability::MaybeIncorrect,
@ -169,9 +166,9 @@ impl LateLintPass<'_> for IncorrectImpls {
if impl_item.ident.name == sym::clone_from {
span_lint_and_sugg(
cx,
INCORRECT_CLONE_IMPL_ON_COPY_TYPE,
NON_CANONICAL_CLONE_IMPL,
impl_item.span,
"incorrect implementation of `clone_from` on a `Copy` type",
"unnecessary implementation of `clone_from` on a `Copy` type",
"remove it",
String::new(),
Applicability::MaybeIncorrect,
@ -222,9 +219,9 @@ impl LateLintPass<'_> for IncorrectImpls {
span_lint_and_then(
cx,
INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE,
NON_CANONICAL_PARTIAL_ORD_IMPL,
item.span,
"incorrect implementation of `partial_cmp` on an `Ord` type",
"non-canonical implementation of `partial_cmp` on an `Ord` type",
|diag| {
let [_, other] = body.params else {
return;

View file

@ -14,7 +14,12 @@ use {rustc_ast as ast, rustc_hir as hir};
const HARD_CODED_ALLOWED_BINARY: &[[&str; 2]] = &[["f32", "f32"], ["f64", "f64"], ["std::string::String", "str"]];
const HARD_CODED_ALLOWED_UNARY: &[&str] = &["f32", "f64", "std::num::Saturating", "std::num::Wrapping"];
const INTEGER_METHODS: &[Symbol] = &[sym::saturating_div, sym::wrapping_div, sym::wrapping_rem, sym::wrapping_rem_euclid];
const INTEGER_METHODS: &[Symbol] = &[
sym::saturating_div,
sym::wrapping_div,
sym::wrapping_rem,
sym::wrapping_rem_euclid,
];
#[derive(Debug)]
pub struct ArithmeticSideEffects {
@ -93,7 +98,14 @@ impl ArithmeticSideEffects {
let is_non_zero_u = |symbol: Option<Symbol>| {
matches!(
symbol,
Some(sym::NonZeroU128 | sym::NonZeroU16 | sym::NonZeroU32 | sym::NonZeroU64 | sym::NonZeroU8 | sym::NonZeroUsize)
Some(
sym::NonZeroU128
| sym::NonZeroU16
| sym::NonZeroU32
| sym::NonZeroU64
| sym::NonZeroU8
| sym::NonZeroUsize
)
)
};
let is_sat_or_wrap = |ty: Ty<'_>| {

View file

@ -17,7 +17,7 @@ pub(crate) fn check<'tcx>(
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
) {
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && is_float(cx, left) {
let left_is_local = match constant_with_source(cx, cx.typeck_results(), left) {
Some((c, s)) if !is_allowed(&c) => s.is_local(),
Some(_) => return,

View file

@ -1,7 +1,7 @@
use std::iter::once;
use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use rustc_ast::ast::{Expr, ExprKind};
use rustc_ast::token::LitKind;
@ -9,6 +9,7 @@ use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{BytePos, Pos, Span};
declare_clippy_lint! {
/// ### What it does
@ -76,15 +77,34 @@ impl EarlyLintPass for RawStrings {
}
if !str.contains(['\\', '"']) {
span_lint_and_sugg(
span_lint_and_then(
cx,
NEEDLESS_RAW_STRINGS,
expr.span,
"unnecessary raw string literal",
"try",
format!("{}\"{}\"", prefix.replace('r', ""), lit.symbol),
|diag| {
let (start, end) = hash_spans(expr.span, prefix, 0, max);
// BytePos: skip over the `b` in `br`, we checked the prefix appears in the source text
let r_pos = expr.span.lo() + BytePos::from_usize(prefix.len() - 1);
let start = start.with_lo(r_pos);
if end.is_empty() {
diag.span_suggestion(
start,
"use a string literal instead",
format!("\"{}\"", str),
Applicability::MachineApplicable,
);
} else {
diag.multipart_suggestion(
"try",
vec![(start, String::new()), (end, String::new())],
Applicability::MachineApplicable,
);
}
},
);
return;
}
@ -96,13 +116,6 @@ impl EarlyLintPass for RawStrings {
let num = str.as_bytes().iter().chain(once(&0)).try_fold(0u8, |acc, &b| {
match b {
b'"' if !following_quote => (following_quote, req) = (true, 1),
// I'm a bit surprised the compiler didn't optimize this out, there's no
// branch but it still ends up doing an unnecessary comparison, it's:
// - cmp r9b,1h
// - sbb cl,-1h
// which will add 1 if it's true. With this change, it becomes:
// - add cl,r9b
// isn't that so much nicer?
b'#' => req += u8::from(following_quote),
_ => {
if following_quote {
@ -126,18 +139,58 @@ impl EarlyLintPass for RawStrings {
};
if req < max {
let hashes = "#".repeat(req as usize);
span_lint_and_sugg(
span_lint_and_then(
cx,
NEEDLESS_RAW_STRING_HASHES,
expr.span,
"unnecessary hashes around raw string literal",
"try",
format!(r#"{prefix}{hashes}"{}"{hashes}"#, lit.symbol),
|diag| {
let (start, end) = hash_spans(expr.span, prefix, req, max);
let message = match max - req {
_ if req == 0 => "remove all the hashes around the literal".to_string(),
1 => "remove one hash from both sides of the literal".to_string(),
n => format!("remove {n} hashes from both sides of the literal"),
};
diag.multipart_suggestion(
message,
vec![(start, String::new()), (end, String::new())],
Applicability::MachineApplicable,
);
},
);
}
}
}
}
/// Returns spans pointing at the unneeded hashes, e.g. for a `req` of `1` and `max` of `3`:
///
/// ```ignore
/// r###".."###
/// ^^ ^^
/// ```
fn hash_spans(literal_span: Span, prefix: &str, req: u8, max: u8) -> (Span, Span) {
let literal_span = literal_span.data();
// BytePos: we checked prefix appears literally in the source text
let hash_start = literal_span.lo + BytePos::from_usize(prefix.len());
let hash_end = literal_span.hi;
// BytePos: req/max are counts of the ASCII character #
let start = Span::new(
hash_start + BytePos(req.into()),
hash_start + BytePos(max.into()),
literal_span.ctxt,
None,
);
let end = Span::new(
hash_end - BytePos(req.into()),
hash_end - BytePos(max.into()),
literal_span.ctxt,
None,
);
(start, end)
}

View file

@ -15,6 +15,8 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[
("clippy::eval_order_dependence", "clippy::mixed_read_write_in_expression"),
("clippy::identity_conversion", "clippy::useless_conversion"),
("clippy::if_let_some_result", "clippy::match_result_ok"),
("clippy::incorrect_clone_impl_on_copy_type", "clippy::non_canonical_clone_impl"),
("clippy::incorrect_partial_ord_impl_on_ord_type", "clippy::non_canonical_partial_ord_impl"),
("clippy::integer_arithmetic", "clippy::arithmetic_side_effects"),
("clippy::logic_bug", "clippy::overly_complex_bool_expr"),
("clippy::new_without_default_derive", "clippy::new_without_default"),
@ -38,12 +40,12 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[
("clippy::drop_bounds", "drop_bounds"),
("clippy::drop_copy", "dropping_copy_types"),
("clippy::drop_ref", "dropping_references"),
("clippy::fn_null_check", "useless_ptr_null_checks"),
("clippy::for_loop_over_option", "for_loops_over_fallibles"),
("clippy::for_loop_over_result", "for_loops_over_fallibles"),
("clippy::for_loops_over_fallibles", "for_loops_over_fallibles"),
("clippy::forget_copy", "forgetting_copy_types"),
("clippy::forget_ref", "forgetting_references"),
("clippy::fn_null_check", "useless_ptr_null_checks"),
("clippy::into_iter_on_array", "array_into_iter"),
("clippy::invalid_atomic_ordering", "invalid_atomic_ordering"),
("clippy::invalid_ref", "invalid_value"),

View file

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::root_macro_call;
use clippy_utils::sugg::Sugg;
use clippy_utils::{
get_enclosing_block, is_expr_path_def_path, is_integer_literal, is_path_diagnostic_item, path_to_local,
@ -148,6 +149,15 @@ impl SlowVectorInit {
/// - `Some(InitializedSize::Uninitialized)` for `Vec::new()`
/// - `None` for other, unrelated kinds of expressions
fn as_vec_initializer<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<InitializedSize<'tcx>> {
// Generally don't warn if the vec initializer comes from an expansion, except for the vec! macro.
// This lets us still warn on `vec![]`, while ignoring other kinds of macros that may output an
// empty vec
if expr.span.from_expansion()
&& root_macro_call(expr.span).map(|m| m.def_id) != cx.tcx.get_diagnostic_item(sym::vec_macro)
{
return None;
}
if let ExprKind::Call(func, [len_expr]) = expr.kind
&& is_expr_path_def_path(cx, func, &paths::VEC_WITH_CAPACITY)
{
@ -205,7 +215,7 @@ impl SlowVectorInit {
span_lint_and_then(cx, SLOW_VECTOR_INITIALIZATION, slow_fill.span, msg, |diag| {
diag.span_suggestion(
vec_alloc.allocation_expr.span,
vec_alloc.allocation_expr.span.source_callsite(),
"consider replacing this with",
format!("vec![0; {len_expr}]"),
Applicability::Unspecified,

View file

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::def_id::DefId;
use rustc_hir::{HirId, Path, PathSegment};
@ -99,17 +100,17 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
&& let Some(first_segment) = get_first_segment(path)
&& is_stable(cx, def_id)
{
let (lint, msg, help) = match first_segment.ident.name {
let (lint, used_mod, replace_with) = match first_segment.ident.name {
sym::std => match cx.tcx.crate_name(def_id.krate) {
sym::core => (
STD_INSTEAD_OF_CORE,
"used import from `std` instead of `core`",
"consider importing the item from `core`",
"std",
"core",
),
sym::alloc => (
STD_INSTEAD_OF_ALLOC,
"used import from `std` instead of `alloc`",
"consider importing the item from `alloc`",
"std",
"alloc",
),
_ => {
self.prev_span = path.span;
@ -120,8 +121,8 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
if cx.tcx.crate_name(def_id.krate) == sym::core {
(
ALLOC_INSTEAD_OF_CORE,
"used import from `alloc` instead of `core`",
"consider importing the item from `core`",
"alloc",
"core",
)
} else {
self.prev_span = path.span;
@ -131,7 +132,14 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
_ => return,
};
if path.span != self.prev_span {
span_lint_and_help(cx, lint, path.span, msg, None, help);
span_lint_and_sugg(
cx,
lint,
first_segment.ident.span,
&format!("used import from `{used_mod}` instead of `{replace_with}`"),
&format!("consider importing the item from `{replace_with}`"),
replace_with.to_string(),
Applicability::MachineApplicable);
self.prev_span = path.span;
}
}

View file

@ -341,44 +341,21 @@ fn block_parents_have_safety_comment(
id: hir::HirId,
) -> bool {
if let Some(node) = get_parent_node(cx.tcx, id) {
return match node {
Node::Expr(expr) => {
if let Some(
Node::Local(hir::Local { span, .. })
| Node::Item(hir::Item {
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,
..
}),
) = get_parent_node(cx.tcx, expr.hir_id)
{
let hir_id = match get_parent_node(cx.tcx, expr.hir_id) {
Some(Node::Local(hir::Local { hir_id, .. })) => *hir_id,
Some(Node::Item(hir::Item { owner_id, .. })) => {
cx.tcx.hir().local_def_id_to_hir_id(owner_id.def_id)
},
_ => unreachable!(),
};
// 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_in_body_has_safety_comment(
cx,
*span,
hir_id,
accept_comment_above_attributes,
)
} else {
!is_branchy(expr)
&& span_with_attrs_in_body_has_safety_comment(
cx,
expr.span,
expr.hir_id,
accept_comment_above_attributes,
)
})) => (*span, cx.tcx.hir().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:
@ -387,28 +364,27 @@ fn block_parents_have_safety_comment(
| hir::StmtKind::Semi(hir::Expr { span, hir_id, .. }),
..
})
| Node::Local(hir::Local { span, hir_id, .. }) => {
span_with_attrs_in_body_has_safety_comment(cx, *span, *hir_id, accept_comment_above_attributes)
},
| Node::Local(hir::Local { span, hir_id, .. }) => (*span, *hir_id),
Node::Item(hir::Item {
kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
span,
owner_id,
..
}) => span_with_attrs_in_body_has_safety_comment(
cx,
*span,
cx.tcx.hir().local_def_id_to_hir_id(owner_id.def_id),
accept_comment_above_attributes,
),
_ => false,
}) => (*span, cx.tcx.hir().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
}
}
/// Extends `span` to also include its attributes, then checks if that span has a safety comment.
fn span_with_attrs_in_body_has_safety_comment(
fn span_with_attrs_has_safety_comment(
cx: &LateContext<'_>,
span: Span,
hir_id: HirId,
@ -420,7 +396,7 @@ fn span_with_attrs_in_body_has_safety_comment(
span
};
span_in_body_has_safety_comment(cx, span)
span_has_safety_comment(cx, span)
}
/// Checks if an expression is "branchy", e.g. loop, match/if/etc.
@ -444,7 +420,7 @@ fn block_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
matches!(
span_from_macro_expansion_has_safety_comment(cx, span),
HasSafetyComment::Yes(_)
) || span_in_body_has_safety_comment(cx, span)
) || span_has_safety_comment(cx, span)
}
fn include_attrs_in_span(cx: &LateContext<'_>, hir_id: HirId, span: Span) -> Span {
@ -633,29 +609,36 @@ fn get_body_search_span(cx: &LateContext<'_>) -> Option<Span> {
let body = cx.enclosing_body?;
let map = cx.tcx.hir();
let mut span = map.body(body).value.span;
let mut maybe_global_var = false;
for (_, node) in map.parent_iter(body.hir_id) {
match node {
Node::Expr(e) => span = e.span,
Node::Block(_)
| Node::Arm(_)
| Node::Stmt(_)
| Node::Local(_)
| Node::Item(hir::Item {
Node::Block(_) | Node::Arm(_) | Node::Stmt(_) | Node::Local(_) => (),
Node::Item(hir::Item {
kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
..
}) => (),
}) => maybe_global_var = true,
Node::Item(hir::Item {
kind: hir::ItemKind::Mod(_),
span: item_span,
..
}) => {
span = *item_span;
break;
},
Node::Crate(mod_) if maybe_global_var => {
span = mod_.spans.inner_span;
},
_ => break,
}
}
Some(span)
}
fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
fn span_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
let source_map = cx.sess().source_map();
let ctxt = span.ctxt();
if ctxt == SyntaxContext::root()
&& let Some(search_span) = get_body_search_span(cx)
{
if ctxt.is_root() && let Some(search_span) = get_body_search_span(cx) {
if let Ok(unsafe_line) = source_map.lookup_line(span.lo())
&& let Some(body_span) = walk_span_to_context(search_span, SyntaxContext::root())
&& let Ok(body_line) = source_map.lookup_line(body_span.lo())

View file

@ -26,7 +26,7 @@ declare_clippy_lint! {
///
/// ### Example
/// ```rust
/// let mut twins = vec!((1, 1), (2, 2));
/// let mut twins = vec![(1, 1), (2, 2)];
/// twins.sort_by_key(|x| { x.1; });
/// ```
#[clippy::version = "1.47.0"]

View file

@ -1,15 +1,18 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::usage::is_potentially_mutated;
use clippy_utils::usage::is_potentially_local_place;
use clippy_utils::{higher, path_to_local};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, Visitor};
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, PathSegment, UnOp};
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Node, PathSegment, UnOp};
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceWithHirId};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::Ty;
use rustc_middle::mir::FakeReadCause;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::def_id::LocalDefId;
use rustc_span::source_map::Span;
@ -192,6 +195,55 @@ fn collect_unwrap_info<'tcx>(
Vec::new()
}
/// A HIR visitor delegate that checks if a local variable of type `Option<_>` is mutated,
/// *except* for if `Option::as_mut` is called.
/// The reason for why we allow that one specifically is that `.as_mut()` cannot change
/// the option to `None`, and that is important because this lint relies on the fact that
/// `is_some` + `unwrap` is equivalent to `if let Some(..) = ..`, which it would not be if
/// the option is changed to None between `is_some` and `unwrap`.
/// (And also `.as_mut()` is a somewhat common method that is still worth linting on.)
struct MutationVisitor<'tcx> {
is_mutated: bool,
local_id: HirId,
tcx: TyCtxt<'tcx>,
}
/// Checks if the parent of the expression pointed at by the given `HirId` is a call to
/// `Option::as_mut`.
///
/// Used by the mutation visitor to specifically allow `.as_mut()` calls.
/// In particular, the `HirId` that the visitor receives is the id of the local expression
/// (i.e. the `x` in `x.as_mut()`), and that is the reason for why we care about its parent
/// expression: that will be where the actual method call is.
fn is_option_as_mut_use(tcx: TyCtxt<'_>, expr_id: HirId) -> bool {
if let Node::Expr(mutating_expr) = tcx.hir().get_parent(expr_id)
&& let ExprKind::MethodCall(path, ..) = mutating_expr.kind
{
path.ident.name.as_str() == "as_mut"
} else {
false
}
}
impl<'tcx> Delegate<'tcx> for MutationVisitor<'tcx> {
fn borrow(&mut self, cat: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) {
if let ty::BorrowKind::MutBorrow = bk
&& is_potentially_local_place(self.local_id, &cat.place)
&& !is_option_as_mut_use(self.tcx, diag_expr_id)
{
self.is_mutated = true;
}
}
fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {
self.is_mutated = true;
}
fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
}
impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
fn visit_branch(
&mut self,
@ -202,10 +254,26 @@ impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
) {
let prev_len = self.unwrappables.len();
for unwrap_info in collect_unwrap_info(self.cx, if_expr, cond, branch, else_branch, true) {
if is_potentially_mutated(unwrap_info.local_id, cond, self.cx)
|| is_potentially_mutated(unwrap_info.local_id, branch, self.cx)
{
// if the variable is mutated, we don't know whether it can be unwrapped:
let mut delegate = MutationVisitor {
tcx: self.cx.tcx,
is_mutated: false,
local_id: unwrap_info.local_id,
};
let infcx = self.cx.tcx.infer_ctxt().build();
let mut vis = ExprUseVisitor::new(
&mut delegate,
&infcx,
cond.hir_id.owner.def_id,
self.cx.param_env,
self.cx.typeck_results(),
);
vis.walk_expr(cond);
vis.walk_expr(branch);
if delegate.is_mutated {
// if the variable is mutated, we don't know whether it can be unwrapped.
// it might have been changed to `None` in between `is_some` + `unwrap`.
continue;
}
self.unwrappables.push(unwrap_info);
@ -215,6 +283,27 @@ impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
}
}
enum AsRefKind {
AsRef,
AsMut,
}
/// Checks if the expression is a method call to `as_{ref,mut}` and returns the receiver of it.
/// If it isn't, the expression itself is returned.
fn consume_option_as_ref<'tcx>(expr: &'tcx Expr<'tcx>) -> (&'tcx Expr<'tcx>, Option<AsRefKind>) {
if let ExprKind::MethodCall(path, recv, ..) = expr.kind {
if path.ident.name == sym::as_ref {
(recv, Some(AsRefKind::AsRef))
} else if path.ident.name.as_str() == "as_mut" {
(recv, Some(AsRefKind::AsMut))
} else {
(expr, None)
}
} else {
(expr, None)
}
}
impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
@ -233,6 +322,7 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
// find `unwrap[_err]()` calls:
if_chain! {
if let ExprKind::MethodCall(method_name, self_arg, ..) = expr.kind;
let (self_arg, as_ref_kind) = consume_option_as_ref(self_arg);
if let Some(id) = path_to_local(self_arg);
if [sym::unwrap, sym::expect, sym!(unwrap_err)].contains(&method_name.ident.name);
let call_to_unwrap = [sym::unwrap, sym::expect].contains(&method_name.ident.name);
@ -268,7 +358,12 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
unwrappable.check.span.with_lo(unwrappable.if_expr.span.lo()),
"try",
format!(
"if let {suggested_pattern} = {unwrappable_variable_name}",
"if let {suggested_pattern} = {borrow_prefix}{unwrappable_variable_name}",
borrow_prefix = match as_ref_kind {
Some(AsRefKind::AsRef) => "&",
Some(AsRefKind::AsMut) => "&mut ",
None => "",
},
),
// We don't track how the unwrapped value is used inside the
// block or suggest deleting the unwrap, so we can't offer a

View file

@ -561,6 +561,26 @@ define_Conf! {
/// Which crates to allow absolute paths from
(absolute_paths_allowed_crates: rustc_data_structures::fx::FxHashSet<String> =
rustc_data_structures::fx::FxHashSet::default()),
/// Lint: EXPLICIT_ITER_LOOP
///
/// Whether to recommend using implicit into iter for reborrowed values.
///
/// #### Example
/// ```
/// let mut vec = vec![1, 2, 3];
/// let rmvec = &mut vec;
/// for _ in rmvec.iter() {}
/// for _ in rmvec.iter_mut() {}
/// ```
///
/// Use instead:
/// ```
/// let mut vec = vec![1, 2, 3];
/// let rmvec = &mut vec;
/// for _ in &*rmvec {}
/// for _ in &mut *rmvec {}
/// ```
(enforce_iter_loop_reborrow: bool = false),
}
/// Search for the configuration file.

View file

@ -5,6 +5,7 @@
#![feature(lint_reasons)]
#![feature(never_type)]
#![feature(rustc_private)]
#![feature(assert_matches)]
#![recursion_limit = "512"]
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
@ -110,6 +111,7 @@ use rustc_span::source_map::SourceMap;
use rustc_span::symbol::{kw, Ident, Symbol};
use rustc_span::{sym, Span};
use rustc_target::abi::Integer;
use visitors::Visitable;
use crate::consts::{constant, miri_to_const, Constant};
use crate::higher::Range;
@ -1286,7 +1288,7 @@ pub fn contains_name<'tcx>(name: Symbol, expr: &'tcx Expr<'_>, cx: &LateContext<
}
/// Returns `true` if `expr` contains a return expression
pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
pub fn contains_return<'tcx>(expr: impl Visitable<'tcx>) -> bool {
for_each_expr(expr, |e| {
if matches!(e.kind, hir::ExprKind::Ret(..)) {
ControlFlow::Break(())

View file

@ -49,6 +49,7 @@ pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];
pub const ITER_EMPTY: [&str; 5] = ["core", "iter", "sources", "empty", "Empty"];
pub const ITER_ONCE: [&str; 5] = ["core", "iter", "sources", "once", "Once"];
pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"];
#[cfg(feature = "internal")]
pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
@ -163,3 +164,4 @@ pub const DEBUG_STRUCT: [&str; 4] = ["core", "fmt", "builders", "DebugStruct"];
pub const ORD_CMP: [&str; 4] = ["core", "cmp", "Ord", "cmp"];
#[expect(clippy::invalid_paths)] // not sure why it thinks this, it works so
pub const BOOL_THEN: [&str; 4] = ["core", "bool", "<impl bool>", "then"];
pub const ARRAY_INTO_ITER: [&str; 4] = ["core", "array", "iter", "IntoIter"];

View file

@ -362,7 +362,7 @@ pub fn snippet_block_with_context<'a>(
}
/// Same as `snippet_with_applicability`, but first walks the span up to the given context. This
/// will result in the macro call, rather then the expansion, if the span is from a child context.
/// will result in the macro call, rather than the expansion, if the span is from a child context.
/// If the span is not from a child context, it will be used directly instead.
///
/// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR node

View file

@ -88,7 +88,7 @@ impl<'a> Sugg<'a> {
}
/// Same as `hir`, but first walks the span up to the given context. This will result in the
/// macro call, rather then the expansion, if the span is from a child context. If the span is
/// macro call, rather than the expansion, if the span is from a child context. If the span is
/// not from a child context, it will be used directly instead.
///
/// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR

View file

@ -27,6 +27,7 @@ use rustc_target::abi::{Size, VariantIdx};
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
use rustc_trait_selection::traits::{Obligation, ObligationCause};
use std::assert_matches::debug_assert_matches;
use std::iter;
use crate::{match_def_path, path_res, paths};
@ -259,7 +260,11 @@ pub fn implements_trait_with_env_from_iter<'tcx>(
})),
);
debug_assert_eq!(tcx.def_kind(trait_id), DefKind::Trait);
debug_assert_matches!(
tcx.def_kind(trait_id),
DefKind::Trait | DefKind::TraitAlias,
"`DefId` must belong to a trait or trait alias"
);
#[cfg(debug_assertions)]
assert_generic_args_match(tcx, trait_id, trait_ref.args);

View file

@ -150,7 +150,7 @@ fn generic_args_certainty(cx: &LateContext<'_>, args: &GenericArgs<'_>) -> Certa
}
/// Tries to tell whether a `QPath` resolves to something certain, e.g., whether all of its path
/// segments generic arguments are are instantiated.
/// segments generic arguments are instantiated.
///
/// `qpath` could refer to either a type or a value. The heuristic never needs the `DefId` of a
/// value. So `DefId`s are retained only when `resolves_to_type` is true.

View file

@ -4,7 +4,7 @@ use core::ops::ControlFlow;
use hir::def::Res;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{self as hir, Expr, ExprKind, HirId, HirIdSet};
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, Place, PlaceBase, PlaceWithHirId};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter;
@ -37,6 +37,17 @@ pub fn is_potentially_mutated<'tcx>(variable: HirId, expr: &'tcx Expr<'_>, cx: &
mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&variable))
}
pub fn is_potentially_local_place(local_id: HirId, place: &Place<'_>) -> bool {
match place.base {
PlaceBase::Local(id) => id == local_id,
PlaceBase::Upvar(_) => {
// Conservatively assume yes.
true
},
_ => false,
}
}
struct MutVarsDelegate {
used_mutably: HirIdSet,
skip: bool,

View file

@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2023-08-24"
channel = "nightly-2023-09-07"
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]

View file

@ -4,7 +4,7 @@
#![warn(rust_2018_idioms, unused_lifetimes)]
#![allow(unused_extern_crates)]
use ui_test::{status_emitter, Args, CommandBuilder, Config, Match, Mode, OutputConflictHandling, RustfixMode};
use ui_test::{status_emitter, Args, CommandBuilder, Config, Match, Mode, OutputConflictHandling};
use std::collections::BTreeMap;
use std::env::{self, set_var, var_os};
@ -114,36 +114,26 @@ fn canonicalize(path: impl AsRef<Path>) -> PathBuf {
}
fn base_config(test_dir: &str) -> (Config, Args) {
let bless = var_os("RUSTC_BLESS").is_some_and(|v| v != "0") || env::args().any(|arg| arg == "--bless");
let args = Args {
filters: env::var("TESTNAME")
.map(|filters| filters.split(',').map(str::to_string).collect())
.unwrap_or_default(),
quiet: false,
check: !bless,
threads: match std::env::var_os("RUST_TEST_THREADS") {
Some(n) => n.to_str().unwrap().parse().unwrap(),
None => std::thread::available_parallelism().unwrap(),
},
skip: Vec::new(),
};
let mut args = Args::test().unwrap();
args.bless |= var_os("RUSTC_BLESS").is_some_and(|v| v != "0");
let mut config = Config {
mode: Mode::Yolo {
rustfix: RustfixMode::Everything,
rustfix: ui_test::RustfixMode::Everything,
},
stderr_filters: vec![(Match::PathBackslash, b"/")],
stdout_filters: vec![],
output_conflict_handling: if bless {
OutputConflictHandling::Bless
} else {
OutputConflictHandling::Error("cargo uibless".into())
},
filter_files: env::var("TESTNAME")
.map(|filters| filters.split(',').map(str::to_string).collect())
.unwrap_or_default(),
target: None,
out_dir: canonicalize(var_os("CARGO_TARGET_DIR").unwrap_or_else(|| "target".into())).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();
}
let current_exe_path = env::current_exe().unwrap();
let deps_path = current_exe_path.parent().unwrap();
let profile_path = deps_path.parent().unwrap();
@ -186,7 +176,6 @@ fn run_ui() {
ui_test::run_tests_generic(
vec![config],
args,
ui_test::default_file_filter,
ui_test::default_per_file_config,
if quiet {
@ -211,7 +200,6 @@ fn run_internal_tests() {
ui_test::run_tests_generic(
vec![config],
args,
ui_test::default_file_filter,
ui_test::default_per_file_config,
if quiet {
@ -245,7 +233,6 @@ fn run_ui_toml() {
ui_test::run_tests_generic(
vec![config],
args,
ui_test::default_file_filter,
|config, path, _file_contents| {
config
@ -302,10 +289,16 @@ fn run_ui_cargo() {
let quiet = args.quiet;
let ignored_32bit = |path: &Path| {
// FIXME: for some reason the modules are linted in a different order for this test
cfg!(target_pointer_width = "32") && path.ends_with("tests/ui-cargo/module_style/fail_mod/Cargo.toml")
};
ui_test::run_tests_generic(
vec![config],
args,
|path, args, _config| path.ends_with("Cargo.toml") && ui_test::default_filter_by_arg(path, args),
|path, config| {
path.ends_with("Cargo.toml") && ui_test::default_any_file_filter(path, config) && !ignored_32bit(path)
},
|config, path, _file_contents| {
config.out_dir = canonicalize(
std::env::current_dir()

View file

@ -1,6 +1,7 @@
error: package `cargo_common_metadata_fail` is missing `package.description` metadata
|
= note: `-D clippy::cargo-common-metadata` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::cargo_common_metadata)]`
error: package `cargo_common_metadata_fail` is missing `either package.license or package.license_file` metadata

View file

@ -1,6 +1,7 @@
error: package `cargo_common_metadata_fail_publish` is missing `package.description` metadata
|
= note: `-D clippy::cargo-common-metadata` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::cargo_common_metadata)]`
error: package `cargo_common_metadata_fail_publish` is missing `either package.license or package.license_file` metadata

View file

@ -1,6 +1,7 @@
error: package `cargo_common_metadata_fail_publish_true` is missing `package.description` metadata
|
= note: `-D clippy::cargo-common-metadata` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::cargo_common_metadata)]`
error: package `cargo_common_metadata_fail_publish_true` is missing `either package.license or package.license_file` metadata

View file

@ -9,6 +9,7 @@ error: file is loaded as a module multiple times: `src/b.rs`
|
= help: replace all but one `mod` item with `use` items
= note: `-D clippy::duplicate-mod` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::duplicate_mod)]`
error: file is loaded as a module multiple times: `src/c.rs`
--> src/main.rs:9:1

View file

@ -2,6 +2,7 @@ error: the "no-" prefix in the feature name "no-qaq" is negative
|
= help: consider renaming the feature to "qaq", but make sure the feature adds functionality
= note: `-D clippy::negative-feature-names` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::negative_feature_names)]`
error: the "no_" prefix in the feature name "no_qaq" is negative
|
@ -19,6 +20,7 @@ error: the "-support" suffix in the feature name "qvq-support" is redundant
|
= help: consider renaming the feature to "qvq"
= note: `-D clippy::redundant-feature-names` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::redundant_feature_names)]`
error: the "_support" suffix in the feature name "qvq_support" is redundant
|

View file

@ -6,6 +6,7 @@ error: `mod.rs` files are required, found `src/bad/inner.rs`
|
= help: move `src/bad/inner.rs` to `src/bad/inner/mod.rs`
= note: `-D clippy::self-named-module-files` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::self_named_module_files)]`
error: `mod.rs` files are required, found `src/bad/inner/stuff.rs`
--> src/bad/inner/stuff.rs:1:1

View file

@ -6,5 +6,6 @@ error: `mod.rs` files are required, found `src/bad.rs`
|
= help: move `src/bad.rs` to `src/bad/mod.rs`
= note: `-D clippy::self-named-module-files` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::self_named_module_files)]`
error: could not compile `fail-mod-remap` (bin "fail-mod-remap") due to previous error

View file

@ -6,5 +6,6 @@ error: `mod.rs` files are not allowed, found `src/bad/mod.rs`
|
= help: move `src/bad/mod.rs` to `src/bad.rs`
= note: `-D clippy::mod-module-files` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::mod_module_files)]`
error: could not compile `fail-no-mod` (bin "fail-no-mod") due to previous error

View file

@ -1,5 +1,6 @@
error: multiple versions for dependency `winapi`: 0.2.8, 0.3.9
|
= note: `-D clippy::multiple-crate-versions` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::multiple_crate_versions)]`
error: could not compile `multiple_crate_versions` (bin "multiple_crate_versions") due to previous error

View file

@ -1,5 +1,6 @@
error: wildcard dependency for `regex`
|
= note: `-D clippy::wildcard-dependencies` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::wildcard_dependencies)]`
error: could not compile `wildcard_dependencies` (bin "wildcard_dependencies") due to previous error

View file

@ -6,6 +6,7 @@ LL | /// Check for lint formulations that are correct
|
= help: try using `Checks for` instead
= note: `-D clippy::almost-standard-lint-formulation` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::almost_standard_lint_formulation)]`
error: non-standard lint formulation
--> $DIR/check_formulation.rs:33:5

View file

@ -16,6 +16,7 @@ help: this `let` statement can also be in the `if_chain!`
LL | let x = "";
| ^^^^^^^^^^^
= note: `-D clippy::if-chain-style` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::if_chain_style)]`
error: `if a && b;` should be `if a; if b;`
--> $DIR/if_chain_style.rs:24:12

View file

@ -5,6 +5,7 @@ LL | pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::invalid-paths` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::invalid_paths)]`
error: invalid path
--> $DIR/invalid_paths.rs:18:5

View file

@ -6,6 +6,7 @@ LL | const DEREF_TRAIT: [&str; 4] = ["core", "ops", "deref", "Deref"];
|
= help: convert all references to use `sym::Deref`
= note: `-D clippy::unnecessary-def-path` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_def_path)]`
error: hardcoded path to a language item
--> $DIR/unnecessary_def_path_hardcoded_path.rs:11:40

View file

@ -30,3 +30,18 @@ macro_rules! item {
const ITEM: usize = 1;
};
}
#[macro_export]
macro_rules! binop {
($t:tt) => {
$t + $t
};
}
#[macro_export]
macro_rules! attr {
($i:item) => {
#[repr(C)]
$i
};
}

View file

@ -8,4 +8,6 @@ disallowed-macros = [
"macros::ty",
"macros::pat",
"macros::item",
"macros::binop",
"macros::attr",
]

View file

@ -20,11 +20,14 @@ fn main() {
let macros::pat!() = 1;
let _: macros::ty!() = "";
macros::item!();
let _ = macros::binop!(1);
eprintln!("allowed");
}
macros::attr! {
struct S;
}
impl S {
macros::item!();

View file

@ -63,23 +63,37 @@ error: use of a disallowed macro `macros::item`
LL | macros::item!();
| ^^^^^^^^^^^^^^^
error: use of a disallowed macro `macros::binop`
--> $DIR/disallowed_macros.rs:23:13
|
LL | let _ = macros::binop!(1);
| ^^^^^^^^^^^^^^^^^
error: use of a disallowed macro `macros::attr`
--> $DIR/disallowed_macros.rs:28:1
|
LL | / macros::attr! {
LL | | struct S;
LL | | }
| |_^
error: use of a disallowed macro `macros::item`
--> $DIR/disallowed_macros.rs:30:5
--> $DIR/disallowed_macros.rs:33:5
|
LL | macros::item!();
| ^^^^^^^^^^^^^^^
error: use of a disallowed macro `macros::item`
--> $DIR/disallowed_macros.rs:34:5
--> $DIR/disallowed_macros.rs:37:5
|
LL | macros::item!();
| ^^^^^^^^^^^^^^^
error: use of a disallowed macro `macros::item`
--> $DIR/disallowed_macros.rs:38:5
--> $DIR/disallowed_macros.rs:41:5
|
LL | macros::item!();
| ^^^^^^^^^^^^^^^
error: aborting due to 13 previous errors
error: aborting due to 15 previous errors

View file

@ -1,469 +0,0 @@
// NOTE: Copied from `ui/auxiliary/proc_macros.rs`, couldn't get `../` to work for some reason
#![feature(let_chains)]
#![feature(proc_macro_span)]
#![allow(clippy::excessive_nesting, dead_code)]
extern crate proc_macro;
use core::mem;
use proc_macro::token_stream::IntoIter;
use proc_macro::Delimiter::{self, Brace, Parenthesis};
use proc_macro::Spacing::{self, Alone, Joint};
use proc_macro::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree as TT};
type Result<T> = core::result::Result<T, TokenStream>;
/// Make a `compile_error!` pointing to the given span.
fn make_error(msg: &str, span: Span) -> TokenStream {
TokenStream::from_iter([
TT::Ident(Ident::new("compile_error", span)),
TT::Punct(punct_with_span('!', Alone, span)),
TT::Group({
let mut msg = Literal::string(msg);
msg.set_span(span);
group_with_span(Parenthesis, TokenStream::from_iter([TT::Literal(msg)]), span)
}),
])
}
fn expect_tt<T>(tt: Option<TT>, f: impl FnOnce(TT) -> Option<T>, expected: &str, span: Span) -> Result<T> {
match tt {
None => Err(make_error(
&format!("unexpected end of input, expected {expected}"),
span,
)),
Some(tt) => {
let span = tt.span();
match f(tt) {
Some(x) => Ok(x),
None => Err(make_error(&format!("unexpected token, expected {expected}"), span)),
}
},
}
}
fn punct_with_span(c: char, spacing: Spacing, span: Span) -> Punct {
let mut p = Punct::new(c, spacing);
p.set_span(span);
p
}
fn group_with_span(delimiter: Delimiter, stream: TokenStream, span: Span) -> Group {
let mut g = Group::new(delimiter, stream);
g.set_span(span);
g
}
/// Token used to escape the following token from the macro's span rules.
const ESCAPE_CHAR: char = '$';
/// Takes a single token followed by a sequence of tokens. Returns the sequence of tokens with their
/// span set to that of the first token. Tokens may be escaped with either `#ident` or `#(tokens)`.
#[proc_macro]
pub fn with_span(input: TokenStream) -> TokenStream {
let mut iter = input.into_iter();
let span = iter.next().unwrap().span();
let mut res = TokenStream::new();
if let Err(e) = write_with_span(span, iter, &mut res) {
e
} else {
res
}
}
/// Takes a sequence of tokens and return the tokens with the span set such that they appear to be
/// from an external macro. Tokens may be escaped with either `#ident` or `#(tokens)`.
#[proc_macro]
pub fn external(input: TokenStream) -> TokenStream {
let mut res = TokenStream::new();
if let Err(e) = write_with_span(Span::mixed_site(), input.into_iter(), &mut res) {
e
} else {
res
}
}
/// Copies all the tokens, replacing all their spans with the given span. Tokens can be escaped
/// either by `#ident` or `#(tokens)`.
fn write_with_span(s: Span, mut input: IntoIter, out: &mut TokenStream) -> Result<()> {
while let Some(tt) = input.next() {
match tt {
TT::Punct(p) if p.as_char() == ESCAPE_CHAR => {
expect_tt(
input.next(),
|tt| match tt {
tt @ (TT::Ident(_) | TT::Literal(_)) => {
out.extend([tt]);
Some(())
},
TT::Punct(mut p) if p.as_char() == ESCAPE_CHAR => {
p.set_span(s);
out.extend([TT::Punct(p)]);
Some(())
},
TT::Group(g) if g.delimiter() == Parenthesis => {
out.extend([TT::Group(group_with_span(Delimiter::None, g.stream(), g.span()))]);
Some(())
},
_ => None,
},
"an ident, a literal, or parenthesized tokens",
p.span(),
)?;
},
TT::Group(g) => {
let mut stream = TokenStream::new();
write_with_span(s, g.stream().into_iter(), &mut stream)?;
out.extend([TT::Group(group_with_span(g.delimiter(), stream, s))]);
},
mut tt => {
tt.set_span(s);
out.extend([tt]);
},
}
}
Ok(())
}
/// Within the item this attribute is attached to, an `inline!` macro is available which expands the
/// contained tokens as though they came from a macro expansion.
///
/// Within the `inline!` macro, any token preceded by `$` is passed as though it were an argument
/// with an automatically chosen fragment specifier. `$ident` will be passed as `ident`, `$1` or
/// `$"literal"` will be passed as `literal`, `$'lt` will be passed as `lifetime`, and `$(...)` will
/// pass the contained tokens as a `tt` sequence (the wrapping parenthesis are removed). If another
/// specifier is required it can be specified within parenthesis like `$(@expr ...)`. This will
/// expand the remaining tokens as a single argument.
///
/// Multiple `inline!` macros may be nested within each other. This will expand as nested macro
/// calls. However, any arguments will be passed as though they came from the outermost context.
#[proc_macro_attribute]
pub fn inline_macros(args: TokenStream, input: TokenStream) -> TokenStream {
let mut args = args.into_iter();
let mac_name = match args.next() {
Some(TT::Ident(name)) => Some(name),
Some(tt) => {
return make_error(
"unexpected argument, expected either an ident or no arguments",
tt.span(),
);
},
None => None,
};
if let Some(tt) = args.next() {
return make_error(
"unexpected argument, expected either an ident or no arguments",
tt.span(),
);
};
let mac_name = if let Some(mac_name) = mac_name {
Ident::new(&format!("__inline_mac_{mac_name}"), Span::call_site())
} else {
let mut input = match LookaheadIter::new(input.clone().into_iter()) {
Some(x) => x,
None => return input,
};
loop {
match input.next() {
None => break Ident::new("__inline_mac", Span::call_site()),
Some(TT::Ident(kind)) => match &*kind.to_string() {
"impl" => break Ident::new("__inline_mac_impl", Span::call_site()),
kind @ ("struct" | "enum" | "union" | "fn" | "mod" | "trait" | "type" | "const" | "static") => {
if let TT::Ident(name) = &input.tt {
break Ident::new(&format!("__inline_mac_{kind}_{name}"), Span::call_site());
} else {
break Ident::new(&format!("__inline_mac_{kind}"), Span::call_site());
}
},
_ => {},
},
_ => {},
}
}
};
let mut expander = Expander::default();
let mut mac = MacWriter::new(mac_name);
if let Err(e) = expander.expand(input.into_iter(), &mut mac) {
return e;
}
let mut out = TokenStream::new();
mac.finish(&mut out);
out.extend(expander.expn);
out
}
/// Wraps a `TokenStream` iterator with a single token lookahead.
struct LookaheadIter {
tt: TT,
iter: IntoIter,
}
impl LookaheadIter {
fn new(mut iter: IntoIter) -> Option<Self> {
iter.next().map(|tt| Self { tt, iter })
}
/// Get's the lookahead token, replacing it with the next token in the stream.
/// Note: If there isn't a next token, this will not return the lookahead token.
fn next(&mut self) -> Option<TT> {
self.iter.next().map(|tt| mem::replace(&mut self.tt, tt))
}
}
/// Builds the macro used to implement all the `inline!` macro calls.
struct MacWriter {
name: Ident,
macros: TokenStream,
next_idx: usize,
}
impl MacWriter {
fn new(name: Ident) -> Self {
Self {
name,
macros: TokenStream::new(),
next_idx: 0,
}
}
/// Inserts a new `inline!` call.
fn insert(&mut self, name_span: Span, bang_span: Span, body: Group, expander: &mut Expander) -> Result<()> {
let idx = self.next_idx;
self.next_idx += 1;
let mut inner = Expander::for_arm(idx);
inner.expand(body.stream().into_iter(), self)?;
let new_arm = inner.arm.unwrap();
self.macros.extend([
TT::Group(Group::new(Parenthesis, new_arm.args_def)),
TT::Punct(Punct::new('=', Joint)),
TT::Punct(Punct::new('>', Alone)),
TT::Group(Group::new(Parenthesis, inner.expn)),
TT::Punct(Punct::new(';', Alone)),
]);
expander.expn.extend([
TT::Ident({
let mut name = self.name.clone();
name.set_span(name_span);
name
}),
TT::Punct(punct_with_span('!', Alone, bang_span)),
]);
let mut call_body = TokenStream::from_iter([TT::Literal(Literal::usize_unsuffixed(idx))]);
if let Some(arm) = expander.arm.as_mut() {
if !new_arm.args.is_empty() {
arm.add_sub_args(new_arm.args, &mut call_body);
}
} else {
call_body.extend(new_arm.args);
}
let mut g = Group::new(body.delimiter(), call_body);
g.set_span(body.span());
expander.expn.extend([TT::Group(g)]);
Ok(())
}
/// Creates the macro definition.
fn finish(self, out: &mut TokenStream) {
if self.next_idx != 0 {
out.extend([
TT::Ident(Ident::new("macro_rules", Span::call_site())),
TT::Punct(Punct::new('!', Alone)),
TT::Ident(self.name),
TT::Group(Group::new(Brace, self.macros)),
])
}
}
}
struct MacroArm {
args_def: TokenStream,
args: Vec<TT>,
}
impl MacroArm {
fn add_single_arg_def(&mut self, kind: &str, dollar_span: Span, arg_span: Span, out: &mut TokenStream) {
let mut name = Ident::new(&format!("_{}", self.args.len()), Span::call_site());
self.args_def.extend([
TT::Punct(Punct::new('$', Alone)),
TT::Ident(name.clone()),
TT::Punct(Punct::new(':', Alone)),
TT::Ident(Ident::new(kind, Span::call_site())),
]);
name.set_span(arg_span);
out.extend([TT::Punct(punct_with_span('$', Alone, dollar_span)), TT::Ident(name)]);
}
fn add_parenthesized_arg_def(&mut self, kind: Ident, dollar_span: Span, arg_span: Span, out: &mut TokenStream) {
let mut name = Ident::new(&format!("_{}", self.args.len()), Span::call_site());
self.args_def.extend([TT::Group(Group::new(
Parenthesis,
TokenStream::from_iter([
TT::Punct(Punct::new('$', Alone)),
TT::Ident(name.clone()),
TT::Punct(Punct::new(':', Alone)),
TT::Ident(kind),
]),
))]);
name.set_span(arg_span);
out.extend([TT::Punct(punct_with_span('$', Alone, dollar_span)), TT::Ident(name)]);
}
fn add_multi_arg_def(&mut self, dollar_span: Span, arg_span: Span, out: &mut TokenStream) {
let mut name = Ident::new(&format!("_{}", self.args.len()), Span::call_site());
self.args_def.extend([TT::Group(Group::new(
Parenthesis,
TokenStream::from_iter([
TT::Punct(Punct::new('$', Alone)),
TT::Group(Group::new(
Parenthesis,
TokenStream::from_iter([
TT::Punct(Punct::new('$', Alone)),
TT::Ident(name.clone()),
TT::Punct(Punct::new(':', Alone)),
TT::Ident(Ident::new("tt", Span::call_site())),
]),
)),
TT::Punct(Punct::new('*', Alone)),
]),
))]);
name.set_span(arg_span);
out.extend([
TT::Punct(punct_with_span('$', Alone, dollar_span)),
TT::Group(group_with_span(
Parenthesis,
TokenStream::from_iter([TT::Punct(punct_with_span('$', Alone, dollar_span)), TT::Ident(name)]),
dollar_span,
)),
TT::Punct(punct_with_span('*', Alone, dollar_span)),
]);
}
fn add_arg(&mut self, dollar_span: Span, tt: TT, input: &mut IntoIter, out: &mut TokenStream) -> Result<()> {
match tt {
TT::Punct(p) if p.as_char() == ESCAPE_CHAR => out.extend([TT::Punct(p)]),
TT::Punct(p) if p.as_char() == '\'' && p.spacing() == Joint => {
let lt_name = expect_tt(
input.next(),
|tt| match tt {
TT::Ident(x) => Some(x),
_ => None,
},
"lifetime name",
p.span(),
)?;
let arg_span = p.span().join(lt_name.span()).unwrap_or(p.span());
self.add_single_arg_def("lifetime", dollar_span, arg_span, out);
self.args.extend([TT::Punct(p), TT::Ident(lt_name)]);
},
TT::Ident(x) => {
self.add_single_arg_def("ident", dollar_span, x.span(), out);
self.args.push(TT::Ident(x));
},
TT::Literal(x) => {
self.add_single_arg_def("literal", dollar_span, x.span(), out);
self.args.push(TT::Literal(x));
},
TT::Group(g) if g.delimiter() == Parenthesis => {
let mut inner = g.stream().into_iter();
if let Some(TT::Punct(p)) = inner.next()
&& p.as_char() == '@'
{
let kind = expect_tt(
inner.next(),
|tt| match tt {
TT::Ident(kind) => Some(kind),
_ => None,
},
"a macro fragment specifier",
p.span(),
)?;
self.add_parenthesized_arg_def(kind, dollar_span, g.span(), out);
self.args.push(TT::Group(group_with_span(Parenthesis, inner.collect(), g.span())))
} else {
self.add_multi_arg_def(dollar_span, g.span(), out);
self.args.push(TT::Group(g));
}
},
tt => return Err(make_error("unsupported escape", tt.span())),
};
Ok(())
}
fn add_sub_args(&mut self, args: Vec<TT>, out: &mut TokenStream) {
self.add_multi_arg_def(Span::call_site(), Span::call_site(), out);
self.args
.extend([TT::Group(Group::new(Parenthesis, TokenStream::from_iter(args)))]);
}
}
#[derive(Default)]
struct Expander {
arm: Option<MacroArm>,
expn: TokenStream,
}
impl Expander {
fn for_arm(idx: usize) -> Self {
Self {
arm: Some(MacroArm {
args_def: TokenStream::from_iter([TT::Literal(Literal::usize_unsuffixed(idx))]),
args: Vec::new(),
}),
expn: TokenStream::new(),
}
}
fn write_tt(&mut self, tt: TT, mac: &mut MacWriter) -> Result<()> {
match tt {
TT::Group(g) => {
let outer = mem::take(&mut self.expn);
self.expand(g.stream().into_iter(), mac)?;
let inner = mem::replace(&mut self.expn, outer);
self.expn
.extend([TT::Group(group_with_span(g.delimiter(), inner, g.span()))]);
},
tt => self.expn.extend([tt]),
}
Ok(())
}
fn expand(&mut self, input: IntoIter, mac: &mut MacWriter) -> Result<()> {
let Some(mut input) = LookaheadIter::new(input) else {
return Ok(());
};
while let Some(tt) = input.next() {
if let TT::Punct(p) = &tt
&& p.as_char() == ESCAPE_CHAR
&& let Some(arm) = self.arm.as_mut()
{
arm.add_arg(p.span(), mem::replace(&mut input.tt, tt), &mut input.iter, &mut self.expn)?;
if input.next().is_none() {
return Ok(());
}
} else if let TT::Punct(p) = &input.tt
&& p.as_char() == '!'
&& let TT::Ident(name) = &tt
&& name.to_string() == "inline"
{
let g = expect_tt(
input.iter.next(),
|tt| match tt {
TT::Group(g) => Some(g),
_ => None,
},
"macro arguments",
p.span(),
)?;
mac.insert(name.span(), p.span(), g, self)?;
if input.next().is_none() {
return Ok(());
}
} else {
self.write_tt(tt, mac)?;
}
}
self.write_tt(input.tt, mac)
}
}

View file

@ -1,4 +1,4 @@
//@aux-build:proc_macros.rs
//@aux-build:../../ui/auxiliary/proc_macros.rs
#![rustfmt::skip]
#![feature(custom_inner_attributes)]
#![allow(unused)]

View file

@ -28,6 +28,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
disallowed-types
doc-valid-idents
enable-raw-pointer-heuristic-for-send
enforce-iter-loop-reborrow
enforced-import-renames
enum-variant-name-threshold
enum-variant-size-threshold
@ -99,6 +100,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
disallowed-types
doc-valid-idents
enable-raw-pointer-heuristic-for-send
enforce-iter-loop-reborrow
enforced-import-renames
enum-variant-name-threshold
enum-variant-size-threshold

View file

@ -1,13 +0,0 @@
extern crate proc_macro;
use proc_macro::{Delimiter, Group, Ident, TokenStream, TokenTree};
#[proc_macro]
pub fn unsafe_block(input: TokenStream) -> TokenStream {
let span = input.into_iter().next().unwrap().span();
TokenStream::from_iter([TokenTree::Ident(Ident::new("unsafe", span)), {
let mut group = Group::new(Delimiter::Brace, TokenStream::new());
group.set_span(span);
TokenTree::Group(group)
}])
}

View file

@ -1,4 +1,4 @@
//@aux-build:proc_macro_unsafe.rs
//@aux-build:../../ui/auxiliary/proc_macro_unsafe.rs
#![warn(clippy::undocumented_unsafe_blocks, clippy::unnecessary_safety_comment)]
#![allow(deref_nullptr, clippy::let_unit_value, clippy::missing_safety_doc)]
@ -564,4 +564,18 @@ fn issue_8679<T: Copy>() {
unsafe {}
}
mod issue_11246 {
// Safety: foo
const _: () = unsafe {};
// Safety: A safety comment
const FOO: () = unsafe {};
// Safety: bar
static BAR: u8 = unsafe { 0 };
}
// Safety: Another safety comment
const FOO: () = unsafe {};
fn main() {}

View file

@ -1,6 +1,6 @@
#![allow(clippy::needless_if)]
#![warn(clippy::bool_comparison)]
#![allow(clippy::incorrect_partial_ord_impl_on_ord_type)]
#![allow(clippy::non_canonical_partial_ord_impl)]
fn main() {
let x = true;

View file

@ -1,6 +1,6 @@
#![allow(clippy::needless_if)]
#![warn(clippy::bool_comparison)]
#![allow(clippy::incorrect_partial_ord_impl_on_ord_type)]
#![allow(clippy::non_canonical_partial_ord_impl)]
fn main() {
let x = true;

View file

@ -1,44 +1,46 @@
error: casting `isize` to `i8` may truncate the value
--> $DIR/cast_size_32bit.rs:12:5
--> $DIR/cast_size.rs:15:5
|
LL | 1isize as i8;
| ^^^^^^^^^^^^
|
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
= note: `-D clippy::cast-possible-truncation` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::cast_possible_truncation)]`
help: ... or use `try_from` and handle the error accordingly
|
LL | i8::try_from(1isize);
| ~~~~~~~~~~~~~~~~~~~~
error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
--> $DIR/cast_size_32bit.rs:15:5
--> $DIR/cast_size.rs:18:5
|
LL | x0 as f64;
| ^^^^^^^^^
|
= note: `-D clippy::cast-precision-loss` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::cast_precision_loss)]`
error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
--> $DIR/cast_size_32bit.rs:16:5
--> $DIR/cast_size.rs:19:5
|
LL | x1 as f64;
| ^^^^^^^^^
error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
--> $DIR/cast_size_32bit.rs:17:5
--> $DIR/cast_size.rs:20:5
|
LL | x0 as f32;
| ^^^^^^^^^
error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
--> $DIR/cast_size_32bit.rs:18:5
--> $DIR/cast_size.rs:21:5
|
LL | x1 as f32;
| ^^^^^^^^^
error: casting `isize` to `i32` may truncate the value on targets with 64-bit wide pointers
--> $DIR/cast_size_32bit.rs:19:5
--> $DIR/cast_size.rs:22:5
|
LL | 1isize as i32;
| ^^^^^^^^^^^^^
@ -50,7 +52,7 @@ LL | i32::try_from(1isize);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `isize` to `u32` may truncate the value on targets with 64-bit wide pointers
--> $DIR/cast_size_32bit.rs:20:5
--> $DIR/cast_size.rs:23:5
|
LL | 1isize as u32;
| ^^^^^^^^^^^^^
@ -62,7 +64,7 @@ LL | u32::try_from(1isize);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers
--> $DIR/cast_size_32bit.rs:21:5
--> $DIR/cast_size.rs:24:5
|
LL | 1usize as u32;
| ^^^^^^^^^^^^^
@ -74,7 +76,7 @@ LL | u32::try_from(1usize);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers
--> $DIR/cast_size_32bit.rs:22:5
--> $DIR/cast_size.rs:25:5
|
LL | 1usize as i32;
| ^^^^^^^^^^^^^
@ -86,15 +88,16 @@ LL | i32::try_from(1usize);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers
--> $DIR/cast_size_32bit.rs:22:5
--> $DIR/cast_size.rs:25:5
|
LL | 1usize as i32;
| ^^^^^^^^^^^^^
|
= note: `-D clippy::cast-possible-wrap` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]`
error: casting `i64` to `isize` may truncate the value on targets with 32-bit wide pointers
--> $DIR/cast_size_32bit.rs:24:5
--> $DIR/cast_size.rs:26:5
|
LL | 1i64 as isize;
| ^^^^^^^^^^^^^
@ -106,7 +109,7 @@ LL | isize::try_from(1i64);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers
--> $DIR/cast_size_32bit.rs:25:5
--> $DIR/cast_size.rs:27:5
|
LL | 1i64 as usize;
| ^^^^^^^^^^^^^
@ -118,7 +121,7 @@ LL | usize::try_from(1i64);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers
--> $DIR/cast_size_32bit.rs:26:5
--> $DIR/cast_size.rs:28:5
|
LL | 1u64 as isize;
| ^^^^^^^^^^^^^
@ -130,13 +133,13 @@ LL | isize::try_from(1u64);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers
--> $DIR/cast_size_32bit.rs:26:5
--> $DIR/cast_size.rs:28:5
|
LL | 1u64 as isize;
| ^^^^^^^^^^^^^
error: casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers
--> $DIR/cast_size_32bit.rs:27:5
--> $DIR/cast_size.rs:29:5
|
LL | 1u64 as usize;
| ^^^^^^^^^^^^^
@ -148,24 +151,31 @@ LL | usize::try_from(1u64);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers
--> $DIR/cast_size_32bit.rs:28:5
--> $DIR/cast_size.rs:30:5
|
LL | 1u32 as isize;
| ^^^^^^^^^^^^^
error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)
--> $DIR/cast_size_32bit.rs:33:5
--> $DIR/cast_size.rs:35:5
|
LL | 999_999_999 as f32;
| ^^^^^^^^^^^^^^^^^^
error: casting integer literal to `f64` is unnecessary
--> $DIR/cast_size_32bit.rs:34:5
error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
--> $DIR/cast_size.rs:36:5
|
LL | 3_999_999_999usize as f64;
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `3_999_999_999_f64`
LL | 9_999_999_999_999_999usize as f64;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: literal out of range for `usize`
--> $DIR/cast_size.rs:36:5
|
= note: `-D clippy::unnecessary-cast` implied by `-D warnings`
LL | 9_999_999_999_999_999usize as f64;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: the literal `9_999_999_999_999_999usize` does not fit into the type `usize` whose range is `0..=4294967295`
= note: `#[deny(overflowing_literals)]` on by default
error: aborting due to 18 previous errors
error: aborting due to 19 previous errors

View file

@ -1,5 +1,5 @@
error: casting `isize` to `i8` may truncate the value
--> $DIR/cast_size.rs:12:5
--> $DIR/cast_size.rs:15:5
|
LL | 1isize as i8;
| ^^^^^^^^^^^^
@ -13,7 +13,7 @@ LL | i8::try_from(1isize);
| ~~~~~~~~~~~~~~~~~~~~
error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
--> $DIR/cast_size.rs:16:5
--> $DIR/cast_size.rs:18:5
|
LL | x0 as f64;
| ^^^^^^^^^
@ -28,19 +28,19 @@ LL | x1 as f64;
| ^^^^^^^^^
error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
--> $DIR/cast_size.rs:21:5
--> $DIR/cast_size.rs:20:5
|
LL | x0 as f32;
| ^^^^^^^^^
error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide)
--> $DIR/cast_size.rs:23:5
--> $DIR/cast_size.rs:21:5
|
LL | x1 as f32;
| ^^^^^^^^^
error: casting `isize` to `i32` may truncate the value on targets with 64-bit wide pointers
--> $DIR/cast_size.rs:25:5
--> $DIR/cast_size.rs:22:5
|
LL | 1isize as i32;
| ^^^^^^^^^^^^^
@ -52,7 +52,7 @@ LL | i32::try_from(1isize);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `isize` to `u32` may truncate the value on targets with 64-bit wide pointers
--> $DIR/cast_size.rs:27:5
--> $DIR/cast_size.rs:23:5
|
LL | 1isize as u32;
| ^^^^^^^^^^^^^
@ -64,7 +64,7 @@ LL | u32::try_from(1isize);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers
--> $DIR/cast_size.rs:29:5
--> $DIR/cast_size.rs:24:5
|
LL | 1usize as u32;
| ^^^^^^^^^^^^^
@ -76,7 +76,7 @@ LL | u32::try_from(1usize);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers
--> $DIR/cast_size.rs:31:5
--> $DIR/cast_size.rs:25:5
|
LL | 1usize as i32;
| ^^^^^^^^^^^^^
@ -88,7 +88,7 @@ LL | i32::try_from(1usize);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers
--> $DIR/cast_size.rs:31:5
--> $DIR/cast_size.rs:25:5
|
LL | 1usize as i32;
| ^^^^^^^^^^^^^
@ -97,7 +97,7 @@ LL | 1usize as i32;
= help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]`
error: casting `i64` to `isize` may truncate the value on targets with 32-bit wide pointers
--> $DIR/cast_size.rs:36:5
--> $DIR/cast_size.rs:26:5
|
LL | 1i64 as isize;
| ^^^^^^^^^^^^^
@ -109,7 +109,7 @@ LL | isize::try_from(1i64);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers
--> $DIR/cast_size.rs:38:5
--> $DIR/cast_size.rs:27:5
|
LL | 1i64 as usize;
| ^^^^^^^^^^^^^
@ -121,7 +121,7 @@ LL | usize::try_from(1i64);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers
--> $DIR/cast_size.rs:40:5
--> $DIR/cast_size.rs:28:5
|
LL | 1u64 as isize;
| ^^^^^^^^^^^^^
@ -133,13 +133,13 @@ LL | isize::try_from(1u64);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers
--> $DIR/cast_size.rs:40:5
--> $DIR/cast_size.rs:28:5
|
LL | 1u64 as isize;
| ^^^^^^^^^^^^^
error: casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers
--> $DIR/cast_size.rs:43:5
--> $DIR/cast_size.rs:29:5
|
LL | 1u64 as usize;
| ^^^^^^^^^^^^^
@ -151,19 +151,19 @@ LL | usize::try_from(1u64);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers
--> $DIR/cast_size.rs:45:5
--> $DIR/cast_size.rs:30:5
|
LL | 1u32 as isize;
| ^^^^^^^^^^^^^
error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide)
--> $DIR/cast_size.rs:51:5
--> $DIR/cast_size.rs:35:5
|
LL | 999_999_999 as f32;
| ^^^^^^^^^^^^^^^^^^
error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide)
--> $DIR/cast_size.rs:53:5
--> $DIR/cast_size.rs:36:5
|
LL | 9_999_999_999_999_999usize as f64;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -1,56 +1,37 @@
//@ignore-32bit
#[warn(
//@stderr-per-bitwidth
//@no-rustfix
#![warn(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
clippy::cast_lossless
)]
#[allow(clippy::no_effect, clippy::unnecessary_operation)]
#![allow(clippy::no_effect, clippy::unnecessary_operation)]
fn main() {
// Casting from *size
1isize as i8;
//~^ ERROR: casting `isize` to `i8` may truncate the value
let x0 = 1isize;
let x1 = 1usize;
x0 as f64;
//~^ ERROR: casting `isize` to `f64` causes a loss of precision on targets with 64-bit
//~| NOTE: `-D clippy::cast-precision-loss` implied by `-D warnings`
x1 as f64;
//~^ ERROR: casting `usize` to `f64` causes a loss of precision on targets with 64-bit
x0 as f32;
//~^ ERROR: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 b
x1 as f32;
//~^ ERROR: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 b
1isize as i32;
//~^ ERROR: casting `isize` to `i32` may truncate the value on targets with 64-bit wid
1isize as u32;
//~^ ERROR: casting `isize` to `u32` may truncate the value on targets with 64-bit wid
1usize as u32;
//~^ ERROR: casting `usize` to `u32` may truncate the value on targets with 64-bit wid
1usize as i32;
//~^ ERROR: casting `usize` to `i32` may truncate the value on targets with 64-bit wid
//~| ERROR: casting `usize` to `i32` may wrap around the value on targets with 32-bit
//~| NOTE: `-D clippy::cast-possible-wrap` implied by `-D warnings`
// Casting to *size
1i64 as isize;
//~^ ERROR: casting `i64` to `isize` may truncate the value on targets with 32-bit wid
1i64 as usize;
//~^ ERROR: casting `i64` to `usize` may truncate the value on targets with 32-bit wid
1u64 as isize;
//~^ ERROR: casting `u64` to `isize` may truncate the value on targets with 32-bit wid
//~| ERROR: casting `u64` to `isize` may wrap around the value on targets with 64-bit
1u64 as usize;
//~^ ERROR: casting `u64` to `usize` may truncate the value on targets with 32-bit wid
1u32 as isize;
//~^ ERROR: casting `u32` to `isize` may wrap around the value on targets with 32-bit
1u32 as usize; // Should not trigger any lint
1i32 as isize; // Neither should this
1i32 as usize;
// Big integer literal to float
999_999_999 as f32;
//~^ ERROR: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide,
9_999_999_999_999_999usize as f64;
//~^ ERROR: casting `usize` to `f64` causes a loss of precision on targets with 64-bit
}
//@no-rustfix

View file

@ -1,56 +0,0 @@
//@ignore-64bit
#[warn(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
clippy::cast_lossless
)]
#[allow(clippy::no_effect, clippy::unnecessary_operation)]
fn main() {
// Casting from *size
1isize as i8;
//~^ ERROR: casting `isize` to `i8` may truncate the value
let x0 = 1isize;
let x1 = 1usize;
x0 as f64;
//~^ ERROR: casting `isize` to `f64` causes a loss of precision on targets with 64-bit
//~| NOTE: `-D clippy::cast-precision-loss` implied by `-D warnings`
x1 as f64;
//~^ ERROR: casting `usize` to `f64` causes a loss of precision on targets with 64-bit
x0 as f32;
//~^ ERROR: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 b
x1 as f32;
//~^ ERROR: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 b
1isize as i32;
//~^ ERROR: casting `isize` to `i32` may truncate the value on targets with 64-bit wid
1isize as u32;
//~^ ERROR: casting `isize` to `u32` may truncate the value on targets with 64-bit wid
1usize as u32;
//~^ ERROR: casting `usize` to `u32` may truncate the value on targets with 64-bit wid
1usize as i32;
//~^ ERROR: casting `usize` to `i32` may truncate the value on targets with 64-bit wid
//~| ERROR: casting `usize` to `i32` may wrap around the value on targets with 32-bit
//~| NOTE: `-D clippy::cast-possible-wrap` implied by `-D warnings`
// Casting to *size
1i64 as isize;
//~^ ERROR: casting `i64` to `isize` may truncate the value on targets with 32-bit wid
1i64 as usize;
//~^ ERROR: casting `i64` to `usize` may truncate the value on targets with 32-bit wid
1u64 as isize;
//~^ ERROR: casting `u64` to `isize` may truncate the value on targets with 32-bit wid
//~| ERROR: casting `u64` to `isize` may wrap around the value on targets with 64-bit
1u64 as usize;
//~^ ERROR: casting `u64` to `usize` may truncate the value on targets with 32-bit wid
1u32 as isize;
//~^ ERROR: casting `u32` to `isize` may wrap around the value on targets with 32-bit
1u32 as usize; // Should not trigger any lint
1i32 as isize; // Neither should this
1i32 as usize;
// Big integer literal to float
999_999_999 as f32;
//~^ ERROR: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide,
3_999_999_999usize as f64;
//~^ ERROR: casting integer literal to `f64` is unnecessary
//~| NOTE: `-D clippy::unnecessary-cast` implied by `-D warnings`
}

View file

@ -128,6 +128,57 @@ fn main() {
assert!(x.is_ok(), "{:?}", x.unwrap_err());
}
fn issue11371() {
let option = Some(());
if option.is_some() {
option.as_ref().unwrap();
//~^ ERROR: called `unwrap` on `option` after checking its variant with `is_some`
} else {
option.as_ref().unwrap();
//~^ ERROR: this call to `unwrap()` will always panic
}
let result = Ok::<(), ()>(());
if result.is_ok() {
result.as_ref().unwrap();
//~^ ERROR: called `unwrap` on `result` after checking its variant with `is_ok`
} else {
result.as_ref().unwrap();
//~^ ERROR: this call to `unwrap()` will always panic
}
let mut option = Some(());
if option.is_some() {
option.as_mut().unwrap();
//~^ ERROR: called `unwrap` on `option` after checking its variant with `is_some`
} else {
option.as_mut().unwrap();
//~^ ERROR: this call to `unwrap()` will always panic
}
let mut result = Ok::<(), ()>(());
if result.is_ok() {
result.as_mut().unwrap();
//~^ ERROR: called `unwrap` on `result` after checking its variant with `is_ok`
} else {
result.as_mut().unwrap();
//~^ ERROR: this call to `unwrap()` will always panic
}
// This should not lint. Statics are, at the time of writing, not linted on anyway,
// but if at some point they are supported by this lint, it should correctly see that
// `X` is being mutated and not suggest `if let Some(..) = X {}`
static mut X: Option<i32> = Some(123);
unsafe {
if X.is_some() {
X = None;
X.unwrap();
}
}
}
fn check_expect() {
let x = Some(());
if x.is_some() {

View file

@ -168,5 +168,73 @@ LL | if x.is_err() {
LL | x.unwrap_err();
| ^^^^^^^^^^^^^^
error: aborting due to 17 previous errors
error: called `unwrap` on `option` after checking its variant with `is_some`
--> $DIR/simple_conditionals.rs:135:9
|
LL | if option.is_some() {
| ------------------- help: try: `if let Some(..) = &option`
LL | option.as_ref().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: this call to `unwrap()` will always panic
--> $DIR/simple_conditionals.rs:138:9
|
LL | if option.is_some() {
| ---------------- because of this check
...
LL | option.as_ref().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: called `unwrap` on `result` after checking its variant with `is_ok`
--> $DIR/simple_conditionals.rs:145:9
|
LL | if result.is_ok() {
| ----------------- help: try: `if let Ok(..) = &result`
LL | result.as_ref().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: this call to `unwrap()` will always panic
--> $DIR/simple_conditionals.rs:148:9
|
LL | if result.is_ok() {
| -------------- because of this check
...
LL | result.as_ref().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: called `unwrap` on `option` after checking its variant with `is_some`
--> $DIR/simple_conditionals.rs:154:9
|
LL | if option.is_some() {
| ------------------- help: try: `if let Some(..) = &mut option`
LL | option.as_mut().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: this call to `unwrap()` will always panic
--> $DIR/simple_conditionals.rs:157:9
|
LL | if option.is_some() {
| ---------------- because of this check
...
LL | option.as_mut().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: called `unwrap` on `result` after checking its variant with `is_ok`
--> $DIR/simple_conditionals.rs:163:9
|
LL | if result.is_ok() {
| ----------------- help: try: `if let Ok(..) = &mut result`
LL | result.as_mut().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: this call to `unwrap()` will always panic
--> $DIR/simple_conditionals.rs:166:9
|
LL | if result.is_ok() {
| -------------- because of this check
...
LL | result.as_mut().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 25 previous errors

View file

@ -1,4 +1,4 @@
#![allow(clippy::incorrect_clone_impl_on_copy_type)]
#![allow(clippy::non_canonical_clone_impl)]
use std::fmt;
use std::marker::PhantomData;

View file

@ -0,0 +1,9 @@
#![feature(trait_alias)]
trait Confusing<F> = Fn(i32) where F: Fn(u32);
fn alias<T: Confusing<F>, F>(_: T, _: F) {}
fn main() {
alias(|_| {}, |_| {});
}

View file

@ -0,0 +1,25 @@
#![warn(clippy::implied_bounds_in_impls)]
use std::fmt::Debug;
use std::ops::*;
fn gen() -> impl PartialOrd + Debug {}
struct Bar {}
trait Foo<T = Self> {}
trait FooNested<T = Option<Self>> {}
impl Foo for Bar {}
impl FooNested for Bar {}
fn foo() -> impl Foo + FooNested {
Bar {}
}
fn test_impl_ops() -> impl Add + Sub + Mul + Div {
1
}
fn test_impl_assign_ops() -> impl AddAssign + SubAssign + MulAssign + DivAssign {
1
}
fn main() {}

View file

@ -0,0 +1,25 @@
#![warn(clippy::implied_bounds_in_impls)]
use std::fmt::Debug;
use std::ops::*;
fn gen() -> impl PartialOrd + PartialEq + Debug {}
struct Bar {}
trait Foo<T = Self> {}
trait FooNested<T = Option<Self>> {}
impl Foo for Bar {}
impl FooNested for Bar {}
fn foo() -> impl Foo + FooNested {
Bar {}
}
fn test_impl_ops() -> impl Add + Sub + Mul + Div {
1
}
fn test_impl_assign_ops() -> impl AddAssign + SubAssign + MulAssign + DivAssign {
1
}
fn main() {}

View file

@ -0,0 +1,16 @@
error: this bound is already specified as the supertrait of `PartialOrd`
--> $DIR/ice-11422.rs:6:31
|
LL | fn gen() -> impl PartialOrd + PartialEq + Debug {}
| ^^^^^^^^^
|
= note: `-D clippy::implied-bounds-in-impls` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::implied_bounds_in_impls)]`
help: try removing this bound
|
LL - fn gen() -> impl PartialOrd + PartialEq + Debug {}
LL + fn gen() -> impl PartialOrd + Debug {}
|
error: aborting due to previous error

View file

@ -3,7 +3,8 @@ fn main() {}
fn no_panic<T>(slice: &[T]) {
let mut iter = slice.iter();
loop {
//~^ ERROR: this loop could be written as a `while let` loop
//~^ ERROR: this loop never actually loops
//~| ERROR: this loop could be written as a `while let` loop
//~| NOTE: `-D clippy::while-let-loop` implied by `-D warnings`
let _ = match iter.next() {
Some(ele) => ele,

View file

@ -1,10 +1,24 @@
error: this loop never actually loops
--> $DIR/ice-360.rs:5:5
|
LL | / loop {
LL | |
LL | |
LL | |
... |
LL | |
LL | | }
| |_____^
|
= note: `#[deny(clippy::never_loop)]` on by default
error: this loop could be written as a `while let` loop
--> $DIR/ice-360.rs:5:5
|
LL | / loop {
LL | |
LL | |
LL | | let _ = match iter.next() {
LL | |
... |
LL | |
LL | | }
@ -14,7 +28,7 @@ LL | | }
= help: to override `-D warnings` add `#[allow(clippy::while_let_loop)]`
error: empty `loop {}` wastes CPU cycles
--> $DIR/ice-360.rs:12:9
--> $DIR/ice-360.rs:13:9
|
LL | loop {}
| ^^^^^^^
@ -23,5 +37,5 @@ LL | loop {}
= note: `-D clippy::empty-loop` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::empty_loop)]`
error: aborting due to 2 previous errors
error: aborting due to 3 previous errors

View file

@ -287,4 +287,17 @@ mod issue10158 {
}
}
mod issue11368 {
pub struct A {
a: u32,
}
impl Default for A {
#[track_caller]
fn default() -> Self {
Self { a: 0 }
}
}
}
fn main() {}

View file

@ -323,4 +323,17 @@ mod issue10158 {
}
}
mod issue11368 {
pub struct A {
a: u32,
}
impl Default for A {
#[track_caller]
fn default() -> Self {
Self { a: 0 }
}
}
}
fn main() {}

View file

@ -1,8 +1,4 @@
#![allow(
clippy::incorrect_clone_impl_on_copy_type,
clippy::incorrect_partial_ord_impl_on_ord_type,
dead_code
)]
#![allow(clippy::non_canonical_clone_impl, clippy::non_canonical_partial_ord_impl, dead_code)]
#![warn(clippy::expl_impl_clone_on_copy)]

View file

@ -1,5 +1,5 @@
error: you are implementing `Clone` explicitly on a `Copy` type
--> $DIR/derive.rs:12:1
--> $DIR/derive.rs:8:1
|
LL | / impl Clone for Qux {
LL | |
@ -10,7 +10,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> $DIR/derive.rs:12:1
--> $DIR/derive.rs:8:1
|
LL | / impl Clone for Qux {
LL | |
@ -23,7 +23,7 @@ LL | | }
= help: to override `-D warnings` add `#[allow(clippy::expl_impl_clone_on_copy)]`
error: you are implementing `Clone` explicitly on a `Copy` type
--> $DIR/derive.rs:37:1
--> $DIR/derive.rs:33:1
|
LL | / impl<'a> Clone for Lt<'a> {
LL | |
@ -34,7 +34,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> $DIR/derive.rs:37:1
--> $DIR/derive.rs:33:1
|
LL | / impl<'a> Clone for Lt<'a> {
LL | |
@ -45,7 +45,7 @@ LL | | }
| |_^
error: you are implementing `Clone` explicitly on a `Copy` type
--> $DIR/derive.rs:49:1
--> $DIR/derive.rs:45:1
|
LL | / impl Clone for BigArray {
LL | |
@ -56,7 +56,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> $DIR/derive.rs:49:1
--> $DIR/derive.rs:45:1
|
LL | / impl Clone for BigArray {
LL | |
@ -67,7 +67,7 @@ LL | | }
| |_^
error: you are implementing `Clone` explicitly on a `Copy` type
--> $DIR/derive.rs:61:1
--> $DIR/derive.rs:57:1
|
LL | / impl Clone for FnPtr {
LL | |
@ -78,7 +78,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> $DIR/derive.rs:61:1
--> $DIR/derive.rs:57:1
|
LL | / impl Clone for FnPtr {
LL | |
@ -89,7 +89,7 @@ LL | | }
| |_^
error: you are implementing `Clone` explicitly on a `Copy` type
--> $DIR/derive.rs:82:1
--> $DIR/derive.rs:78:1
|
LL | / impl<T: Clone> Clone for Generic2<T> {
LL | |
@ -100,7 +100,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> $DIR/derive.rs:82:1
--> $DIR/derive.rs:78:1
|
LL | / impl<T: Clone> Clone for Generic2<T> {
LL | |

View file

@ -1,6 +1,6 @@
#![warn(clippy::derive_ord_xor_partial_ord)]
#![allow(clippy::unnecessary_wraps)]
#![allow(clippy::incorrect_partial_ord_impl_on_ord_type)]
#![allow(clippy::non_canonical_partial_ord_impl)]
use std::cmp::Ordering;

View file

@ -7,10 +7,12 @@ use proc_macros::{external, inline_macros};
fn should_trigger() {
loop {}
#[allow(clippy::never_loop)]
loop {
loop {}
}
#[allow(clippy::never_loop)]
'outer: loop {
'inner: loop {}
}
@ -18,6 +20,7 @@ fn should_trigger() {
#[inline_macros]
fn should_not_trigger() {
#[allow(clippy::never_loop)]
loop {
panic!("This is fine")
}

View file

@ -9,7 +9,7 @@ LL | loop {}
= help: to override `-D warnings` add `#[allow(clippy::empty_loop)]`
error: empty `loop {}` wastes CPU cycles
--> $DIR/empty_loop.rs:11:9
--> $DIR/empty_loop.rs:12:9
|
LL | loop {}
| ^^^^^^^
@ -17,7 +17,7 @@ LL | loop {}
= help: you should either use `panic!()` or add `std::thread::sleep(..);` to the loop body
error: empty `loop {}` wastes CPU cycles
--> $DIR/empty_loop.rs:15:9
--> $DIR/empty_loop.rs:17:9
|
LL | 'inner: loop {}
| ^^^^^^^^^^^^^^^

View file

@ -1,4 +1,4 @@
//@ignore-target-x86
//@ignore-32bit
#![warn(clippy::enum_clike_unportable_variant)]
#![allow(unused, non_upper_case_globals)]

View file

@ -5,51 +5,52 @@ LL | X = 0x1_0000_0000,
| ^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::enum-clike-unportable-variant` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::enum_clike_unportable_variant)]`
error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:15:5
--> $DIR/enum_clike_unportable_variant.rs:17:5
|
LL | X = 0x1_0000_0000,
| ^^^^^^^^^^^^^^^^^
error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:18:5
--> $DIR/enum_clike_unportable_variant.rs:21:5
|
LL | A = 0xFFFF_FFFF,
| ^^^^^^^^^^^^^^^
error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:25:5
--> $DIR/enum_clike_unportable_variant.rs:29:5
|
LL | Z = 0xFFFF_FFFF,
| ^^^^^^^^^^^^^^^
error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:26:5
--> $DIR/enum_clike_unportable_variant.rs:31:5
|
LL | A = 0x1_0000_0000,
| ^^^^^^^^^^^^^^^^^
error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:28:5
--> $DIR/enum_clike_unportable_variant.rs:34:5
|
LL | C = (i32::MIN as isize) - 1,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:34:5
--> $DIR/enum_clike_unportable_variant.rs:41:5
|
LL | Z = 0xFFFF_FFFF,
| ^^^^^^^^^^^^^^^
error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:35:5
--> $DIR/enum_clike_unportable_variant.rs:43:5
|
LL | A = 0x1_0000_0000,
| ^^^^^^^^^^^^^^^^^
error: C-like enum variant discriminant is not portable to 32-bit targets
--> $DIR/enum_clike_unportable_variant.rs:40:5
--> $DIR/enum_clike_unportable_variant.rs:49:5
|
LL | X = <usize as Trait>::Number,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -205,7 +205,7 @@ fn main() {
}
}
f_str(&&ref_str); // `needless_borrow` will suggest removing both references
f_str(&ref_str); // `needless_borrow` will suggest removing both references
f_str(&ref_str); // `needless_borrow` will suggest removing only one reference
let x = &&40;
@ -293,4 +293,32 @@ fn main() {
fn return_dyn_assoc<'a>(x: &'a &'a u32) -> &'a <&'a u32 as WithAssoc>::Assoc {
*x
}
// Issue #11366
let _: &mut u32 = match &mut Some(&mut 0u32) {
Some(x) => x,
None => panic!(),
};
// Issue #11474
pub struct Variant {
pub anonymous: Variant0,
}
pub union Variant0 {
pub anonymous: std::mem::ManuallyDrop<Variant00>,
}
pub struct Variant00 {
pub anonymous: Variant000,
}
pub union Variant000 {
pub val: i32,
}
unsafe {
let mut p = core::mem::zeroed::<Variant>();
(*p.anonymous.anonymous).anonymous.val = 1;
}
}

View file

@ -293,4 +293,32 @@ fn main() {
fn return_dyn_assoc<'a>(x: &'a &'a u32) -> &'a <&'a u32 as WithAssoc>::Assoc {
*x
}
// Issue #11366
let _: &mut u32 = match &mut Some(&mut 0u32) {
Some(x) => &mut *x,
None => panic!(),
};
// Issue #11474
pub struct Variant {
pub anonymous: Variant0,
}
pub union Variant0 {
pub anonymous: std::mem::ManuallyDrop<Variant00>,
}
pub struct Variant00 {
pub anonymous: Variant000,
}
pub union Variant000 {
pub val: i32,
}
unsafe {
let mut p = core::mem::zeroed::<Variant>();
(*p.anonymous.anonymous).anonymous.val = 1;
}
}

View file

@ -194,10 +194,10 @@ LL | let _ = f_str(**ref_ref_str);
| ^^^^^^^^^^^^^ help: try: `ref_ref_str`
error: deref which would be done by auto-deref
--> $DIR/explicit_auto_deref.rs:208:13
--> $DIR/explicit_auto_deref.rs:208:12
|
LL | f_str(&&*ref_str); // `needless_borrow` will suggest removing both references
| ^^^^^^^^ help: try: `ref_str`
| ^^^^^^^^^ help: try: `ref_str`
error: deref which would be done by auto-deref
--> $DIR/explicit_auto_deref.rs:209:12
@ -235,5 +235,11 @@ error: deref which would be done by auto-deref
LL | *x
| ^^ help: try: `x`
error: aborting due to 39 previous errors
error: deref which would be done by auto-deref
--> $DIR/explicit_auto_deref.rs:299:20
|
LL | Some(x) => &mut *x,
| ^^^^^^^ help: try: `x`
error: aborting due to 40 previous errors

View file

@ -4,6 +4,7 @@
clippy::similar_names,
clippy::needless_borrow,
clippy::deref_addrof,
clippy::unnecessary_mut_passed,
dead_code
)]
@ -20,15 +21,15 @@ fn main() {
for _ in rvec {}
let rmvec = &mut vec;
for _ in &*rmvec {}
for _ in &mut *rmvec {}
for _ in rmvec.iter() {}
for _ in rmvec.iter_mut() {}
for _ in &vec {} // these are fine
for _ in &mut vec {} // these are fine
for _ in &[1, 2, 3] {}
for _ in &*(&mut [1, 2, 3]) {}
for _ in (&mut [1, 2, 3]).iter() {}
for _ in &[0; 32] {}
for _ in &[0; 33] {}

View file

@ -4,6 +4,7 @@
clippy::similar_names,
clippy::needless_borrow,
clippy::deref_addrof,
clippy::unnecessary_mut_passed,
dead_code
)]

View file

@ -1,5 +1,5 @@
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:16:14
--> $DIR/explicit_iter_loop.rs:17:14
|
LL | for _ in vec.iter() {}
| ^^^^^^^^^^ help: to write this more concisely, try: `&vec`
@ -11,133 +11,106 @@ LL | #![deny(clippy::explicit_iter_loop)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:17:14
--> $DIR/explicit_iter_loop.rs:18:14
|
LL | for _ in vec.iter_mut() {}
| ^^^^^^^^^^^^^^ help: to write this more concisely, try: `&mut vec`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:20:14
--> $DIR/explicit_iter_loop.rs:21:14
|
LL | for _ in rvec.iter() {}
| ^^^^^^^^^^^ help: to write this more concisely, try: `rvec`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:23:14
|
LL | for _ in rmvec.iter() {}
| ^^^^^^^^^^^^ help: to write this more concisely, try: `&*rmvec`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:24:14
|
LL | for _ in rmvec.iter_mut() {}
| ^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `&mut *rmvec`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:29:14
--> $DIR/explicit_iter_loop.rs:30:14
|
LL | for _ in [1, 2, 3].iter() {}
| ^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[1, 2, 3]`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:31:14
|
LL | for _ in (&mut [1, 2, 3]).iter() {}
| ^^^^^^^^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `&*(&mut [1, 2, 3])`
error: the method `iter` doesn't need a mutable reference
--> $DIR/explicit_iter_loop.rs:31:14
|
LL | for _ in (&mut [1, 2, 3]).iter() {}
| ^^^^^^^^^^^^^^^^
|
= note: `-D clippy::unnecessary-mut-passed` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_mut_passed)]`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:33:14
--> $DIR/explicit_iter_loop.rs:34:14
|
LL | for _ in [0; 32].iter() {}
| ^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[0; 32]`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:34:14
--> $DIR/explicit_iter_loop.rs:35:14
|
LL | for _ in [0; 33].iter() {}
| ^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[0; 33]`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:37:14
--> $DIR/explicit_iter_loop.rs:38:14
|
LL | for _ in ll.iter() {}
| ^^^^^^^^^ help: to write this more concisely, try: `&ll`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:39:14
--> $DIR/explicit_iter_loop.rs:40:14
|
LL | for _ in rll.iter() {}
| ^^^^^^^^^^ help: to write this more concisely, try: `rll`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:42:14
--> $DIR/explicit_iter_loop.rs:43:14
|
LL | for _ in vd.iter() {}
| ^^^^^^^^^ help: to write this more concisely, try: `&vd`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:44:14
--> $DIR/explicit_iter_loop.rs:45:14
|
LL | for _ in rvd.iter() {}
| ^^^^^^^^^^ help: to write this more concisely, try: `rvd`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:47:14
--> $DIR/explicit_iter_loop.rs:48:14
|
LL | for _ in bh.iter() {}
| ^^^^^^^^^ help: to write this more concisely, try: `&bh`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:50:14
--> $DIR/explicit_iter_loop.rs:51:14
|
LL | for _ in hm.iter() {}
| ^^^^^^^^^ help: to write this more concisely, try: `&hm`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:53:14
--> $DIR/explicit_iter_loop.rs:54:14
|
LL | for _ in bt.iter() {}
| ^^^^^^^^^ help: to write this more concisely, try: `&bt`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:56:14
--> $DIR/explicit_iter_loop.rs:57:14
|
LL | for _ in hs.iter() {}
| ^^^^^^^^^ help: to write this more concisely, try: `&hs`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:59:14
--> $DIR/explicit_iter_loop.rs:60:14
|
LL | for _ in bs.iter() {}
| ^^^^^^^^^ help: to write this more concisely, try: `&bs`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:148:14
--> $DIR/explicit_iter_loop.rs:149:14
|
LL | for _ in x.iter() {}
| ^^^^^^^^ help: to write this more concisely, try: `&x`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:149:14
--> $DIR/explicit_iter_loop.rs:150:14
|
LL | for _ in x.iter_mut() {}
| ^^^^^^^^^^^^ help: to write this more concisely, try: `&mut x`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/explicit_iter_loop.rs:152:14
--> $DIR/explicit_iter_loop.rs:153:14
|
LL | for _ in r.iter() {}
| ^^^^^^^^ help: to write this more concisely, try: `r`
error: aborting due to 22 previous errors
error: aborting due to 18 previous errors

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