diff options
65 files changed, 2689 insertions, 338 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 92cc19edb05..fd7232e280f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 --> diff --git a/Cargo.toml b/Cargo.toml index cd04c78ef9e..2d8b590dbe3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ tempfile = { version = "3.2", optional = true } termize = "0.1" [dev-dependencies] -ui_test = "0.17.0" +ui_test = "0.20" tester = "0.9" regex = "1.5" toml = "0.7.3" diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index daaefd06a97..b02457307d7 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -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) diff --git a/book/src/development/emitting_lints.md b/book/src/development/emitting_lints.md new file mode 100644 index 00000000000..a12f6aa91b3 --- /dev/null +++ b/book/src/development/emitting_lints.md @@ -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 diff --git a/book/src/development/trait_checking.md b/book/src/development/trait_checking.md new file mode 100644 index 00000000000..fb263922cf8 --- /dev/null +++ b/book/src/development/trait_checking.md @@ -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 diff --git a/book/src/development/writing_tests.md b/book/src/development/writing_tests.md new file mode 100644 index 00000000000..7cb530a3bb9 --- /dev/null +++ b/book/src/development/writing_tests.md @@ -0,0 +1,223 @@ +# 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. +<!-- FIXME: (blyxyas) Link to "Emitting lints" when that gets merged --> + +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 +``` diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index caaad6d1173..52c795e04fe 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -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) + + diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index f73f1ed18f8..150d883985c 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -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, diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index 03b5a2d6d08..bbce6e1dd8f 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -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 diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 58c27855000..4bff55b2ecb 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -609,12 +609,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, }, )); diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ab71bfbdc58..4ff97e03a14 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -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` } diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index 7b8c88235a9..6ab256ef001 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -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]) diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index ffd29ab7630..1fb16adad7a 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -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); diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index cc19ac55e5e..5cdf0bd891f 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -1,6 +1,5 @@ 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::source::snippet; @@ -18,7 +17,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 +38,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 +115,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 +145,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 +218,67 @@ 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, + }; + combine_seq(result, || { + if cx.typeck_results().expr_ty(expr).is_never() { + NeverLoopResult::Diverging + } else { + NeverLoopResult::Normal + } + }) } fn never_loop_expr_all<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>( cx: &LateContext<'tcx>, - es: &mut T, - ignore_ids: &mut Vec<HirId>, - 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>, + es: T, + local_labels: &mut Vec<(HirId, bool)>, 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 { diff --git a/clippy_lints/src/methods/iter_out_of_bounds.rs b/clippy_lints/src/methods/iter_out_of_bounds.rs new file mode 100644 index 00000000000..79c6d63254b --- /dev/null +++ b/clippy_lints/src/methods/iter_out_of_bounds.rs @@ -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", + ); +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 5fb0d9f0a57..81223fa8d95 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -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. @@ -4146,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, @@ -4173,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); diff --git a/clippy_lints/src/missing_asserts_for_indexing.rs b/clippy_lints/src/missing_asserts_for_indexing.rs new file mode 100644 index 00000000000..08fec2b8ec8 --- /dev/null +++ b/clippy_lints/src/missing_asserts_for_indexing.rs @@ -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); + } +} diff --git a/clippy_lints/src/raw_strings.rs b/clippy_lints/src/raw_strings.rs index ccabb577cb7..a4fe501f79c 100644 --- a/clippy_lints/src/raw_strings.rs +++ b/clippy_lints/src/raw_strings.rs @@ -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) +} diff --git a/clippy_lints/src/unit_return_expecting_ord.rs b/clippy_lints/src/unit_return_expecting_ord.rs index dd829ded0d0..de4b8738e35 100644 --- a/clippy_lints/src/unit_return_expecting_ord.rs +++ b/clippy_lints/src/unit_return_expecting_ord.rs @@ -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"] diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 58ae0656db7..26889475522 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -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. diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index a567c5cbdc9..2fb24b5c7ed 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -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"]; diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index dc4ee725681..0b2d1e2b089 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -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 diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index ee5a49a2073..ae8ee371ffa 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -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 diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs index 06fd9529044..dc7756533ac 100644 --- a/clippy_utils/src/ty/type_certainty/mod.rs +++ b/clippy_utils/src/ty/type_certainty/mod.rs @@ -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. diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 844e66728f2..171dea49d19 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -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: true }, + 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 @@ -302,8 +291,7 @@ fn run_ui_cargo() { 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), |config, path, _file_contents| { config.out_dir = canonicalize( std::env::current_dir() diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index cdabe6460cd..b97bb144468 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -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 diff --git a/tests/ui/crashes/ice-360.rs b/tests/ui/crashes/ice-360.rs index 28589e1efed..0d10932b098 100644 --- a/tests/ui/crashes/ice-360.rs +++ b/tests/ui/crashes/ice-360.rs @@ -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, diff --git a/tests/ui/crashes/ice-360.stderr b/tests/ui/crashes/ice-360.stderr index 292b27dd934..73287d231de 100644 --- a/tests/ui/crashes/ice-360.stderr +++ b/tests/ui/crashes/ice-360.stderr @@ -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 | | } @@ -13,7 +27,7 @@ LL | | } = note: `-D clippy::while-let-loop` implied by `-D warnings` error: empty `loop {}` wastes CPU cycles - --> $DIR/ice-360.rs:12:9 + --> $DIR/ice-360.rs:13:9 | LL | loop {} | ^^^^^^^ @@ -21,5 +35,5 @@ LL | loop {} = help: you should either use `panic!()` or add `std::thread::sleep(..);` to the loop body = note: `-D clippy::empty-loop` implied by `-D warnings` -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors diff --git a/tests/ui/empty_loop.rs b/tests/ui/empty_loop.rs index 54e8fb4907c..be347563135 100644 --- a/tests/ui/empty_loop.rs +++ b/tests/ui/empty_loop.rs @@ -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") } diff --git a/tests/ui/empty_loop.stderr b/tests/ui/empty_loop.stderr index 7602412334b..8594f26036e 100644 --- a/tests/ui/empty_loop.stderr +++ b/tests/ui/empty_loop.stderr @@ -8,7 +8,7 @@ LL | loop {} = note: `-D clippy::empty-loop` implied by `-D warnings` error: empty `loop {}` wastes CPU cycles - --> $DIR/empty_loop.rs:11:9 + --> $DIR/empty_loop.rs:12:9 | LL | loop {} | ^^^^^^^ @@ -16,7 +16,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 {} | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/explicit_auto_deref.fixed b/tests/ui/explicit_auto_deref.fixed index 86bee11eb87..d046a926935 100644 --- a/tests/ui/explicit_auto_deref.fixed +++ b/tests/ui/explicit_auto_deref.fixed @@ -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!(), + }; } diff --git a/tests/ui/explicit_auto_deref.rs b/tests/ui/explicit_auto_deref.rs index 7a505bdf558..3dd08e487f8 100644 --- a/tests/ui/explicit_auto_deref.rs +++ b/tests/ui/explicit_auto_deref.rs @@ -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!(), + }; } diff --git a/tests/ui/explicit_auto_deref.stderr b/tests/ui/explicit_auto_deref.stderr index 34ce188530c..e98f1c40b27 100644 --- a/tests/ui/explicit_auto_deref.stderr +++ b/tests/ui/explicit_auto_deref.stderr @@ -193,10 +193,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 @@ -234,5 +234,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 diff --git a/tests/ui/explicit_iter_loop.fixed b/tests/ui/explicit_iter_loop.fixed index 57292114e38..f08397defa5 100644 --- a/tests/ui/explicit_iter_loop.fixed +++ b/tests/ui/explicit_iter_loop.fixed @@ -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] {} diff --git a/tests/ui/explicit_iter_loop.rs b/tests/ui/explicit_iter_loop.rs index 66280c23843..2ee6825d445 100644 --- a/tests/ui/explicit_iter_loop.rs +++ b/tests/ui/explicit_iter_loop.rs @@ -4,6 +4,7 @@ clippy::similar_names, clippy::needless_borrow, clippy::deref_addrof, + clippy::unnecessary_mut_passed, dead_code )] diff --git a/tests/ui/explicit_iter_loop.stderr b/tests/ui/explicit_iter_loop.stderr index c311096117f..725d9b63cf8 100644 --- a/tests/ui/explicit_iter_loop.stderr +++ b/tests/ui/explicit_iter_loop.stderr @@ -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,132 +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` - -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 diff --git a/tests/ui/iter_out_of_bounds.rs b/tests/ui/iter_out_of_bounds.rs new file mode 100644 index 00000000000..3cfe6e82fc1 --- /dev/null +++ b/tests/ui/iter_out_of_bounds.rs @@ -0,0 +1,71 @@ +//@no-rustfix + +#![deny(clippy::iter_out_of_bounds)] +#![allow(clippy::useless_vec)] + +fn opaque_empty_iter() -> impl Iterator<Item = ()> { + std::iter::empty() +} + +fn main() { + #[allow(clippy::never_loop)] + for _ in [1, 2, 3].iter().skip(4) { + //~^ ERROR: this `.skip()` call skips more items than the iterator will produce + unreachable!(); + } + for (i, _) in [1, 2, 3].iter().take(4).enumerate() { + //~^ ERROR: this `.take()` call takes more items than the iterator will produce + assert!(i <= 2); + } + + #[allow(clippy::needless_borrow)] + for _ in (&&&&&&[1, 2, 3]).iter().take(4) {} + //~^ ERROR: this `.take()` call takes more items than the iterator will produce + + for _ in [1, 2, 3].iter().skip(4) {} + //~^ ERROR: this `.skip()` call skips more items than the iterator will produce + + for _ in [1; 3].iter().skip(4) {} + //~^ ERROR: this `.skip()` call skips more items than the iterator will produce + + // Should not lint + for _ in opaque_empty_iter().skip(1) {} + + for _ in vec![1, 2, 3].iter().skip(4) {} + //~^ ERROR: this `.skip()` call skips more items than the iterator will produce + + for _ in vec![1; 3].iter().skip(4) {} + //~^ ERROR: this `.skip()` call skips more items than the iterator will produce + + let x = [1, 2, 3]; + for _ in x.iter().skip(4) {} + //~^ ERROR: this `.skip()` call skips more items than the iterator will produce + + let n = 4; + for _ in x.iter().skip(n) {} + //~^ ERROR: this `.skip()` call skips more items than the iterator will produce + + let empty = std::iter::empty::<i8>; + + for _ in empty().skip(1) {} + //~^ ERROR: this `.skip()` call skips more items than the iterator will produce + + for _ in empty().take(1) {} + //~^ ERROR: this `.take()` call takes more items than the iterator will produce + + for _ in std::iter::once(1).skip(2) {} + //~^ ERROR: this `.skip()` call skips more items than the iterator will produce + + for _ in std::iter::once(1).take(2) {} + //~^ ERROR: this `.take()` call takes more items than the iterator will produce + + for x in [].iter().take(1) { + //~^ ERROR: this `.take()` call takes more items than the iterator will produce + let _: &i32 = x; + } + + // ok, not out of bounds + for _ in [1].iter().take(1) {} + for _ in [1, 2, 3].iter().take(2) {} + for _ in [1, 2, 3].iter().skip(2) {} +} diff --git a/tests/ui/iter_out_of_bounds.stderr b/tests/ui/iter_out_of_bounds.stderr new file mode 100644 index 00000000000..f235faec8e5 --- /dev/null +++ b/tests/ui/iter_out_of_bounds.stderr @@ -0,0 +1,119 @@ +error: this `.skip()` call skips more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:12:14 + | +LL | for _ in [1, 2, 3].iter().skip(4) { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this operation is useless and will create an empty iterator +note: the lint level is defined here + --> $DIR/iter_out_of_bounds.rs:3:9 + | +LL | #![deny(clippy::iter_out_of_bounds)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this `.take()` call takes more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:16:19 + | +LL | for (i, _) in [1, 2, 3].iter().take(4).enumerate() { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this operation is useless and the returned iterator will simply yield the same items + +error: this `.take()` call takes more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:22:14 + | +LL | for _ in (&&&&&&[1, 2, 3]).iter().take(4) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this operation is useless and the returned iterator will simply yield the same items + +error: this `.skip()` call skips more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:25:14 + | +LL | for _ in [1, 2, 3].iter().skip(4) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this operation is useless and will create an empty iterator + +error: this `.skip()` call skips more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:28:14 + | +LL | for _ in [1; 3].iter().skip(4) {} + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: this operation is useless and will create an empty iterator + +error: this `.skip()` call skips more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:34:14 + | +LL | for _ in vec![1, 2, 3].iter().skip(4) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this operation is useless and will create an empty iterator + +error: this `.skip()` call skips more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:37:14 + | +LL | for _ in vec![1; 3].iter().skip(4) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this operation is useless and will create an empty iterator + +error: this `.skip()` call skips more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:41:14 + | +LL | for _ in x.iter().skip(4) {} + | ^^^^^^^^^^^^^^^^ + | + = note: this operation is useless and will create an empty iterator + +error: this `.skip()` call skips more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:45:14 + | +LL | for _ in x.iter().skip(n) {} + | ^^^^^^^^^^^^^^^^ + | + = note: this operation is useless and will create an empty iterator + +error: this `.skip()` call skips more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:50:14 + | +LL | for _ in empty().skip(1) {} + | ^^^^^^^^^^^^^^^ + | + = note: this operation is useless and will create an empty iterator + +error: this `.take()` call takes more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:53:14 + | +LL | for _ in empty().take(1) {} + | ^^^^^^^^^^^^^^^ + | + = note: this operation is useless and the returned iterator will simply yield the same items + +error: this `.skip()` call skips more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:56:14 + | +LL | for _ in std::iter::once(1).skip(2) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this operation is useless and will create an empty iterator + +error: this `.take()` call takes more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:59:14 + | +LL | for _ in std::iter::once(1).take(2) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this operation is useless and the returned iterator will simply yield the same items + +error: this `.take()` call takes more items than the iterator will produce + --> $DIR/iter_out_of_bounds.rs:62:14 + | +LL | for x in [].iter().take(1) { + | ^^^^^^^^^^^^^^^^^ + | + = note: this operation is useless and the returned iterator will simply yield the same items + +error: aborting due to 14 previous errors + diff --git a/tests/ui/iter_skip_next.fixed b/tests/ui/iter_skip_next.fixed index 310b24a9cde..3e41b363249 100644 --- a/tests/ui/iter_skip_next.fixed +++ b/tests/ui/iter_skip_next.fixed @@ -4,6 +4,7 @@ #![allow(clippy::disallowed_names)] #![allow(clippy::iter_nth)] #![allow(clippy::useless_vec)] +#![allow(clippy::iter_out_of_bounds)] #![allow(unused_mut, dead_code)] extern crate option_helpers; diff --git a/tests/ui/iter_skip_next.rs b/tests/ui/iter_skip_next.rs index 222d6a2a184..6d96441ca96 100644 --- a/tests/ui/iter_skip_next.rs +++ b/tests/ui/iter_skip_next.rs @@ -4,6 +4,7 @@ #![allow(clippy::disallowed_names)] #![allow(clippy::iter_nth)] #![allow(clippy::useless_vec)] +#![allow(clippy::iter_out_of_bounds)] #![allow(unused_mut, dead_code)] extern crate option_helpers; diff --git a/tests/ui/iter_skip_next.stderr b/tests/ui/iter_skip_next.stderr index ca6970b27f1..4ee26e088ce 100644 --- a/tests/ui/iter_skip_next.stderr +++ b/tests/ui/iter_skip_next.stderr @@ -1,5 +1,5 @@ error: called `skip(..).next()` on an iterator - --> $DIR/iter_skip_next.rs:16:28 + --> $DIR/iter_skip_next.rs:17:28 | LL | let _ = some_vec.iter().skip(42).next(); | ^^^^^^^^^^^^^^^^ help: use `nth` instead: `.nth(42)` @@ -7,37 +7,37 @@ LL | let _ = some_vec.iter().skip(42).next(); = note: `-D clippy::iter-skip-next` implied by `-D warnings` error: called `skip(..).next()` on an iterator - --> $DIR/iter_skip_next.rs:17:36 + --> $DIR/iter_skip_next.rs:18:36 | LL | let _ = some_vec.iter().cycle().skip(42).next(); | ^^^^^^^^^^^^^^^^ help: use `nth` instead: `.nth(42)` error: called `skip(..).next()` on an iterator - --> $DIR/iter_skip_next.rs:18:20 + --> $DIR/iter_skip_next.rs:19:20 | LL | let _ = (1..10).skip(10).next(); | ^^^^^^^^^^^^^^^^ help: use `nth` instead: `.nth(10)` error: called `skip(..).next()` on an iterator - --> $DIR/iter_skip_next.rs:19:33 + --> $DIR/iter_skip_next.rs:20:33 | LL | let _ = &some_vec[..].iter().skip(3).next(); | ^^^^^^^^^^^^^^^ help: use `nth` instead: `.nth(3)` error: called `skip(..).next()` on an iterator - --> $DIR/iter_skip_next.rs:27:26 + --> $DIR/iter_skip_next.rs:28:26 | LL | let _: Vec<&str> = sp.skip(1).next().unwrap().split(' ').collect(); | ^^^^^^^^^^^^^^^ help: use `nth` instead: `.nth(1)` error: called `skip(..).next()` on an iterator - --> $DIR/iter_skip_next.rs:29:29 + --> $DIR/iter_skip_next.rs:30:29 | LL | let _: Vec<&str> = s.skip(1).next().unwrap().split(' ').collect(); | ^^^^^^^^^^^^^^^ help: use `nth` instead: `.nth(1)` error: called `skip(..).next()` on an iterator - --> $DIR/iter_skip_next.rs:35:29 + --> $DIR/iter_skip_next.rs:36:29 | LL | let _: Vec<&str> = s.skip(1).next().unwrap().split(' ').collect(); | ^^^^^^^^^^^^^^^ help: use `nth` instead: `.nth(1)` diff --git a/tests/ui/iter_skip_next_unfixable.rs b/tests/ui/iter_skip_next_unfixable.rs index 9c224f4117d..6c98bdc8c88 100644 --- a/tests/ui/iter_skip_next_unfixable.rs +++ b/tests/ui/iter_skip_next_unfixable.rs @@ -1,5 +1,5 @@ #![warn(clippy::iter_skip_next)] -#![allow(dead_code)] +#![allow(dead_code, clippy::iter_out_of_bounds)] //@no-rustfix /// Checks implementation of `ITER_SKIP_NEXT` lint fn main() { diff --git a/tests/ui/iter_skip_zero.fixed b/tests/ui/iter_skip_zero.fixed index 62a83d5905b..447d07100e9 100644 --- a/tests/ui/iter_skip_zero.fixed +++ b/tests/ui/iter_skip_zero.fixed @@ -1,5 +1,5 @@ //@aux-build:proc_macros.rs -#![allow(clippy::useless_vec, unused)] +#![allow(clippy::useless_vec, clippy::iter_out_of_bounds, unused)] #![warn(clippy::iter_skip_zero)] #[macro_use] diff --git a/tests/ui/iter_skip_zero.rs b/tests/ui/iter_skip_zero.rs index c96696dde65..ba63c398180 100644 --- a/tests/ui/iter_skip_zero.rs +++ b/tests/ui/iter_skip_zero.rs @@ -1,5 +1,5 @@ //@aux-build:proc_macros.rs -#![allow(clippy::useless_vec, unused)] +#![allow(clippy::useless_vec, clippy::iter_out_of_bounds, unused)] #![warn(clippy::iter_skip_zero)] #[macro_use] diff --git a/tests/ui/missing_asserts_for_indexing.fixed b/tests/ui/missing_asserts_for_indexing.fixed new file mode 100644 index 00000000000..a96827259f5 --- /dev/null +++ b/tests/ui/missing_asserts_for_indexing.fixed @@ -0,0 +1,121 @@ +#![allow(unused)] +#![warn(clippy::missing_asserts_for_indexing)] + +// ok +fn sum_with_assert(v: &[u8]) -> u8 { + assert!(v.len() > 4); + v[0] + v[1] + v[2] + v[3] + v[4] +} + +// ok +fn sum_with_assert_other_way(v: &[u8]) -> u8 { + assert!(5 <= v.len()); + v[0] + v[1] + v[2] + v[3] + v[4] +} + +// ok +fn sum_with_assert_ge(v: &[u8]) -> u8 { + assert!(v.len() >= 5); + v[0] + v[1] + v[2] + v[3] + v[4] +} + +// ok +fn sum_with_assert_ge_other_way(v: &[u8]) -> u8 { + assert!(4 < v.len()); + v[0] + v[1] + v[2] + v[3] + v[4] +} + +fn sum_with_assert_lt(v: &[u8]) -> u8 { + assert!(v.len() > 4); + v[0] + v[1] + v[2] + v[3] + v[4] + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the +} + +fn sum_with_assert_le(v: &[u8]) -> u8 { + assert!(v.len() > 4); + v[0] + v[1] + v[2] + v[3] + v[4] + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the +} + +fn sum_with_incorrect_assert_len(v: &[u8]) -> u8 { + assert!(v.len() > 4); + v[0] + v[1] + v[2] + v[3] + v[4] + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the +} + +fn sum_with_incorrect_assert_len2(v: &[u8]) -> u8 { + assert!(v.len() > 4); + v[0] + v[1] + v[2] + v[3] + v[4] + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the +} + +// ok, don't lint for single array access +fn single_access(v: &[u8]) -> u8 { + v[0] +} + +// ok +fn subslice_ok(v: &[u8]) { + assert!(v.len() > 3); + let _ = v[0]; + let _ = v[1..4]; +} + +fn subslice_bad(v: &[u8]) { + assert!(v.len() > 3); + let _ = v[0]; + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the + let _ = v[1..4]; +} + +// ok +fn subslice_inclusive_ok(v: &[u8]) { + assert!(v.len() > 4); + let _ = v[0]; + let _ = v[1..=4]; +} + +fn subslice_inclusive_bad(v: &[u8]) { + assert!(v.len() > 4); + let _ = v[0]; + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the + let _ = v[1..=4]; +} + +fn index_different_slices_ok(v1: &[u8], v2: &[u8]) { + assert!(v1.len() > 12); + assert!(v2.len() > 15); + let _ = v1[0] + v1[12]; + let _ = v2[5] + v2[15]; +} + +fn index_different_slices_wrong_len(v1: &[u8], v2: &[u8]) { + assert!(v1.len() > 12); + assert!(v2.len() > 15); + let _ = v1[0] + v1[12]; + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the + let _ = v2[5] + v2[15]; + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the +} +fn index_different_slices_one_wrong_len(v1: &[u8], v2: &[u8]) { + assert!(v1.len() > 12); + assert!(v2.len() > 15); + let _ = v1[0] + v1[12]; + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the + let _ = v2[5] + v2[15]; +} + +fn side_effect() -> &'static [u8] { + &[] +} + +fn index_side_effect_expr() { + let _ = side_effect()[0] + side_effect()[1]; +} + +// ok, single access for different slices +fn index_different_slice_in_same_expr(v1: &[u8], v2: &[u8]) { + let _ = v1[0] + v2[1]; +} + +fn main() {} diff --git a/tests/ui/missing_asserts_for_indexing.rs b/tests/ui/missing_asserts_for_indexing.rs new file mode 100644 index 00000000000..0b4b883acf8 --- /dev/null +++ b/tests/ui/missing_asserts_for_indexing.rs @@ -0,0 +1,121 @@ +#![allow(unused)] +#![warn(clippy::missing_asserts_for_indexing)] + +// ok +fn sum_with_assert(v: &[u8]) -> u8 { + assert!(v.len() > 4); + v[0] + v[1] + v[2] + v[3] + v[4] +} + +// ok +fn sum_with_assert_other_way(v: &[u8]) -> u8 { + assert!(5 <= v.len()); + v[0] + v[1] + v[2] + v[3] + v[4] +} + +// ok +fn sum_with_assert_ge(v: &[u8]) -> u8 { + assert!(v.len() >= 5); + v[0] + v[1] + v[2] + v[3] + v[4] +} + +// ok +fn sum_with_assert_ge_other_way(v: &[u8]) -> u8 { + assert!(4 < v.len()); + v[0] + v[1] + v[2] + v[3] + v[4] +} + +fn sum_with_assert_lt(v: &[u8]) -> u8 { + assert!(v.len() < 5); + v[0] + v[1] + v[2] + v[3] + v[4] + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the +} + +fn sum_with_assert_le(v: &[u8]) -> u8 { + assert!(v.len() <= 5); + v[0] + v[1] + v[2] + v[3] + v[4] + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the +} + +fn sum_with_incorrect_assert_len(v: &[u8]) -> u8 { + assert!(v.len() > 3); + v[0] + v[1] + v[2] + v[3] + v[4] + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the +} + +fn sum_with_incorrect_assert_len2(v: &[u8]) -> u8 { + assert!(v.len() >= 4); + v[0] + v[1] + v[2] + v[3] + v[4] + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the +} + +// ok, don't lint for single array access +fn single_access(v: &[u8]) -> u8 { + v[0] +} + +// ok +fn subslice_ok(v: &[u8]) { + assert!(v.len() > 3); + let _ = v[0]; + let _ = v[1..4]; +} + +fn subslice_bad(v: &[u8]) { + assert!(v.len() >= 3); + let _ = v[0]; + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the + let _ = v[1..4]; +} + +// ok +fn subslice_inclusive_ok(v: &[u8]) { + assert!(v.len() > 4); + let _ = v[0]; + let _ = v[1..=4]; +} + +fn subslice_inclusive_bad(v: &[u8]) { + assert!(v.len() >= 4); + let _ = v[0]; + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the + let _ = v[1..=4]; +} + +fn index_different_slices_ok(v1: &[u8], v2: &[u8]) { + assert!(v1.len() > 12); + assert!(v2.len() > 15); + let _ = v1[0] + v1[12]; + let _ = v2[5] + v2[15]; +} + +fn index_different_slices_wrong_len(v1: &[u8], v2: &[u8]) { + assert!(v1.len() >= 12); + assert!(v2.len() >= 15); + let _ = v1[0] + v1[12]; + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the + let _ = v2[5] + v2[15]; + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the +} +fn index_different_slices_one_wrong_len(v1: &[u8], v2: &[u8]) { + assert!(v1.len() >= 12); + assert!(v2.len() > 15); + let _ = v1[0] + v1[12]; + //~^ ERROR: indexing into a slice multiple times with an `assert` that does not cover the + let _ = v2[5] + v2[15]; +} + +fn side_effect() -> &'static [u8] { + &[] +} + +fn index_side_effect_expr() { + let _ = side_effect()[0] + side_effect()[1]; +} + +// ok, single access for different slices +fn index_different_slice_in_same_expr(v1: &[u8], v2: &[u8]) { + let _ = v1[0] + v2[1]; +} + +fn main() {} diff --git a/tests/ui/missing_asserts_for_indexing.stderr b/tests/ui/missing_asserts_for_indexing.stderr new file mode 100644 index 00000000000..75eb4a35cd4 --- /dev/null +++ b/tests/ui/missing_asserts_for_indexing.stderr @@ -0,0 +1,252 @@ +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> $DIR/missing_asserts_for_indexing.rs:30:5 + | +LL | assert!(v.len() < 5); + | -------------------- help: provide the highest index that is indexed with: `assert!(v.len() > 4)` +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:30:5 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:30:12 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:30:19 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:30:26 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:30:33 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ + = note: asserting the length before indexing will elide bounds checks + = note: `-D clippy::missing-asserts-for-indexing` implied by `-D warnings` + +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> $DIR/missing_asserts_for_indexing.rs:36:5 + | +LL | assert!(v.len() <= 5); + | --------------------- help: provide the highest index that is indexed with: `assert!(v.len() > 4)` +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:36:5 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:36:12 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:36:19 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:36:26 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:36:33 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> $DIR/missing_asserts_for_indexing.rs:42:5 + | +LL | assert!(v.len() > 3); + | -------------------- help: provide the highest index that is indexed with: `assert!(v.len() > 4)` +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:42:5 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:42:12 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:42:19 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:42:26 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:42:33 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> $DIR/missing_asserts_for_indexing.rs:48:5 + | +LL | assert!(v.len() >= 4); + | --------------------- help: provide the highest index that is indexed with: `assert!(v.len() > 4)` +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:48:5 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:48:12 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:48:19 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:48:26 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:48:33 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> $DIR/missing_asserts_for_indexing.rs:66:13 + | +LL | assert!(v.len() >= 3); + | --------------------- help: provide the highest index that is indexed with: `assert!(v.len() > 3)` +LL | let _ = v[0]; + | _____________^ +LL | | +LL | | let _ = v[1..4]; + | |___________________^ + | +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:66:13 + | +LL | let _ = v[0]; + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:68:13 + | +LL | let _ = v[1..4]; + | ^^^^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> $DIR/missing_asserts_for_indexing.rs:80:13 + | +LL | assert!(v.len() >= 4); + | --------------------- help: provide the highest index that is indexed with: `assert!(v.len() > 4)` +LL | let _ = v[0]; + | _____________^ +LL | | +LL | | let _ = v[1..=4]; + | |____________________^ + | +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:80:13 + | +LL | let _ = v[0]; + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:82:13 + | +LL | let _ = v[1..=4]; + | ^^^^^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> $DIR/missing_asserts_for_indexing.rs:95:13 + | +LL | assert!(v1.len() >= 12); + | ----------------------- help: provide the highest index that is indexed with: `assert!(v1.len() > 12)` +LL | assert!(v2.len() >= 15); +LL | let _ = v1[0] + v1[12]; + | ^^^^^^^^^^^^^^ + | +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:95:13 + | +LL | let _ = v1[0] + v1[12]; + | ^^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:95:21 + | +LL | let _ = v1[0] + v1[12]; + | ^^^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> $DIR/missing_asserts_for_indexing.rs:97:13 + | +LL | assert!(v2.len() >= 15); + | ----------------------- help: provide the highest index that is indexed with: `assert!(v2.len() > 15)` +... +LL | let _ = v2[5] + v2[15]; + | ^^^^^^^^^^^^^^ + | +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:97:13 + | +LL | let _ = v2[5] + v2[15]; + | ^^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:97:21 + | +LL | let _ = v2[5] + v2[15]; + | ^^^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> $DIR/missing_asserts_for_indexing.rs:103:13 + | +LL | assert!(v1.len() >= 12); + | ----------------------- help: provide the highest index that is indexed with: `assert!(v1.len() > 12)` +LL | assert!(v2.len() > 15); +LL | let _ = v1[0] + v1[12]; + | ^^^^^^^^^^^^^^ + | +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:103:13 + | +LL | let _ = v1[0] + v1[12]; + | ^^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing.rs:103:21 + | +LL | let _ = v1[0] + v1[12]; + | ^^^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: aborting due to 9 previous errors + diff --git a/tests/ui/missing_asserts_for_indexing_unfixable.rs b/tests/ui/missing_asserts_for_indexing_unfixable.rs new file mode 100644 index 00000000000..4346ed892f2 --- /dev/null +++ b/tests/ui/missing_asserts_for_indexing_unfixable.rs @@ -0,0 +1,49 @@ +#![allow(unused)] +#![warn(clippy::missing_asserts_for_indexing)] + +fn sum(v: &[u8]) -> u8 { + v[0] + v[1] + v[2] + v[3] + v[4] + //~^ ERROR: indexing into a slice multiple times without an `assert` +} + +fn subslice(v: &[u8]) { + let _ = v[0]; + //~^ ERROR: indexing into a slice multiple times without an `assert` + let _ = v[1..4]; +} + +fn variables(v: &[u8]) -> u8 { + let a = v[0]; + //~^ ERROR: indexing into a slice multiple times without an `assert` + let b = v[1]; + let c = v[2]; + a + b + c +} + +fn index_different_slices(v1: &[u8], v2: &[u8]) { + let _ = v1[0] + v1[12]; + let _ = v2[5] + v2[15]; +} + +fn index_different_slices2(v1: &[u8], v2: &[u8]) { + assert!(v1.len() > 12); + let _ = v1[0] + v1[12]; + let _ = v2[5] + v2[15]; +} + +struct Foo<'a> { + v: &'a [u8], + v2: &'a [u8], +} + +fn index_struct_field(f: &Foo<'_>) { + let _ = f.v[0] + f.v[1]; + //~^ ERROR: indexing into a slice multiple times without an `assert` +} + +fn index_struct_different_fields(f: &Foo<'_>) { + // ok, different fields + let _ = f.v[0] + f.v2[1]; +} + +fn main() {} diff --git a/tests/ui/missing_asserts_for_indexing_unfixable.stderr b/tests/ui/missing_asserts_for_indexing_unfixable.stderr new file mode 100644 index 00000000000..a26524d9a11 --- /dev/null +++ b/tests/ui/missing_asserts_for_indexing_unfixable.stderr @@ -0,0 +1,163 @@ +error: indexing into a slice multiple times without an `assert` + --> $DIR/missing_asserts_for_indexing_unfixable.rs:5:5 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider asserting the length before indexing: `assert!(v.len() > 4);` +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:5:5 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:5:12 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:5:19 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:5:26 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:5:33 + | +LL | v[0] + v[1] + v[2] + v[3] + v[4] + | ^^^^ + = note: asserting the length before indexing will elide bounds checks + = note: `-D clippy::missing-asserts-for-indexing` implied by `-D warnings` + +error: indexing into a slice multiple times without an `assert` + --> $DIR/missing_asserts_for_indexing_unfixable.rs:10:13 + | +LL | let _ = v[0]; + | _____________^ +LL | | +LL | | let _ = v[1..4]; + | |___________________^ + | + = help: consider asserting the length before indexing: `assert!(v.len() > 3);` +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:10:13 + | +LL | let _ = v[0]; + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:12:13 + | +LL | let _ = v[1..4]; + | ^^^^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times without an `assert` + --> $DIR/missing_asserts_for_indexing_unfixable.rs:16:13 + | +LL | let a = v[0]; + | _____________^ +LL | | +LL | | let b = v[1]; +LL | | let c = v[2]; + | |________________^ + | + = help: consider asserting the length before indexing: `assert!(v.len() > 2);` +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:16:13 + | +LL | let a = v[0]; + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:18:13 + | +LL | let b = v[1]; + | ^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:19:13 + | +LL | let c = v[2]; + | ^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times without an `assert` + --> $DIR/missing_asserts_for_indexing_unfixable.rs:24:13 + | +LL | let _ = v1[0] + v1[12]; + | ^^^^^^^^^^^^^^ + | + = help: consider asserting the length before indexing: `assert!(v1.len() > 12);` +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:24:13 + | +LL | let _ = v1[0] + v1[12]; + | ^^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:24:21 + | +LL | let _ = v1[0] + v1[12]; + | ^^^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times without an `assert` + --> $DIR/missing_asserts_for_indexing_unfixable.rs:25:13 + | +LL | let _ = v2[5] + v2[15]; + | ^^^^^^^^^^^^^^ + | + = help: consider asserting the length before indexing: `assert!(v2.len() > 15);` +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:25:13 + | +LL | let _ = v2[5] + v2[15]; + | ^^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:25:21 + | +LL | let _ = v2[5] + v2[15]; + | ^^^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times without an `assert` + --> $DIR/missing_asserts_for_indexing_unfixable.rs:31:13 + | +LL | let _ = v2[5] + v2[15]; + | ^^^^^^^^^^^^^^ + | + = help: consider asserting the length before indexing: `assert!(v2.len() > 15);` +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:31:13 + | +LL | let _ = v2[5] + v2[15]; + | ^^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:31:21 + | +LL | let _ = v2[5] + v2[15]; + | ^^^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times without an `assert` + --> $DIR/missing_asserts_for_indexing_unfixable.rs:40:13 + | +LL | let _ = f.v[0] + f.v[1]; + | ^^^^^^^^^^^^^^^ + | + = help: consider asserting the length before indexing: `assert!(f.v.len() > 1);` +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:40:13 + | +LL | let _ = f.v[0] + f.v[1]; + | ^^^^^^ +note: slice indexed here + --> $DIR/missing_asserts_for_indexing_unfixable.rs:40:22 + | +LL | let _ = f.v[0] + f.v[1]; + | ^^^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: aborting due to 7 previous errors + diff --git a/tests/ui/needless_collect_indirect.rs b/tests/ui/needless_collect_indirect.rs index 9d66c5f255f..b4a536cb159 100644 --- a/tests/ui/needless_collect_indirect.rs +++ b/tests/ui/needless_collect_indirect.rs @@ -260,6 +260,7 @@ mod issue_8553 { let w = v.iter().collect::<Vec<_>>(); //~^ ERROR: avoid using `collect()` when not needed // Do lint + #[allow(clippy::never_loop)] for _ in 0..w.len() { todo!(); } @@ -270,6 +271,7 @@ mod issue_8553 { let v: Vec<usize> = vec.iter().map(|i| i * i).collect(); let w = v.iter().collect::<Vec<_>>(); // Do not lint, because w is used. + #[allow(clippy::never_loop)] for _ in 0..w.len() { todo!(); } @@ -283,6 +285,7 @@ mod issue_8553 { let mut w = v.iter().collect::<Vec<_>>(); //~^ ERROR: avoid using `collect()` when not needed // Do lint + #[allow(clippy::never_loop)] while 1 == w.len() { todo!(); } @@ -293,6 +296,7 @@ mod issue_8553 { let mut v: Vec<usize> = vec.iter().map(|i| i * i).collect(); let mut w = v.iter().collect::<Vec<_>>(); // Do not lint, because w is used. + #[allow(clippy::never_loop)] while 1 == w.len() { todo!(); } @@ -306,6 +310,7 @@ mod issue_8553 { let mut w = v.iter().collect::<Vec<_>>(); //~^ ERROR: avoid using `collect()` when not needed // Do lint + #[allow(clippy::never_loop)] while let Some(i) = Some(w.len()) { todo!(); } @@ -316,6 +321,7 @@ mod issue_8553 { let mut v: Vec<usize> = vec.iter().map(|i| i * i).collect(); let mut w = v.iter().collect::<Vec<_>>(); // Do not lint, because w is used. + #[allow(clippy::never_loop)] while let Some(i) = Some(w.len()) { todo!(); } diff --git a/tests/ui/needless_collect_indirect.stderr b/tests/ui/needless_collect_indirect.stderr index 9337a741242..47048b74125 100644 --- a/tests/ui/needless_collect_indirect.stderr +++ b/tests/ui/needless_collect_indirect.stderr @@ -230,11 +230,12 @@ help: take the original Iterator's count instead of collecting it and finding th LL ~ LL | LL | // Do lint +LL | #[allow(clippy::never_loop)] LL ~ for _ in 0..v.iter().count() { | error: avoid using `collect()` when not needed - --> $DIR/needless_collect_indirect.rs:283:30 + --> $DIR/needless_collect_indirect.rs:285:30 | LL | let mut w = v.iter().collect::<Vec<_>>(); | ^^^^^^^ @@ -247,11 +248,12 @@ help: take the original Iterator's count instead of collecting it and finding th LL ~ LL | LL | // Do lint +LL | #[allow(clippy::never_loop)] LL ~ while 1 == v.iter().count() { | error: avoid using `collect()` when not needed - --> $DIR/needless_collect_indirect.rs:306:30 + --> $DIR/needless_collect_indirect.rs:310:30 | LL | let mut w = v.iter().collect::<Vec<_>>(); | ^^^^^^^ @@ -264,6 +266,7 @@ help: take the original Iterator's count instead of collecting it and finding th LL ~ LL | LL | // Do lint +LL | #[allow(clippy::never_loop)] LL ~ while let Some(i) = Some(v.iter().count()) { | diff --git a/tests/ui/needless_raw_string.fixed b/tests/ui/needless_raw_string.fixed index 4db375178b4..85549810513 100644 --- a/tests/ui/needless_raw_string.fixed +++ b/tests/ui/needless_raw_string.fixed @@ -9,8 +9,13 @@ fn main() { b"aaa"; br#""aaa""#; br#"\s"#; - // currently disabled: https://github.com/rust-lang/rust/issues/113333 - // cr#"aaa"#; - // cr#""aaa""#; - // cr#"\s"#; + c"aaa"; + cr#""aaa""#; + cr#"\s"#; + + " + a + multiline + string + "; } diff --git a/tests/ui/needless_raw_string.rs b/tests/ui/needless_raw_string.rs index 59c75fda41b..06d49730387 100644 --- a/tests/ui/needless_raw_string.rs +++ b/tests/ui/needless_raw_string.rs @@ -9,8 +9,13 @@ fn main() { br#"aaa"#; br#""aaa""#; br#"\s"#; - // currently disabled: https://github.com/rust-lang/rust/issues/113333 - // cr#"aaa"#; - // cr#""aaa""#; - // cr#"\s"#; + cr#"aaa"#; + cr#""aaa""#; + cr#"\s"#; + + r#" + a + multiline + string + "#; } diff --git a/tests/ui/needless_raw_string.stderr b/tests/ui/needless_raw_string.stderr index ddc36af2e72..9c417fe1699 100644 --- a/tests/ui/needless_raw_string.stderr +++ b/tests/ui/needless_raw_string.stderr @@ -2,15 +2,57 @@ error: unnecessary raw string literal --> $DIR/needless_raw_string.rs:6:5 | LL | r#"aaa"#; - | ^^^^^^^^ help: try: `"aaa"` + | ^^^^^^^^ | = note: `-D clippy::needless-raw-strings` implied by `-D warnings` +help: try + | +LL - r#"aaa"#; +LL + "aaa"; + | error: unnecessary raw string literal --> $DIR/needless_raw_string.rs:9:5 | LL | br#"aaa"#; - | ^^^^^^^^^ help: try: `b"aaa"` + | ^^^^^^^^^ + | +help: try + | +LL - br#"aaa"#; +LL + b"aaa"; + | + +error: unnecessary raw string literal + --> $DIR/needless_raw_string.rs:12:5 + | +LL | cr#"aaa"#; + | ^^^^^^^^^ + | +help: try + | +LL - cr#"aaa"#; +LL + c"aaa"; + | + +error: unnecessary raw string literal + --> $DIR/needless_raw_string.rs:16:5 + | +LL | / r#" +LL | | a +LL | | multiline +LL | | string +LL | | "#; + | |______^ + | +help: try + | +LL ~ " +LL | a +LL | multiline +LL | string +LL ~ "; + | -error: aborting due to 2 previous errors +error: aborting due to 4 previous errors diff --git a/tests/ui/needless_raw_string_hashes.fixed b/tests/ui/needless_raw_string_hashes.fixed index 84902157ab4..e980adeeff4 100644 --- a/tests/ui/needless_raw_string_hashes.fixed +++ b/tests/ui/needless_raw_string_hashes.fixed @@ -3,17 +3,22 @@ #![feature(c_str_literals)] fn main() { - r#"aaa"#; + r"\aaa"; r#"Hello "world"!"#; r####" "### "## "# "####; r###" "aa" "# "## "###; - br#"aaa"#; + br"\aaa"; br#"Hello "world"!"#; br####" "### "## "# "####; br###" "aa" "# "## "###; - // currently disabled: https://github.com/rust-lang/rust/issues/113333 - // cr#"aaa"#; - // cr##"Hello "world"!"##; - // cr######" "### "## "# "######; - // cr######" "aa" "# "## "######; + cr"\aaa"; + cr#"Hello "world"!"#; + cr####" "### "## "# "####; + cr###" "aa" "# "## "###; + + r" + \a + multiline + string + "; } diff --git a/tests/ui/needless_raw_string_hashes.rs b/tests/ui/needless_raw_string_hashes.rs index 62abae83859..6113c5f25ae 100644 --- a/tests/ui/needless_raw_string_hashes.rs +++ b/tests/ui/needless_raw_string_hashes.rs @@ -3,17 +3,22 @@ #![feature(c_str_literals)] fn main() { - r#"aaa"#; + r#"\aaa"#; r##"Hello "world"!"##; r######" "### "## "# "######; r######" "aa" "# "## "######; - br#"aaa"#; + br#"\aaa"#; br##"Hello "world"!"##; br######" "### "## "# "######; br######" "aa" "# "## "######; - // currently disabled: https://github.com/rust-lang/rust/issues/113333 - // cr#"aaa"#; - // cr##"Hello "world"!"##; - // cr######" "### "## "# "######; - // cr######" "aa" "# "## "######; + cr#"\aaa"#; + cr##"Hello "world"!"##; + cr######" "### "## "# "######; + cr######" "aa" "# "## "######; + + r#" + \a + multiline + string + "#; } diff --git a/tests/ui/needless_raw_string_hashes.stderr b/tests/ui/needless_raw_string_hashes.stderr index 9649d59a71f..ab6a87c5e8d 100644 --- a/tests/ui/needless_raw_string_hashes.stderr +++ b/tests/ui/needless_raw_string_hashes.stderr @@ -1,40 +1,166 @@ error: unnecessary hashes around raw string literal + --> $DIR/needless_raw_string_hashes.rs:6:5 + | +LL | r#"\aaa"#; + | ^^^^^^^^^ + | + = note: `-D clippy::needless-raw-string-hashes` implied by `-D warnings` +help: remove all the hashes around the literal + | +LL - r#"\aaa"#; +LL + r"\aaa"; + | + +error: unnecessary hashes around raw string literal --> $DIR/needless_raw_string_hashes.rs:7:5 | LL | r##"Hello "world"!"##; - | ^^^^^^^^^^^^^^^^^^^^^ help: try: `r#"Hello "world"!"#` + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: remove one hash from both sides of the literal + | +LL - r##"Hello "world"!"##; +LL + r#"Hello "world"!"#; | - = note: `-D clippy::needless-raw-string-hashes` implied by `-D warnings` error: unnecessary hashes around raw string literal --> $DIR/needless_raw_string_hashes.rs:8:5 | LL | r######" "### "## "# "######; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `r####" "### "## "# "####` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove 2 hashes from both sides of the literal + | +LL - r######" "### "## "# "######; +LL + r####" "### "## "# "####; + | error: unnecessary hashes around raw string literal --> $DIR/needless_raw_string_hashes.rs:9:5 | LL | r######" "aa" "# "## "######; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `r###" "aa" "# "## "###` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove 3 hashes from both sides of the literal + | +LL - r######" "aa" "# "## "######; +LL + r###" "aa" "# "## "###; + | + +error: unnecessary hashes around raw string literal + --> $DIR/needless_raw_string_hashes.rs:10:5 + | +LL | br#"\aaa"#; + | ^^^^^^^^^^ + | +help: remove all the hashes around the literal + | +LL - br#"\aaa"#; +LL + br"\aaa"; + | error: unnecessary hashes around raw string literal --> $DIR/needless_raw_string_hashes.rs:11:5 | LL | br##"Hello "world"!"##; - | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `br#"Hello "world"!"#` + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove one hash from both sides of the literal + | +LL - br##"Hello "world"!"##; +LL + br#"Hello "world"!"#; + | error: unnecessary hashes around raw string literal --> $DIR/needless_raw_string_hashes.rs:12:5 | LL | br######" "### "## "# "######; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `br####" "### "## "# "####` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove 2 hashes from both sides of the literal + | +LL - br######" "### "## "# "######; +LL + br####" "### "## "# "####; + | error: unnecessary hashes around raw string literal --> $DIR/needless_raw_string_hashes.rs:13:5 | LL | br######" "aa" "# "## "######; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `br###" "aa" "# "## "###` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove 3 hashes from both sides of the literal + | +LL - br######" "aa" "# "## "######; +LL + br###" "aa" "# "## "###; + | + +error: unnecessary hashes around raw string literal + --> $DIR/needless_raw_string_hashes.rs:14:5 + | +LL | cr#"\aaa"#; + | ^^^^^^^^^^ + | +help: remove all the hashes around the literal + | +LL - cr#"\aaa"#; +LL + cr"\aaa"; + | + +error: unnecessary hashes around raw string literal + --> $DIR/needless_raw_string_hashes.rs:15:5 + | +LL | cr##"Hello "world"!"##; + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove one hash from both sides of the literal + | +LL - cr##"Hello "world"!"##; +LL + cr#"Hello "world"!"#; + | + +error: unnecessary hashes around raw string literal + --> $DIR/needless_raw_string_hashes.rs:16:5 + | +LL | cr######" "### "## "# "######; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove 2 hashes from both sides of the literal + | +LL - cr######" "### "## "# "######; +LL + cr####" "### "## "# "####; + | + +error: unnecessary hashes around raw string literal + --> $DIR/needless_raw_string_hashes.rs:17:5 + | +LL | cr######" "aa" "# "## "######; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove 3 hashes from both sides of the literal + | +LL - cr######" "aa" "# "## "######; +LL + cr###" "aa" "# "## "###; + | + +error: unnecessary hashes around raw string literal + --> $DIR/needless_raw_string_hashes.rs:19:5 + | +LL | / r#" +LL | | \a +LL | | multiline +LL | | string +LL | | "#; + | |______^ + | +help: remove all the hashes around the literal + | +LL ~ r" +LL | \a +LL | multiline +LL | string +LL ~ "; + | -error: aborting due to 6 previous errors +error: aborting due to 13 previous errors diff --git a/tests/ui/never_loop.rs b/tests/ui/never_loop.rs index 001b94391ca..33208364f0e 100644 --- a/tests/ui/never_loop.rs +++ b/tests/ui/never_loop.rs @@ -337,10 +337,8 @@ pub fn test26() { pub fn test27() { loop { - //~^ ERROR: this loop never actually loops 'label: { let x = true; - // Lints because we cannot prove it's always `true` if x { break 'label; } @@ -349,6 +347,51 @@ pub fn test27() { } } +// issue 11004 +pub fn test29() { + loop { + 'label: { + if true { + break 'label; + } + return; + } + } +} + +pub fn test30() { + 'a: loop { + 'b: { + for j in 0..2 { + if j == 1 { + break 'b; + } + } + break 'a; + } + } +} + +pub fn test31(b: bool) { + 'a: loop { + 'b: { + 'c: loop { + //~^ ERROR: this loop never actually loops + if b { break 'c } else { break 'b } + } + continue 'a; + } + break 'a; + } +} + +pub fn test32(b: bool) { + loop { + //~^ ERROR: this loop never actually loops + panic!("oh no"); + } +} + fn main() { test1(); test2(); diff --git a/tests/ui/never_loop.stderr b/tests/ui/never_loop.stderr index 6d6d2c8ac52..37ccd63d27c 100644 --- a/tests/ui/never_loop.stderr +++ b/tests/ui/never_loop.stderr @@ -153,16 +153,22 @@ LL | if let Some(_) = (0..20).next() { | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: this loop never actually loops - --> $DIR/never_loop.rs:339:5 + --> $DIR/never_loop.rs:378:13 + | +LL | / 'c: loop { +LL | | +LL | | if b { break 'c } else { break 'b } +LL | | } + | |_____________^ + +error: this loop never actually loops + --> $DIR/never_loop.rs:389:5 | LL | / loop { LL | | -LL | | 'label: { -LL | | let x = true; -... | -LL | | } +LL | | panic!("oh no"); LL | | } | |_____^ -error: aborting due to 14 previous errors +error: aborting due to 15 previous errors diff --git a/tests/ui/similar_names.rs b/tests/ui/similar_names.rs index c5a941316da..f46af56c6e2 100644 --- a/tests/ui/similar_names.rs +++ b/tests/ui/similar_names.rs @@ -3,6 +3,7 @@ unused, clippy::println_empty_string, clippy::empty_loop, + clippy::never_loop, clippy::diverging_sub_expression, clippy::let_unit_value )] diff --git a/tests/ui/similar_names.stderr b/tests/ui/similar_names.stderr index 059e26d5891..00735617bc2 100644 --- a/tests/ui/similar_names.stderr +++ b/tests/ui/similar_names.stderr @@ -1,84 +1,84 @@ error: binding's name is too similar to existing binding - --> $DIR/similar_names.rs:21:9 + --> $DIR/similar_names.rs:22:9 | LL | let bpple: i32; | ^^^^^ | note: existing binding defined here - --> $DIR/similar_names.rs:19:9 + --> $DIR/similar_names.rs:20:9 | LL | let apple: i32; | ^^^^^ = note: `-D clippy::similar-names` implied by `-D warnings` error: binding's name is too similar to existing binding - --> $DIR/similar_names.rs:24:9 + --> $DIR/similar_names.rs:25:9 | LL | let cpple: i32; | ^^^^^ | note: existing binding defined here - --> $DIR/similar_names.rs:19:9 + --> $DIR/similar_names.rs:20:9 | LL | let apple: i32; | ^^^^^ error: binding's name is too similar to existing binding - --> $DIR/similar_names.rs:49:9 + --> $DIR/similar_names.rs:50:9 | LL | let bluby: i32; | ^^^^^ | note: existing binding defined here - --> $DIR/similar_names.rs:48:9 + --> $DIR/similar_names.rs:49:9 | LL | let blubx: i32; | ^^^^^ error: binding's name is too similar to existing binding - --> $DIR/similar_names.rs:54:9 + --> $DIR/similar_names.rs:55:9 | LL | let coke: i32; | ^^^^ | note: existing binding defined here - --> $DIR/similar_names.rs:52:9 + --> $DIR/similar_names.rs:53:9 | LL | let cake: i32; | ^^^^ error: binding's name is too similar to existing binding - --> $DIR/similar_names.rs:73:9 + --> $DIR/similar_names.rs:74:9 | LL | let xyzeabc: i32; | ^^^^^^^ | note: existing binding defined here - --> $DIR/similar_names.rs:71:9 + --> $DIR/similar_names.rs:72:9 | LL | let xyz1abc: i32; | ^^^^^^^ error: binding's name is too similar to existing binding - --> $DIR/similar_names.rs:78:9 + --> $DIR/similar_names.rs:79:9 | LL | let parsee: i32; | ^^^^^^ | note: existing binding defined here - --> $DIR/similar_names.rs:76:9 + --> $DIR/similar_names.rs:77:9 | LL | let parser: i32; | ^^^^^^ error: binding's name is too similar to existing binding - --> $DIR/similar_names.rs:100:16 + --> $DIR/similar_names.rs:101:16 | LL | bpple: sprang, | ^^^^^^ | note: existing binding defined here - --> $DIR/similar_names.rs:99:16 + --> $DIR/similar_names.rs:100:16 | LL | apple: spring, | ^^^^^^ diff --git a/tests/ui/vec.fixed b/tests/ui/vec.fixed index 3ff2acbe28f..de1eb613ce0 100644 --- a/tests/ui/vec.fixed +++ b/tests/ui/vec.fixed @@ -1,5 +1,10 @@ #![warn(clippy::useless_vec)] -#![allow(clippy::nonstandard_macro_braces, clippy::uninlined_format_args, unused)] +#![allow( + clippy::nonstandard_macro_braces, + clippy::never_loop, + clippy::uninlined_format_args, + unused +)] use std::rc::Rc; diff --git a/tests/ui/vec.rs b/tests/ui/vec.rs index 2ab025f424a..1da10c28880 100644 --- a/tests/ui/vec.rs +++ b/tests/ui/vec.rs @@ -1,5 +1,10 @@ #![warn(clippy::useless_vec)] -#![allow(clippy::nonstandard_macro_braces, clippy::uninlined_format_args, unused)] +#![allow( + clippy::nonstandard_macro_braces, + clippy::never_loop, + clippy::uninlined_format_args, + unused +)] use std::rc::Rc; diff --git a/tests/ui/vec.stderr b/tests/ui/vec.stderr index 5cd6d9fa8c7..a4c2b8984e0 100644 --- a/tests/ui/vec.stderr +++ b/tests/ui/vec.stderr @@ -1,5 +1,5 @@ error: useless use of `vec!` - --> $DIR/vec.rs:30:14 + --> $DIR/vec.rs:35:14 | LL | on_slice(&vec![]); | ^^^^^^^ help: you can use a slice directly: `&[]` @@ -7,109 +7,109 @@ LL | on_slice(&vec![]); = note: `-D clippy::useless-vec` implied by `-D warnings` error: useless use of `vec!` - --> $DIR/vec.rs:32:18 + --> $DIR/vec.rs:37:18 | LL | on_mut_slice(&mut vec![]); | ^^^^^^^^^^^ help: you can use a slice directly: `&mut []` error: useless use of `vec!` - --> $DIR/vec.rs:34:14 + --> $DIR/vec.rs:39:14 | LL | on_slice(&vec![1, 2]); | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` error: useless use of `vec!` - --> $DIR/vec.rs:36:18 + --> $DIR/vec.rs:41:18 | LL | on_mut_slice(&mut vec![1, 2]); | ^^^^^^^^^^^^^^^ help: you can use a slice directly: `&mut [1, 2]` error: useless use of `vec!` - --> $DIR/vec.rs:38:14 + --> $DIR/vec.rs:43:14 | LL | on_slice(&vec![1, 2]); | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` error: useless use of `vec!` - --> $DIR/vec.rs:40:18 + --> $DIR/vec.rs:45:18 | LL | on_mut_slice(&mut vec![1, 2]); | ^^^^^^^^^^^^^^^ help: you can use a slice directly: `&mut [1, 2]` error: useless use of `vec!` - --> $DIR/vec.rs:42:14 + --> $DIR/vec.rs:47:14 | LL | on_slice(&vec!(1, 2)); | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` error: useless use of `vec!` - --> $DIR/vec.rs:44:18 + --> $DIR/vec.rs:49:18 | LL | on_mut_slice(&mut vec![1, 2]); | ^^^^^^^^^^^^^^^ help: you can use a slice directly: `&mut [1, 2]` error: useless use of `vec!` - --> $DIR/vec.rs:46:14 + --> $DIR/vec.rs:51:14 | LL | on_slice(&vec![1; 2]); | ^^^^^^^^^^^ help: you can use a slice directly: `&[1; 2]` error: useless use of `vec!` - --> $DIR/vec.rs:48:18 + --> $DIR/vec.rs:53:18 | LL | on_mut_slice(&mut vec![1; 2]); | ^^^^^^^^^^^^^^^ help: you can use a slice directly: `&mut [1; 2]` error: useless use of `vec!` - --> $DIR/vec.rs:74:19 + --> $DIR/vec.rs:79:19 | LL | let _x: i32 = vec![1, 2, 3].iter().sum(); | ^^^^^^^^^^^^^ help: you can use an array directly: `[1, 2, 3]` error: useless use of `vec!` - --> $DIR/vec.rs:77:17 + --> $DIR/vec.rs:82:17 | LL | let mut x = vec![1, 2, 3]; | ^^^^^^^^^^^^^ help: you can use an array directly: `[1, 2, 3]` error: useless use of `vec!` - --> $DIR/vec.rs:83:22 + --> $DIR/vec.rs:88:22 | LL | let _x: &[i32] = &vec![1, 2, 3]; | ^^^^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2, 3]` error: useless use of `vec!` - --> $DIR/vec.rs:85:14 + --> $DIR/vec.rs:90:14 | LL | for _ in vec![1, 2, 3] {} | ^^^^^^^^^^^^^ help: you can use an array directly: `[1, 2, 3]` error: useless use of `vec!` - --> $DIR/vec.rs:123:20 + --> $DIR/vec.rs:128:20 | LL | for _string in vec![repro!(true), repro!(null)] { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can use an array directly: `[repro!(true), repro!(null)]` error: useless use of `vec!` - --> $DIR/vec.rs:140:18 + --> $DIR/vec.rs:145:18 | LL | in_macro!(1, vec![1, 2], vec![1; 2]); | ^^^^^^^^^^ help: you can use an array directly: `[1, 2]` error: useless use of `vec!` - --> $DIR/vec.rs:140:30 + --> $DIR/vec.rs:145:30 | LL | in_macro!(1, vec![1, 2], vec![1; 2]); | ^^^^^^^^^^ help: you can use an array directly: `[1; 2]` error: useless use of `vec!` - --> $DIR/vec.rs:159:14 + --> $DIR/vec.rs:164:14 | LL | for a in vec![1, 2, 3] { | ^^^^^^^^^^^^^ help: you can use an array directly: `[1, 2, 3]` error: useless use of `vec!` - --> $DIR/vec.rs:163:14 + --> $DIR/vec.rs:168:14 | LL | for a in vec![String::new(), String::new()] { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can use an array directly: `[String::new(), String::new()]` diff --git a/tests/ui/write_literal_2.stderr b/tests/ui/write_literal_2.stderr index 84b302d8d3b..20b1f0e2a45 100644 --- a/tests/ui/write_literal_2.stderr +++ b/tests/ui/write_literal_2.stderr @@ -2,9 +2,14 @@ error: unnecessary raw string literal --> $DIR/write_literal_2.rs:13:24 | LL | writeln!(v, r"{}", r"{hello}"); - | ^^^^^^^^^^ help: try: `"{hello}"` + | ^^^^^^^^^^ | = note: `-D clippy::needless-raw-strings` implied by `-D warnings` +help: try + | +LL - writeln!(v, r"{}", r"{hello}"); +LL + writeln!(v, r"{}", "{hello}"); + | error: literal with an empty format string --> $DIR/write_literal_2.rs:10:23 |
