mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 14:55:26 +00:00
Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
d2b08432db
33
.github/workflows/clippy_bors.yml
vendored
33
.github/workflows/clippy_bors.yml
vendored
@ -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
|
||||
|
@ -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
|
||||
@ -5570,4 +5572,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 -->
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
217
book/src/development/emitting_lints.md
Normal file
217
book/src/development/emitting_lints.md
Normal 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
|
105
book/src/development/trait_checking.md
Normal file
105
book/src/development/trait_checking.md
Normal 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
|
218
book/src/development/writing_tests.md
Normal file
218
book/src/development/writing_tests.md
Normal 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
|
||||
```
|
@ -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)
|
||||
|
||||
|
||||
|
@ -365,6 +365,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 +457,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,
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
trait_id,
|
||||
[impl_ty.into()].into_iter().chain(args.iter().copied()),
|
||||
cx.param_env,
|
||||
)
|
||||
.must_apply_modulo_regions()
|
||||
&& implements_trait(
|
||||
cx,
|
||||
impl_ty,
|
||||
trait_id,
|
||||
&args[..cx.tcx.generics_of(trait_id).params.len() - 1],
|
||||
)
|
||||
{
|
||||
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,
|
||||
},
|
||||
));
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -508,7 +508,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>)> {
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,80 @@ 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.
|
||||
/// 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
|
||||
/// is what `U` in `GenericTrait<U>` is substituted with)
|
||||
/// ()
|
||||
/// 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]
|
||||
{
|
||||
hir_ty_to_ty(tcx, ty_a) == hir_ty_to_ty(tcx, ty_b)
|
||||
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)
|
||||
{
|
||||
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 +149,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.args.map_or([].as_slice(), |a| a.args), predicates, trait_def_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -135,18 +163,27 @@ fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
|
||||
&& let [.., path] = poly_trait.trait_ref.path.segments
|
||||
&& let implied_args = path.args.map_or([].as_slice(), |a| a.args)
|
||||
&& 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)| {
|
||||
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)
|
||||
{
|
||||
Some(span)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
&& let Some(implied_by_span) = implied_bounds
|
||||
.iter()
|
||||
.find_map(|&(span, implied_by_args, preds, implied_by_def_id)| {
|
||||
preds.iter().find_map(|(clause, _)| {
|
||||
if let ClauseKind::Trait(tr) = clause.kind().skip_binder()
|
||||
&& tr.def_id() == def_id
|
||||
&& is_same_generics(
|
||||
cx.tcx,
|
||||
tr.trait_ref.args,
|
||||
implied_by_args,
|
||||
implied_args,
|
||||
implied_by_def_id,
|
||||
def_id,
|
||||
)
|
||||
{
|
||||
Some(span)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
{
|
||||
let implied_by = snippet(cx, implied_by_span, "..");
|
||||
span_lint_and_then(
|
||||
|
@ -5,6 +5,7 @@ use clippy_utils::{get_parent_node, is_res_lang_ctor, last_path_segment, match_d
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, ItemKind, LangItem, Node, UnOp};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::EarlyBinder;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
@ -125,7 +126,7 @@ impl LateLintPass<'_> for IncorrectImpls {
|
||||
if cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) {
|
||||
return;
|
||||
}
|
||||
let ItemKind::Impl(_) = item.kind else {
|
||||
let ItemKind::Impl(imp) = item.kind else {
|
||||
return;
|
||||
};
|
||||
let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir().impl_item(impl_item.impl_item_id()).kind else {
|
||||
@ -188,12 +189,7 @@ impl LateLintPass<'_> for IncorrectImpls {
|
||||
.diagnostic_items(trait_impl.def_id.krate)
|
||||
.name_to_id
|
||||
.get(&sym::Ord)
|
||||
&& implements_trait(
|
||||
cx,
|
||||
trait_impl.self_ty(),
|
||||
*ord_def_id,
|
||||
&[],
|
||||
)
|
||||
&& implements_trait(cx, hir_ty_to_ty(cx.tcx, imp.self_ty), *ord_def_id, &[])
|
||||
{
|
||||
// If the `cmp` call likely needs to be fully qualified in the suggestion
|
||||
// (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't
|
||||
|
@ -210,6 +210,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;
|
||||
@ -695,7 +696,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));
|
||||
@ -1099,6 +1101,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`
|
||||
}
|
||||
|
||||
|
@ -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])
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
});
|
||||
combine_seq(e1, combine_branches(e2, e3, ignore_ids))
|
||||
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_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
|
||||
}
|
||||
},
|
||||
// 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)
|
||||
}),
|
||||
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 {
|
||||
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
|
||||
never_loop_expr(cx, expr, ignore_ids, main_loop_id)
|
||||
},
|
||||
InlineAsmOperand::Out { expr, .. } => {
|
||||
never_loop_expr_all(cx, &mut expr.iter().copied(), ignore_ids, 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,
|
||||
main_loop_id,
|
||||
),
|
||||
InlineAsmOperand::Const { .. }
|
||||
| InlineAsmOperand::SymFn { .. }
|
||||
| InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise,
|
||||
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
|
||||
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
|
||||
})
|
||||
.fold(NeverLoopResult::Otherwise, combine_seq),
|
||||
},
|
||||
ExprKind::Become(e) => combine_seq(never_loop_expr(cx, e, local_labels, main_loop_id), || {
|
||||
NeverLoopResult::Diverging
|
||||
}),
|
||||
ExprKind::InlineAsm(asm) => combine_seq_many(asm.operands.iter().map(|(o, _)| match o {
|
||||
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
|
||||
never_loop_expr(cx, expr, local_labels, main_loop_id)
|
||||
},
|
||||
InlineAsmOperand::Out { expr, .. } => {
|
||||
never_loop_expr_all(cx, expr.iter().copied(), local_labels, main_loop_id)
|
||||
},
|
||||
InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
|
||||
cx,
|
||||
once(*in_expr).chain(out_expr.iter().copied()),
|
||||
local_labels,
|
||||
main_loop_id,
|
||||
),
|
||||
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 {
|
||||
|
@ -36,7 +36,8 @@ struct PathAndSpan {
|
||||
span: Span,
|
||||
}
|
||||
|
||||
/// `MacroRefData` includes the name of the macro.
|
||||
/// `MacroRefData` includes the name of the macro
|
||||
/// and the path from `SourceMap::span_to_filename`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MacroRefData {
|
||||
name: String,
|
||||
|
@ -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,71 +81,83 @@ 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 {
|
||||
if numbers_found.contains(&num) {
|
||||
num += 1;
|
||||
}
|
||||
// Given a list of (potentially overlapping) ranges like:
|
||||
// 1..=5, 3..=7, 6..=10
|
||||
// We want to find the range with the highest end that still contains the current number
|
||||
else if let Some(range) = ranges_found
|
||||
.iter()
|
||||
.filter(|range| range.contains(&num))
|
||||
.max_by_key(|range| range.end())
|
||||
{
|
||||
num = range.end() + 1;
|
||||
} else {
|
||||
break 'contains false;
|
||||
}
|
||||
let mut num = min.val;
|
||||
while num <= max.val {
|
||||
if numbers_found.contains(&num) {
|
||||
num += 1;
|
||||
}
|
||||
// Given a list of (potentially overlapping) ranges like:
|
||||
// 1..=5, 3..=7, 6..=10
|
||||
// We want to find the range with the highest end that still contains the current number
|
||||
else if let Some(range) = ranges_found
|
||||
.iter()
|
||||
.filter(|range| range.contains(&num))
|
||||
.max_by_key(|range| range.end())
|
||||
{
|
||||
num = range.end() + 1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
break 'contains true;
|
||||
};
|
||||
|
||||
if contains_whole_range {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_RANGE_PATTERNS,
|
||||
pat.span,
|
||||
"this OR pattern can be rewritten using a range",
|
||||
"try",
|
||||
format!("{min}..={max}"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
||||
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}{range_kind}{max}"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
106
clippy_lints/src/methods/iter_out_of_bounds.rs
Normal file
106
clippy_lints/src/methods/iter_out_of_bounds.rs
Normal 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",
|
||||
);
|
||||
}
|
@ -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()`
|
||||
|
@ -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,12 +3912,16 @@ impl Methods {
|
||||
unnecessary_lazy_eval::check(cx, expr, recv, arg, "and");
|
||||
}
|
||||
},
|
||||
("any", [arg]) 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) =>
|
||||
{
|
||||
string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
|
||||
("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 => {
|
||||
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);
|
||||
|
@ -6,7 +6,8 @@ use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, GenericArgKind};
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::GenericArgKind;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::symbol::Ident;
|
||||
use std::iter;
|
||||
|
391
clippy_lints/src/missing_asserts_for_indexing.rs
Normal file
391
clippy_lints/src/missing_asserts_for_indexing.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -96,10 +96,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
|
||||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::If(..) => {
|
||||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::Path(_) => {
|
||||
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
|
||||
if adj
|
||||
|
@ -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<'_>| {
|
||||
|
@ -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,
|
||||
|
@ -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,14 +77,24 @@ 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),
|
||||
Applicability::MachineApplicable,
|
||||
|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);
|
||||
|
||||
diag.multipart_suggestion(
|
||||
"try",
|
||||
vec![(start, String::new()), (end, String::new())],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
@ -96,13 +107,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 +130,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),
|
||||
Applicability::MachineApplicable,
|
||||
|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)
|
||||
}
|
||||
|
@ -3,11 +3,9 @@ use clippy_utils::is_from_proc_macro;
|
||||
use clippy_utils::ty::needs_ordered_drop;
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{
|
||||
BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath,
|
||||
};
|
||||
use rustc_hir::{BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::{in_external_macro, is_from_async_await};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::DesugaringKind;
|
||||
@ -72,9 +70,6 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
|
||||
// the local is user-controlled
|
||||
if !in_external_macro(cx.sess(), local.span);
|
||||
if !is_from_proc_macro(cx, expr);
|
||||
// Async function parameters are lowered into the closure body, so we can't lint them.
|
||||
// see `lower_maybe_async_body` in `rust_ast_lowering`
|
||||
if !is_from_async_await(local.span);
|
||||
then {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
@ -111,12 +106,7 @@ fn affects_assignments(cx: &LateContext<'_>, mutability: Mutability, bind: HirId
|
||||
}
|
||||
|
||||
/// Check if a rebinding of a local affects the code's drop behavior.
|
||||
fn affects_drop_behavior<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
bind: HirId,
|
||||
rebind: HirId,
|
||||
rebind_expr: &Expr<'tcx>,
|
||||
) -> bool {
|
||||
fn affects_drop_behavior<'tcx>(cx: &LateContext<'tcx>, bind: HirId, rebind: HirId, rebind_expr: &Expr<'tcx>) -> bool {
|
||||
let hir = cx.tcx.hir();
|
||||
|
||||
// the rebinding is in a different scope than the original binding
|
||||
|
@ -55,11 +55,11 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
||||
if matches!(cx.tcx.def_kind(id.owner_id), DefKind::Impl { .. })
|
||||
&& let item = cx.tcx.hir().item(id)
|
||||
&& let ItemKind::Impl(Impl {
|
||||
items,
|
||||
of_trait,
|
||||
self_ty,
|
||||
..
|
||||
}) = &item.kind
|
||||
items,
|
||||
of_trait,
|
||||
self_ty,
|
||||
..
|
||||
}) = &item.kind
|
||||
&& let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind
|
||||
{
|
||||
if !map.contains_key(res) {
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
|
||||
span,
|
||||
..
|
||||
}),
|
||||
) = 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,
|
||||
)
|
||||
}
|
||||
let (span, hir_id) = match node {
|
||||
Node::Expr(expr) => match get_parent_node(cx.tcx, expr.hir_id) {
|
||||
Some(Node::Local(hir::Local { span, hir_id, .. })) => (*span, *hir_id),
|
||||
Some(Node::Item(hir::Item {
|
||||
kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
|
||||
span,
|
||||
owner_id,
|
||||
..
|
||||
})) => (*span, cx.tcx.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
|
||||
}
|
||||
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 {
|
||||
@ -639,29 +615,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())
|
||||
|
@ -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"]
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -83,9 +83,9 @@ pub fn span_lint_and_help<T: LintContext>(
|
||||
cx.struct_span_lint(lint, span, msg.to_string(), |diag| {
|
||||
let help = help.to_string();
|
||||
if let Some(help_span) = help_span {
|
||||
diag.span_help(help_span, help.to_string());
|
||||
diag.span_help(help_span, help);
|
||||
} else {
|
||||
diag.help(help.to_string());
|
||||
diag.help(help);
|
||||
}
|
||||
docs_link(diag, lint);
|
||||
diag
|
||||
|
@ -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(())
|
||||
|
@ -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"];
|
||||
|
@ -8,7 +8,7 @@ use rustc_hir::{BlockCheckMode, Expr, ExprKind, UnsafeSource};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_session::Session;
|
||||
use rustc_span::source_map::{original_sp, SourceMap};
|
||||
use rustc_span::{hygiene, BytePos, SourceFileAndLine, Pos, SourceFile, Span, SpanData, SyntaxContext, DUMMY_SP};
|
||||
use rustc_span::{hygiene, BytePos, Pos, SourceFile, SourceFileAndLine, Span, SpanData, SyntaxContext, DUMMY_SP};
|
||||
use std::borrow::Cow;
|
||||
use std::ops::Range;
|
||||
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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,34 +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 },
|
||||
mode: Mode::Yolo {
|
||||
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();
|
||||
@ -184,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 {
|
||||
@ -209,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 {
|
||||
@ -243,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
|
||||
@ -300,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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
@ -8,4 +8,6 @@ disallowed-macros = [
|
||||
"macros::ty",
|
||||
"macros::pat",
|
||||
"macros::item",
|
||||
"macros::binop",
|
||||
"macros::attr",
|
||||
]
|
||||
|
@ -20,11 +20,14 @@ fn main() {
|
||||
let macros::pat!() = 1;
|
||||
let _: macros::ty!() = "";
|
||||
macros::item!();
|
||||
let _ = macros::binop!(1);
|
||||
|
||||
eprintln!("allowed");
|
||||
}
|
||||
|
||||
struct S;
|
||||
macros::attr! {
|
||||
struct S;
|
||||
}
|
||||
|
||||
impl S {
|
||||
macros::item!();
|
||||
|
@ -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::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: use of a disallowed macro `macros::attr`
|
||||
--> $DIR/disallowed_macros.rs:28:1
|
||||
|
|
||||
LL | / macros::attr! {
|
||||
LL | | struct S;
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error: aborting due to 15 previous errors
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
//@aux-build:proc_macros.rs
|
||||
//@aux-build:../../ui/auxiliary/proc_macros.rs
|
||||
#![rustfmt::skip]
|
||||
#![feature(custom_inner_attributes)]
|
||||
#![allow(unused)]
|
||||
@ -156,7 +156,7 @@ fn main() {
|
||||
for i in {{{{xx}}}} {{{{{{{{}}}}}}}}
|
||||
|
||||
while let Some(i) = {{{{{{Some(1)}}}}}} {{{{{{{}}}}}}}
|
||||
|
||||
|
||||
while {{{{{{{{true}}}}}}}} {{{{{{{{{}}}}}}}}}
|
||||
|
||||
let d = D { d: {{{{{{{{{{{{{{{{{{{{{{{3}}}}}}}}}}}}}}}}}}}}}}} };
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}])
|
||||
}
|
@ -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() {}
|
||||
|
@ -1,5 +1,5 @@
|
||||
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;
|
||||
| ^^^^^^^^^^^^
|
||||
@ -12,7 +12,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_32bit.rs:15:5
|
||||
--> $DIR/cast_size.rs:18:5
|
||||
|
|
||||
LL | x0 as f64;
|
||||
| ^^^^^^^^^
|
||||
@ -20,25 +20,25 @@ LL | x0 as f64;
|
||||
= note: `-D clippy::cast-precision-loss` implied by `-D warnings`
|
||||
|
||||
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 +50,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 +62,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 +74,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,7 +86,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_32bit.rs:22:5
|
||||
--> $DIR/cast_size.rs:25:5
|
||||
|
|
||||
LL | 1usize as i32;
|
||||
| ^^^^^^^^^^^^^
|
||||
@ -94,7 +94,7 @@ LL | 1usize as i32;
|
||||
= note: `-D clippy::cast-possible-wrap` implied by `-D warnings`
|
||||
|
||||
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 +106,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 +118,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 +130,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 +148,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
|
||||
|
@ -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;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
@ -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
|
||||
|
@ -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`
|
||||
}
|
@ -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() {
|
||||
|
@ -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
|
||||
|
||||
|
9
tests/ui/crashes/ice-11337.rs
Normal file
9
tests/ui/crashes/ice-11337.rs
Normal 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(|_| {}, |_| {});
|
||||
}
|
25
tests/ui/crashes/ice-11422.fixed
Normal file
25
tests/ui/crashes/ice-11422.fixed
Normal 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() {}
|
25
tests/ui/crashes/ice-11422.rs
Normal file
25
tests/ui/crashes/ice-11422.rs
Normal 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() {}
|
16
tests/ui/crashes/ice-11422.stderr
Normal file
16
tests/ui/crashes/ice-11422.stderr
Normal 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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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() {}
|
||||
|
@ -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() {}
|
||||
|
@ -5,7 +5,6 @@
|
||||
)]
|
||||
#![warn(clippy::expl_impl_clone_on_copy)]
|
||||
|
||||
|
||||
#[derive(Copy)]
|
||||
struct Qux;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
error: you are implementing `Clone` explicitly on a `Copy` type
|
||||
--> $DIR/derive.rs:12:1
|
||||
--> $DIR/derive.rs:11: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:11: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:36: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:36: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:48: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:48: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:60: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:60: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:81: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:81:1
|
||||
|
|
||||
LL | / impl<T: Clone> Clone for Generic2<T> {
|
||||
LL | |
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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 {}
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
@ -1,4 +1,4 @@
|
||||
//@ignore-target-x86
|
||||
//@ignore-32bit
|
||||
|
||||
#![warn(clippy::enum_clike_unportable_variant)]
|
||||
#![allow(unused, non_upper_case_globals)]
|
||||
|
@ -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,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -22,9 +22,9 @@ mod rustc_ok {
|
||||
|
||||
#[expect(illegal_floating_point_literal_pattern)]
|
||||
match x {
|
||||
5.0 => {}
|
||||
6.0 => {}
|
||||
_ => {}
|
||||
5.0 => {},
|
||||
6.0 => {},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -41,9 +41,9 @@ mod rustc_warn {
|
||||
#[expect(illegal_floating_point_literal_pattern)]
|
||||
//~^ ERROR: this lint expectation is unfulfilled
|
||||
match x {
|
||||
5 => {}
|
||||
6 => {}
|
||||
_ => {}
|
||||
5 => {},
|
||||
6 => {},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,10 @@ 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!(),
|
||||
};
|
||||
}
|
||||
|
@ -293,4 +293,10 @@ 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!(),
|
||||
};
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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] {}
|
||||
|
@ -4,6 +4,7 @@
|
||||
clippy::similar_names,
|
||||
clippy::needless_borrow,
|
||||
clippy::deref_addrof,
|
||||
clippy::unnecessary_mut_passed,
|
||||
dead_code
|
||||
)]
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -41,6 +41,16 @@ impl PartialEq for X {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<f32> for X {
|
||||
fn eq(&self, o: &f32) -> bool {
|
||||
if self.val.is_nan() {
|
||||
o.is_nan()
|
||||
} else {
|
||||
self.val == *o // no error, inside "eq" fn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
ZERO == 0f32; //no error, comparison with zero is ok
|
||||
1.0f32 != f32::INFINITY; // also comparison with infinity
|
||||
@ -48,6 +58,9 @@ fn main() {
|
||||
ZERO == 0.0; //no error, comparison with zero is ok
|
||||
ZERO + ZERO != 1.0; //no error, comparison with zero is ok
|
||||
|
||||
let x = X { val: 1.0 };
|
||||
x == 1.0; // no error, custom type that implement PartialOrder for float is not checked
|
||||
|
||||
ONE == 1f32;
|
||||
ONE == 1.0 + 0.0;
|
||||
ONE + ONE == ZERO + ONE + ONE;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user