diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 9b3fd3ddfeb..b8ea424ef34 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -143,6 +143,25 @@ jobs: env: OS: ${{ runner.os }} + metadata_collection: + needs: base + runs-on: ubuntu-latest + + steps: + # Setup + - uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master + with: + github_token: "${{ secrets.github_token }}" + + - name: Checkout + uses: actions/checkout@v3.0.2 + + - name: Install toolchain + run: rustup show active-toolchain + + - name: Test metadata collection + run: cargo collect-metadata + integration_build: needs: changelog runs-on: ubuntu-latest diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index a179bfa7261..ff471207b65 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -26,10 +26,19 @@ jobs: - name: Install remark run: npm install remark-cli remark-lint remark-lint-maximum-line-length remark-preset-lint-recommended remark-gfm + - name: Install mdbook + run: | + mkdir mdbook + curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.4.18/mdbook-v0.4.18-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook + echo `pwd`/mdbook >> $GITHUB_PATH + # Run - name: Check *.md files run: git ls-files -z '*.md' | xargs -0 -n 1 -I {} ./node_modules/.bin/remark {} -u lint -f > /dev/null + - name: Build mdbook + run: mdbook build book + # These jobs doesn't actually test anything, but they're only used to tell # bors the build completed, as there is no practical way to detect when a # workflow is successful listening to webhooks only. diff --git a/.gitignore b/.gitignore index 3e50c45a9b6..503ae3c5090 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ helper.txt *.iml .vscode .idea + +# mdbook generated output +/book/book diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ef338b819d..6aaf12ed932 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3605,6 +3605,7 @@ Released 2018-09-13 [`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match [`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref [`needless_option_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_take +[`needless_parens_on_range_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_parens_on_range_literals [`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value [`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark [`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop @@ -3677,6 +3678,7 @@ Released 2018-09-13 [`rc_buffer`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer [`rc_clone_in_vec_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_clone_in_vec_init [`rc_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex +[`read_zero_byte_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#read_zero_byte_vec [`recursive_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#recursive_format_impl [`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation [`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ab2bd59137..e81e7ceedcb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,19 +13,14 @@ anything, feel free to ask questions on issues or visit the `#clippy` on [Zulip] All contributors are expected to follow the [Rust Code of Conduct]. - [Contributing to Clippy](#contributing-to-clippy) - - [Getting started](#getting-started) - - [High level approach](#high-level-approach) - - [Finding something to fix/improve](#finding-something-to-fiximprove) + - [The Clippy book](#the-clippy-book) + - [High level approach](#high-level-approach) + - [Finding something to fix/improve](#finding-something-to-fiximprove) - [Writing code](#writing-code) - [Getting code-completion for rustc internals to work](#getting-code-completion-for-rustc-internals-to-work) - [IntelliJ Rust](#intellij-rust) - [Rust Analyzer](#rust-analyzer) - [How Clippy works](#how-clippy-works) - - [Syncing changes between Clippy and `rust-lang/rust`](#syncing-changes-between-clippy-and-rust-langrust) - - [Patching git-subtree to work with big repos](#patching-git-subtree-to-work-with-big-repos) - - [Performing the sync from `rust-lang/rust` to Clippy](#performing-the-sync-from-rust-langrust-to-clippy) - - [Performing the sync from Clippy to `rust-lang/rust`](#performing-the-sync-from-clippy-to-rust-langrust) - - [Defining remotes](#defining-remotes) - [Issue and PR triage](#issue-and-pr-triage) - [Bors and Homu](#bors-and-homu) - [Contributions](#contributions) @@ -33,24 +28,29 @@ All contributors are expected to follow the [Rust Code of Conduct]. [Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy [Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct -## Getting started +## The Clippy book -**Note: If this is your first time contributing to Clippy, you should -first read the [Basics docs](doc/basics.md).** +If you're new to Clippy and don't know where to start the [Clippy book] includes +a developer guide and is a good place to start your journey. -### High level approach + +[Clippy book]: book/src + +## High level approach 1. Find something to fix/improve 2. Change code (likely some file in `clippy_lints/src/`) -3. Follow the instructions in the [Basics docs](doc/basics.md) to get set up +3. Follow the instructions in the [Basics docs](book/src/development/basics.md) + to get set up 4. Run `cargo test` in the root directory and wiggle code until it passes 5. Open a PR (also can be done after 2. if you run into problems) -### Finding something to fix/improve +## Finding something to fix/improve -All issues on Clippy are mentored, if you want help simply ask @Manishearth, @flip1995, @phansch -or @llogiq directly by mentioning them in the issue or over on [Zulip]. This list may be out of date. -All currently active mentors can be found [here](https://github.com/rust-lang/highfive/blob/master/highfive/configs/rust-lang/rust-clippy.json#L3) +All issues on Clippy are mentored, if you want help simply ask someone from the +Clippy team directly by mentioning them in the issue or over on [Zulip]. All +currently active team members can be found +[here](https://github.com/rust-lang/highfive/blob/master/highfive/configs/rust-lang/rust-clippy.json#L3) Some issues are easier than others. The [`good-first-issue`] label can be used to find the easy issues. You can use `@rustbot claim` to assign the issue to yourself. @@ -91,20 +91,6 @@ an AST expression). `match_def_path()` in Clippy's `utils` module can also be us [let chains]: https://github.com/rust-lang/rust/pull/94927 [nest-less]: https://github.com/rust-lang/rust-clippy/blob/5e4f0922911536f80d9591180fa604229ac13939/clippy_lints/src/bit_mask.rs#L133-L159 -## Writing code - -Have a look at the [docs for writing lints][adding_lints] for more details. - -If you want to add a new lint or change existing ones apart from bugfixing, it's -also a good idea to give the [stability guarantees][rfc_stability] and -[lint categories][rfc_lint_cats] sections of the [Clippy 1.0 RFC][clippy_rfc] a -quick read. - -[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md -[clippy_rfc]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md -[rfc_stability]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#stability-guarantees -[rfc_lint_cats]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#lint-audit-and-categories - ## Getting code-completion for rustc internals to work ### IntelliJ Rust @@ -205,126 +191,6 @@ That's why the `else_if_without_else` example uses the `register_early_pass` fun [early_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html [late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html -## Syncing changes between Clippy and [`rust-lang/rust`] - -Clippy currently gets built with a pinned nightly version. - -In the `rust-lang/rust` repository, where rustc resides, there's a copy of Clippy -that compiler hackers modify from time to time to adapt to changes in the unstable -API of the compiler. - -We need to sync these changes back to this repository periodically, and the changes -made to this repository in the meantime also need to be synced to the `rust-lang/rust` repository. - -To avoid flooding the `rust-lang/rust` PR queue, this two-way sync process is done -in a bi-weekly basis if there's no urgent changes. This is done starting on the day of -the Rust stable release and then every other week. That way we guarantee that we keep -this repo up to date with the latest compiler API, and every feature in Clippy is available -for 2 weeks in nightly, before it can get to beta. For reference, the first sync -following this cadence was performed the 2020-08-27. - -This process is described in detail in the following sections. For general information -about `subtree`s in the Rust repository see [Rust's `CONTRIBUTING.md`][subtree]. - -### Patching git-subtree to work with big repos - -Currently, there's a bug in `git-subtree` that prevents it from working properly -with the [`rust-lang/rust`] repo. There's an open PR to fix that, but it's stale. -Before continuing with the following steps, we need to manually apply that fix to -our local copy of `git-subtree`. - -You can get the patched version of `git-subtree` from [here][gitgitgadget-pr]. -Put this file under `/usr/lib/git-core` (taking a backup of the previous file) -and make sure it has the proper permissions: - -```bash -sudo cp --backup /path/to/patched/git-subtree.sh /usr/lib/git-core/git-subtree -sudo chmod --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree -sudo chown --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree -``` - -_Note:_ The first time running `git subtree push` a cache has to be built. This -involves going through the complete Clippy history once. For this you have to -increase the stack limit though, which you can do with `ulimit -s 60000`. -Make sure to run the `ulimit` command from the same session you call git subtree. - -_Note:_ If you are a Debian user, `dash` is the shell used by default for scripts instead of `sh`. -This shell has a hardcoded recursion limit set to 1000. In order to make this process work, -you need to force the script to run `bash` instead. You can do this by editing the first -line of the `git-subtree` script and changing `sh` to `bash`. - -### Performing the sync from [`rust-lang/rust`] to Clippy - -Here is a TL;DR version of the sync process (all of the following commands have -to be run inside the `rust` directory): - -1. Clone the [`rust-lang/rust`] repository or make sure it is up to date. -2. Checkout the commit from the latest available nightly. You can get it using `rustup check`. -3. Sync the changes to the rust-copy of Clippy to your Clippy fork: - ```bash - # Make sure to change `your-github-name` to your github name in the following command. Also be - # sure to either use a net-new branch, e.g. `sync-from-rust`, or delete the branch beforehand - # because changes cannot be fast forwarded - git subtree push -P src/tools/clippy git@github.com:your-github-name/rust-clippy sync-from-rust - ``` - - _Note:_ This will directly push to the remote repository. You can also push - to your local copy by replacing the remote address with `/path/to/rust-clippy` - directory. - - _Note:_ Most of the time you have to create a merge commit in the - `rust-clippy` repo (this has to be done in the Clippy repo, not in the - rust-copy of Clippy): - ```bash - git fetch origin && git fetch upstream - git checkout sync-from-rust - git merge upstream/master - ``` -4. Open a PR to `rust-lang/rust-clippy` and wait for it to get merged (to - accelerate the process ping the `@rust-lang/clippy` team in your PR and/or - ~~annoy~~ ask them in the [Zulip] stream.) - -### Performing the sync from Clippy to [`rust-lang/rust`] - -All of the following commands have to be run inside the `rust` directory. - -1. Make sure Clippy itself is up-to-date by following the steps outlined in the previous -section if necessary. - -2. Sync the `rust-lang/rust-clippy` master to the rust-copy of Clippy: - ```bash - git checkout -b sync-from-clippy - git subtree pull -P src/tools/clippy https://github.com/rust-lang/rust-clippy master - ``` -3. Open a PR to [`rust-lang/rust`] - -### Defining remotes - -You may want to define remotes, so you don't have to type out the remote -addresses on every sync. You can do this with the following commands (these -commands still have to be run inside the `rust` directory): - -```bash -# Set clippy-upstream remote for pulls -$ git remote add clippy-upstream https://github.com/rust-lang/rust-clippy -# Make sure to not push to the upstream repo -$ git remote set-url --push clippy-upstream DISABLED -# Set clippy-origin remote to your fork for pushes -$ git remote add clippy-origin git@github.com:your-github-name/rust-clippy -# Set a local remote -$ git remote add clippy-local /path/to/rust-clippy -``` - -You can then sync with the remote names from above, e.g.: - -```bash -$ git subtree push -P src/tools/clippy clippy-local sync-from-rust -``` - -[gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493 -[subtree]: https://rustc-dev-guide.rust-lang.org/contributing.html#external-dependencies-subtree -[`rust-lang/rust`]: https://github.com/rust-lang/rust - ## Issue and PR triage Clippy is following the [Rust triage procedure][triage] for issues and pull diff --git a/Cargo.toml b/Cargo.toml index 3c8b758d53d..e4060ce29a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ rustc_tools_util = { version = "0.2", path = "rustc_tools_util" } [features] deny-warnings = ["clippy_lints/deny-warnings"] integration = ["tempfile"] -internal = ["clippy_lints/internal"] +internal = ["clippy_lints/internal", "tempfile"] [package.metadata.rust-analyzer] # This package uses #[feature(rustc_private)] diff --git a/book/README.md b/book/README.md new file mode 100644 index 00000000000..b652194d0d1 --- /dev/null +++ b/book/README.md @@ -0,0 +1,4 @@ +# Clippy Book + +This is the source for the Clippy Book. See the +[book](src/infrastructure/book.md) for more information. diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 00000000000..93b6641f7e1 --- /dev/null +++ b/book/book.toml @@ -0,0 +1,28 @@ +[book] +authors = ["The Rust Clippy Developers"] +language = "en" +multilingual = false +src = "src" +title = "Clippy Documentation" + +[rust] +edition = "2018" + +[output.html] +edit-url-template = "https://github.com/rust-lang/rust-clippy/edit/master/book/{path}" +git-repository-url = "https://github.com/rust-lang/rust-clippy/tree/master/book" +mathjax-support = true +site-url = "/rust-clippy/" + +[output.html.playground] +editable = true +line-numbers = true + +[output.html.search] +boost-hierarchy = 2 +boost-paragraph = 1 +boost-title = 2 +expand = true +heading-split-level = 2 +limit-results = 20 +use-boolean-and = true diff --git a/book/src/README.md b/book/src/README.md new file mode 100644 index 00000000000..de1f70d7e96 --- /dev/null +++ b/book/src/README.md @@ -0,0 +1,34 @@ +# Clippy + +[![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test%22+event%3Apush+branch%3Aauto) +[![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](#license) + +A collection of lints to catch common mistakes and improve your +[Rust](https://github.com/rust-lang/rust) code. + +[There are over 500 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) + +Lints are divided into categories, each with a default [lint +level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how +much Clippy is supposed to ~~annoy~~ help you by changing the lint level by +category. + +| Category | Description | Default level | +| --------------------- | ----------------------------------------------------------------------------------- | ------------- | +| `clippy::all` | all lints that are on by default (correctness, suspicious, style, complexity, perf) | **warn/deny** | +| `clippy::correctness` | code that is outright wrong or useless | **deny** | +| `clippy::suspicious` | code that is most likely wrong or useless | **warn** | +| `clippy::complexity` | code that does something simple but in a complex way | **warn** | +| `clippy::perf` | code that can be written to run faster | **warn** | +| `clippy::style` | code that should be written in a more idiomatic way | **warn** | +| `clippy::pedantic` | lints which are rather strict or might have false positives | allow | +| `clippy::nursery` | new lints that are still under development | allow | +| `clippy::cargo` | lints for the cargo manifest | allow | | allow | + +More to come, please [file an +issue](https://github.com/rust-lang/rust-clippy/issues) if you have ideas! + +The [lint list](https://rust-lang.github.io/rust-clippy/master/index.html) also +contains "restriction lints", which are for things which are usually not +considered "bad", but may be useful to turn on in specific cases. These should +be used very selectively, if at all. diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 00000000000..0b945faf9b7 --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,23 @@ +# Summary + +[Introduction](README.md) + +- [Installation](installation.md) +- [Usage](usage.md) +- [Configuration](configuration.md) +- [Clippy's Lints](lints.md) +- [Continuous Integration](continuous_integration/README.md) + - [GitHub Actions](continuous_integration/github_actions.md) + - [Travis CI](continuous_integration/travis.md) +- [Development](development/README.md) + - [Basics](development/basics.md) + - [Adding Lints](development/adding_lints.md) + - [Common Tools](development/common_tools_writing_lints.md) + - [Infrastructure](development/infrastructure/README.md) + - [Syncing changes between Clippy and rust-lang/rust](development/infrastructure/sync.md) + - [Backporting Changes](development/infrastructure/backport.md) + - [Updating the Changelog](development/infrastructure/changelog_update.md) + - [Release a New Version](development/infrastructure/release.md) + - [The Clippy Book](development/infrastructure/book.md) + - [Proposals](development/proposals/README.md) + - [Roadmap 2021](development/proposals/roadmap-2021.md) diff --git a/book/src/configuration.md b/book/src/configuration.md new file mode 100644 index 00000000000..6e295ac3181 --- /dev/null +++ b/book/src/configuration.md @@ -0,0 +1,92 @@ +# Configuring Clippy + +> **Note:** The configuration file is unstable and may be deprecated in the future. + +Some lints can be configured in a TOML file named `clippy.toml` or `.clippy.toml`. It contains a +basic `variable = value` mapping eg. + +```toml +avoid-breaking-exported-api = false +blacklisted-names = ["toto", "tata", "titi"] +cognitive-complexity-threshold = 30 +``` + +See the [list of lints](https://rust-lang.github.io/rust-clippy/master/index.html) for more information about which +lints can be configured and the meaning of the variables. + +To deactivate the "for further information visit *lint-link*" message you can define the `CLIPPY_DISABLE_DOCS_LINKS` +environment variable. + +### Allowing/denying lints + +You can add options to your code to `allow`/`warn`/`deny` Clippy lints: + +* the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`) + +* all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`, + `#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive lints prone to false + positives. + +* only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.) + +* `allow`/`warn`/`deny` can be limited to a single function or module using `#[allow(...)]`, etc. + +Note: `allow` means to suppress the lint for your code. With `warn` the lint will only emit a warning, while with `deny` +the lint will emit an error, when triggering for your code. An error causes clippy to exit with an error code, so is +useful in scripts like CI/CD. + +If you do not want to include your lint levels in your code, you can globally enable/disable lints by passing extra +flags to Clippy during the run: + +To allow `lint_name`, run + +```terminal +cargo clippy -- -A clippy::lint_name +``` + +And to warn on `lint_name`, run + +```terminal +cargo clippy -- -W clippy::lint_name +``` + +This also works with lint groups. For example you can run Clippy with warnings for all lints enabled: + +```terminal +cargo clippy -- -W clippy::pedantic +``` + +If you care only about a single lint, you can allow all others and then explicitly warn on the lint(s) you are +interested in: + +```terminal +cargo clippy -- -A clippy::all -W clippy::useless_format -W clippy::... +``` + +### Specifying the minimum supported Rust version + +Projects that intend to support old versions of Rust can disable lints pertaining to newer features by specifying the +minimum supported Rust version (MSRV) in the clippy configuration file. + +```toml +msrv = "1.30.0" +``` + +The MSRV can also be specified as an inner attribute, like below. + +```rust +#![feature(custom_inner_attributes)] +#![clippy::msrv = "1.30.0"] + +fn main() { + ... +} +``` + +You can also omit the patch version when specifying the MSRV, so `msrv = 1.30` +is equivalent to `msrv = 1.30.0`. + +Note: `custom_inner_attributes` is an unstable feature so it has to be enabled explicitly. + +Lints that recognize this configuration option can be +found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv) diff --git a/book/src/continuous_integration/README.md b/book/src/continuous_integration/README.md new file mode 100644 index 00000000000..e5c3673bde4 --- /dev/null +++ b/book/src/continuous_integration/README.md @@ -0,0 +1,18 @@ +# Continuous Integration + +It is recommended to run Clippy on CI with `-Dwarnings`, so that Clippy lints +prevent CI from passing. To enforce errors on warnings on all `cargo` commands +not just `cargo clippy`, you can set the env var `RUSTFLAGS="-Dwarnings"`. + +We recommend to use Clippy from the same toolchain, that you use for compiling +your crate for maximum compatibility. E.g. if your crate is compiled with the +`stable` toolchain, you should also use `stable` Clippy. + +> _Note:_ New Clippy lints are first added to the `nightly` toolchain. If you +> want to help with improving Clippy and have CI resources left, please consider +> adding a `nightly` Clippy check to your CI and report problems like false +> positives back to us. With that we can fix bugs early, before they can get to +> stable. + +This chapter will give an overview on how to use Clippy on different popular CI +providers. diff --git a/book/src/continuous_integration/github_actions.md b/book/src/continuous_integration/github_actions.md new file mode 100644 index 00000000000..42a43ef1380 --- /dev/null +++ b/book/src/continuous_integration/github_actions.md @@ -0,0 +1,21 @@ +# GitHub Actions + +On the GitHub hosted runners, Clippy from the latest stable Rust version comes +pre-installed. So all you have to do is to run `cargo clippy`. + +```yml +on: push +name: Clippy check + +# Make sure CI fails on all warnings, including Clippy lints +env: + RUSTFLAGS: "-Dwarnings" + +jobs: + clippy_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Run Clippy + run: cargo clippy --all-targets --all-features +``` diff --git a/book/src/continuous_integration/travis.md b/book/src/continuous_integration/travis.md new file mode 100644 index 00000000000..85b9ed53dae --- /dev/null +++ b/book/src/continuous_integration/travis.md @@ -0,0 +1,20 @@ +# Travis CI + +You can add Clippy to Travis CI in the same way you use it locally: + +```yml +language: rust +rust: + - stable + - beta +before_script: + - rustup component add clippy +script: + - cargo clippy + # if you want the build job to fail when encountering warnings, use + - cargo clippy -- -D warnings + # in order to also check tests and non-default crate features, use + - cargo clippy --all-targets --all-features -- -D warnings + - cargo test + # etc. +``` diff --git a/book/src/development/README.md b/book/src/development/README.md new file mode 100644 index 00000000000..5cf7201cffa --- /dev/null +++ b/book/src/development/README.md @@ -0,0 +1,43 @@ +# Clippy Development + +Hello fellow Rustacean! If you made it here, you're probably interested in +making Clippy better by contributing to it. In that case, welcome to the +project! + +> _Note:_ If you're just interested in using Clippy, there's nothing to see from +> this point onward and you should return to one of the earlier chapters. + +## Getting started + +If this is your first time contributing to Clippy, you should first read the +[Basics docs](basics.md). This will explain the basics on how to get the source +code and how to compile and test the code. + +## Writing code + +If you have done the basic setup, it's time to start hacking. + +The [Adding lints](adding_lints.md) chapter is a walk through on how to add a +new lint to Clippy. This is also interesting if you just want to fix a lint, +because it also covers how to test lints and gives an overview of the bigger +picture. + +If you want to add a new lint or change existing ones apart from bugfixing, it's +also a good idea to give the [stability guarantees][rfc_stability] and +[lint categories][rfc_lint_cats] sections of the [Clippy 1.0 RFC][clippy_rfc] a +quick read. The lint categories are also described [earlier in this +book](../lints.md). + +> _Note:_ Some higher level things about contributing to Clippy are still +> covered in the [`CONTRIBUTING.md`] document. Some of those will be moved to +> the book over time, like: +> - Finding something to fix +> - IDE setup +> - High level overview on how Clippy works +> - Triage procedure +> - Bors and Homu + +[clippy_rfc]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md +[rfc_stability]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#stability-guarantees +[rfc_lint_cats]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#lint-audit-and-categories +[`CONTRIBUTING.md`]: https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md diff --git a/doc/adding_lints.md b/book/src/development/adding_lints.md similarity index 73% rename from doc/adding_lints.md rename to book/src/development/adding_lints.md index 3e0b1c5c4f7..3da07fcb968 100644 --- a/doc/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -45,9 +45,9 @@ take a look at our [lint naming guidelines][lint_naming]. To get started on this lint you can run `cargo dev new_lint --name=foo_functions --pass=early --category=pedantic` (category will default to nursery if not provided). This command will create two files: `tests/ui/foo_functions.rs` and -`clippy_lints/src/foo_functions.rs`, as well as -[registering the lint](#lint-registration). For cargo lints, two project -hierarchies (fail/pass) will be created by default under `tests/ui-cargo`. +`clippy_lints/src/foo_functions.rs`, as well as [registering the +lint](#lint-registration). For cargo lints, two project hierarchies (fail/pass) +will be created by default under `tests/ui-cargo`. Next, we'll open up these files and add our lint! @@ -58,8 +58,8 @@ Let's write some tests first that we can execute while we iterate on our lint. Clippy uses UI tests for testing. UI tests check that the output of Clippy is exactly as expected. 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 further down. +Note that you don't have to create this file yourself, we'll get to generating +the `.stderr` files further down. We start by opening the test file created at `tests/ui/foo_functions.rs`. @@ -96,61 +96,57 @@ fn main() { } ``` -Now we can run the test with `TESTNAME=foo_functions cargo uitest`, -currently this test is meaningless though. +Now we can run the test with `TESTNAME=foo_functions cargo uitest`, currently +this test is meaningless though. -While we are working on implementing our lint, we can keep running the UI -test. That allows us to check if the output is turning into what we want. +While we are working on implementing our lint, we can keep running the UI test. +That allows us to check if the output is turning into what we want. -Once we are satisfied with the output, we need to run -`cargo dev bless` to update the `.stderr` file for our lint. -Please note that, we should run `TESTNAME=foo_functions cargo uitest` -every time before running `cargo dev bless`. -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 dev bless` for the +Once we are satisfied with the output, we need to run `cargo dev bless` to +update the `.stderr` file for our lint. Please note that, we should run +`TESTNAME=foo_functions cargo uitest` every time before running `cargo dev +bless`. 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 dev bless` for the specific lint you are creating/editing. Note that if the generated files are empty, they should be removed. -Note that you can run multiple test files by specifying a comma separated list: -`TESTNAME=foo_functions,test2,test3`. +> _Note:_ you can run multiple test files by specifying a comma separated list: +> `TESTNAME=foo_functions,test2,test3`. ### Cargo lints -For cargo lints, the process of testing differs in that we are interested in -the `Cargo.toml` manifest file. We also need a minimal crate associated -with that manifest. +For cargo lints, the process of testing differs in that we are interested in the +`Cargo.toml` manifest file. We also need a minimal crate associated with that +manifest. -If our new lint is named e.g. `foo_categories`, after running `cargo dev new_lint` -we will find by default two new crates, each with its manifest file: +If our new lint is named e.g. `foo_categories`, 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. +* `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. +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, and prepending the `TESTNAME` -variable to `cargo uitest` works too. +The process of generating the `.stderr` file is the same, and prepending the +`TESTNAME` variable to `cargo uitest` works too. ## Rustfix tests -If the lint you are working on is making use of structured suggestions, the -test file should include a `// run-rustfix` comment at the top. This will +If the lint you are working on is making use of structured suggestions, the test +file should include a `// run-rustfix` comment at the top. This will additionally run [rustfix] for that test. Rustfix will apply the suggestions -from the lint to the code of the test file and compare that to the contents of -a `.fixed` file. +from the lint to the code of the test file and compare that to the contents of a +`.fixed` file. -Use `cargo dev bless` to automatically generate the -`.fixed` file after running the tests. +Use `cargo dev bless` to automatically generate the `.fixed` file after running +the tests. [rustfix]: https://github.com/rust-lang/rustfix -## Edition 2018 tests - -Some features require the 2018 edition to work (e.g. `async_await`), but -compile-test tests run on the 2015 edition by default. To change this behavior -add `// edition:2018` at the top of the test file (note that it's space-sensitive). - ## Testing manually Manually testing against an example file can be useful if you have added some @@ -166,9 +162,9 @@ implementing our lint now. ## Lint declaration -Let's start by opening the new file created in the `clippy_lints` crate -at `clippy_lints/src/foo_functions.rs`. That's the crate where all the -lint code is. This file has already imported some initial things we will need: +Let's start by opening the new file created in the `clippy_lints` crate at +`clippy_lints/src/foo_functions.rs`. That's the crate where all the lint code +is. This file has already imported some initial things we will need: ```rust use rustc_lint::{EarlyLintPass, EarlyContext}; @@ -178,7 +174,8 @@ use rustc_ast::ast::*; The next step is to update the lint declaration. Lints are declared using the [`declare_clippy_lint!`][declare_clippy_lint] macro, and we just need to update -the auto-generated lint declaration to have a real description, something like this: +the auto-generated lint declaration to have a real description, something like +this: ```rust declare_clippy_lint! { @@ -198,24 +195,25 @@ declare_clippy_lint! { ``` * The section of lines prefixed with `///` constitutes the lint documentation - section. This is the default documentation style and will be displayed - [like this][example_lint_page]. To render and open this documentation locally - in a browser, run `cargo dev serve`. -* The `#[clippy::version]` attribute will be rendered as part of the lint documentation. - The value should be set to the current Rust version that the lint is developed in, - it can be retrieved by running `rustc -vV` in the rust-clippy directory. The version - is listed under *release*. (Use the version without the `-nightly`) suffix. -* `FOO_FUNCTIONS` is the name of our lint. Be sure to follow the - [lint naming guidelines][lint_naming] here when naming your lint. - In short, the name should state the thing that is being checked for and - read well when used with `allow`/`warn`/`deny`. -* `pedantic` sets the lint level to `Allow`. - The exact mapping can be found [here][category_level_mapping] + section. This is the default documentation style and will be displayed [like + this][example_lint_page]. To render and open this documentation locally in a + browser, run `cargo dev serve`. +* The `#[clippy::version]` attribute will be rendered as part of the lint + documentation. The value should be set to the current Rust version that the + lint is developed in, it can be retrieved by running `rustc -vV` in the + rust-clippy directory. The version is listed under *release*. (Use the version + without the `-nightly`) suffix. +* `FOO_FUNCTIONS` is the name of our lint. Be sure to follow the [lint naming + guidelines][lint_naming] here when naming your lint. In short, the name should + state the thing that is being checked for and read well when used with + `allow`/`warn`/`deny`. +* `pedantic` sets the lint level to `Allow`. The exact mapping can be found + [here][category_level_mapping] * The last part should be a text that explains what exactly is wrong with the code -The rest of this file contains an empty implementation for our lint pass, -which in this case is `EarlyLintPass` and should look like this: +The rest of this file contains an empty implementation for our lint pass, which +in this case is `EarlyLintPass` and should look like this: ```rust // clippy_lints/src/foo_functions.rs @@ -324,9 +322,9 @@ impl EarlyLintPass for FooFunctions { Running our UI test should now produce output that contains the lint message. According to [the rustc-dev-guide], the text should be matter of fact and avoid -capitalization and periods, unless multiple sentences are needed. -When code or an identifier must appear in a message or label, it should be -surrounded with single grave accents \`. +capitalization and periods, unless multiple sentences are needed. When code or +an identifier must appear in a message or label, it should be surrounded with +single grave accents \`. [check_fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html#method.check_fn [diagnostics]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/diagnostics.rs @@ -382,8 +380,8 @@ fn is_foo_fn(fn_kind: FnKind<'_>) -> bool { ``` Now we should also run the full test suite with `cargo test`. At this point -running `cargo test` should produce the expected output. Remember to run -`cargo dev bless` to update the `.stderr` file. +running `cargo test` should produce the expected output. Remember to run `cargo +dev bless` to update the `.stderr` file. `cargo test` (as opposed to `cargo uitest`) will also ensure that our lint implementation is not violating any Clippy lints itself. @@ -397,13 +395,16 @@ pass. ## Specifying the lint's minimum supported Rust version (MSRV) -Sometimes a lint makes suggestions that require a certain version of Rust. For example, the `manual_strip` lint suggests -using `str::strip_prefix` and `str::strip_suffix` which is only available after Rust 1.45. In such cases, you need to -ensure that the MSRV configured for the project is >= the MSRV of the required Rust feature. If multiple features are -required, just use the one with a lower MSRV. +Sometimes a lint makes suggestions that require a certain version of Rust. For +example, the `manual_strip` lint suggests using `str::strip_prefix` and +`str::strip_suffix` which is only available after Rust 1.45. In such cases, you +need to ensure that the MSRV configured for the project is >= the MSRV of the +required Rust feature. If multiple features are required, just use the one with +a lower MSRV. -First, add an MSRV alias for the required feature in [`clippy_utils::msrvs`](/clippy_utils/src/msrvs.rs). This can be -accessed later as `msrvs::STR_STRIP_PREFIX`, for example. +First, add an MSRV alias for the required feature in +[`clippy_utils::msrvs`](/clippy_utils/src/msrvs.rs). This can be accessed later +as `msrvs::STR_STRIP_PREFIX`, for example. ```rust msrv_aliases! { @@ -412,8 +413,9 @@ msrv_aliases! { } ``` -In order to access the project-configured MSRV, you need to have an `msrv` field in the LintPass struct, and a -constructor to initialize the field. The `msrv` value is passed to the constructor in `clippy_lints/lib.rs`. +In order to access the project-configured MSRV, you need to have an `msrv` field +in the LintPass struct, and a constructor to initialize the field. The `msrv` +value is passed to the constructor in `clippy_lints/lib.rs`. ```rust pub struct ManualStrip { @@ -472,11 +474,10 @@ If you have trouble implementing your lint, there is also the internal `author` lint to generate Clippy code that detects the offending pattern. It does not work for all of the Rust syntax, but can give a good starting point. -The quickest way to use it, is the -[Rust playground: play.rust-lang.org][author_example]. -Put the code you want to lint into the editor and add the `#[clippy::author]` -attribute above the item. Then run Clippy via `Tools -> Clippy` and you should -see the generated code in the output below. +The quickest way to use it, is the [Rust playground: +play.rust-lang.org][author_example]. Put the code you want to lint into the +editor and add the `#[clippy::author]` attribute above the item. Then run Clippy +via `Tools -> Clippy` and you should see the generated code in the output below. [Here][author_example] is an example on the playground. @@ -487,13 +488,15 @@ you are implementing your lint. ## Print HIR lint -To implement a lint, it's helpful to first understand the internal representation -that rustc uses. Clippy has the `#[clippy::dump]` attribute that prints the -[_High-Level Intermediate Representation (HIR)_] of the item, statement, or -expression that the attribute is attached to. To attach the attribute to expressions -you often need to enable `#![feature(stmt_expr_attributes)]`. +To implement a lint, it's helpful to first understand the internal +representation that rustc uses. Clippy has the `#[clippy::dump]` attribute that +prints the [_High-Level Intermediate Representation (HIR)_] of the item, +statement, or expression that the attribute is attached to. To attach the +attribute to expressions you often need to enable +`#![feature(stmt_expr_attributes)]`. -[Here][print_hir_example] you can find an example, just select _Tools_ and run _Clippy_. +[Here][print_hir_example] you can find an example, just select _Tools_ and run +_Clippy_. [_High-Level Intermediate Representation (HIR)_]: https://rustc-dev-guide.rust-lang.org/hir.html [print_hir_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=daf14db3a7f39ca467cd1b86c34b9afb @@ -518,7 +521,7 @@ declare_clippy_lint! { /// ```rust,ignore /// // A short example of code that triggers the lint /// ``` - /// + /// /// Use instead: /// ```rust,ignore /// // A short example of improved code that doesn't trigger the lint @@ -537,9 +540,9 @@ list][lint_list]. ## Running rustfmt -[Rustfmt] is a tool for formatting Rust code according to style guidelines. -Your code has to be formatted by `rustfmt` before a PR can be merged. -Clippy uses nightly `rustfmt` in the CI. +[Rustfmt] is a tool for formatting Rust code according to style guidelines. Your +code has to be formatted by `rustfmt` before a PR can be merged. Clippy uses +nightly `rustfmt` in the CI. It can be installed via `rustup`: @@ -575,94 +578,105 @@ Before submitting your PR make sure you followed all of the basic requirements: ## Adding configuration to a lint -Clippy supports the configuration of lints values using a `clippy.toml` file in the workspace -directory. Adding a configuration to a lint can be useful for thresholds or to constrain some -behavior that can be seen as a false positive for some users. Adding a configuration is done -in the following steps: +Clippy supports the configuration of lints values using a `clippy.toml` file in +the workspace directory. Adding a configuration to a lint can be useful for +thresholds or to constrain some behavior that can be seen as a false positive +for some users. Adding a configuration is done in the following steps: -1. Adding a new configuration entry to [clippy_lints::utils::conf](/clippy_lints/src/utils/conf.rs) - like this: - ```rust - /// Lint: LINT_NAME. - /// - /// - (configuration_ident: Type = DefaultValue), - ``` - The doc comment is automatically added to the documentation of the listed lints. The default - value will be formatted using the `Debug` implementation of the type. +1. Adding a new configuration entry to + [clippy_lints::utils::conf](/clippy_lints/src/utils/conf.rs) like this: + + ```rust + /// Lint: LINT_NAME. + /// + /// + (configuration_ident: Type = DefaultValue), + ``` + + The doc comment is automatically added to the documentation of the listed + lints. The default value will be formatted using the `Debug` implementation + of the type. 2. Adding the configuration value to the lint impl struct: - 1. This first requires the definition of a lint impl struct. Lint impl structs are usually - generated with the `declare_lint_pass!` macro. This struct needs to be defined manually - to add some kind of metadata to it: - ```rust - // Generated struct definition - declare_lint_pass!(StructName => [ - LINT_NAME - ]); + 1. This first requires the definition of a lint impl struct. Lint impl + structs are usually generated with the `declare_lint_pass!` macro. This + struct needs to be defined manually to add some kind of metadata to it: + ```rust + // Generated struct definition + declare_lint_pass!(StructName => [ + LINT_NAME + ]); - // New manual definition struct - #[derive(Copy, Clone)] - pub struct StructName {} + // New manual definition struct + #[derive(Copy, Clone)] + pub struct StructName {} - impl_lint_pass!(StructName => [ - LINT_NAME - ]); - ``` + impl_lint_pass!(StructName => [ + LINT_NAME + ]); + ``` - 2. Next add the configuration value and a corresponding creation method like this: - ```rust - #[derive(Copy, Clone)] - pub struct StructName { - configuration_ident: Type, - } + 2. Next add the configuration value and a corresponding creation method like + this: + ```rust + #[derive(Copy, Clone)] + pub struct StructName { + configuration_ident: Type, + } - // ... + // ... - impl StructName { - pub fn new(configuration_ident: Type) -> Self { - Self { - configuration_ident, - } - } - } - ``` + impl StructName { + pub fn new(configuration_ident: Type) -> Self { + Self { + configuration_ident, + } + } + } + ``` 3. Passing the configuration value to the lint impl struct: - First find the struct construction in the [clippy_lints lib file](/clippy_lints/src/lib.rs). - The configuration value is now cloned or copied into a local value that is then passed to the - impl struct like this: - ```rust - // Default generated registration: - store.register_*_pass(|| box module::StructName); + First find the struct construction in the [clippy_lints lib + file](/clippy_lints/src/lib.rs). The configuration value is now cloned or + copied into a local value that is then passed to the impl struct like this: - // New registration with configuration value - let configuration_ident = conf.configuration_ident.clone(); - store.register_*_pass(move || box module::StructName::new(configuration_ident)); - ``` + ```rust + // Default generated registration: + store.register_*_pass(|| box module::StructName); - Congratulations the work is almost done. The configuration value can now be accessed - in the linting code via `self.configuration_ident`. + // New registration with configuration value + let configuration_ident = conf.configuration_ident.clone(); + store.register_*_pass(move || box module::StructName::new(configuration_ident)); + ``` + + Congratulations the work is almost done. The configuration value can now be + accessed in the linting code via `self.configuration_ident`. 4. Adding tests: - 1. The default configured value can be tested like any normal lint in [`tests/ui`](/tests/ui). - 2. The configuration itself will be tested separately in [`tests/ui-toml`](/tests/ui-toml). - Simply add a new subfolder with a fitting name. This folder contains a `clippy.toml` file - with the configuration value and a rust file that should be linted by Clippy. The test can - otherwise be written as usual. + 1. The default configured value can be tested like any normal lint in + [`tests/ui`](/tests/ui). + 2. The configuration itself will be tested separately in + [`tests/ui-toml`](/tests/ui-toml). Simply add a new subfolder with a + fitting name. This folder contains a `clippy.toml` file with the + configuration value and a rust file that should be linted by Clippy. The + test can otherwise be written as usual. ## Cheat Sheet Here are some pointers to things you are likely going to need for every lint: * [Clippy utils][utils] - Various helper functions. Maybe the function you need - is already in here ([`is_type_diagnostic_item`], [`implements_trait`], [`snippet`], etc) + is already in here ([`is_type_diagnostic_item`], [`implements_trait`], + [`snippet`], etc) * [Clippy diagnostics][diagnostics] * [Let chains][let-chains] -* [`from_expansion`][from_expansion] and [`in_external_macro`][in_external_macro] +* [`from_expansion`][from_expansion] and + [`in_external_macro`][in_external_macro] * [`Span`][span] * [`Applicability`][applicability] -* [Common tools for writing lints](common_tools_writing_lints.md) helps with common operations -* [The rustc-dev-guide][rustc-dev-guide] explains a lot of internal compiler concepts +* [Common tools for writing lints](common_tools_writing_lints.md) helps with + common operations +* [The rustc-dev-guide][rustc-dev-guide] explains a lot of internal compiler + concepts * [The nightly rustc docs][nightly_docs] which has been linked to throughout this guide diff --git a/doc/basics.md b/book/src/development/basics.md similarity index 75% rename from doc/basics.md rename to book/src/development/basics.md index 57a90a924ec..78c429ea013 100644 --- a/doc/basics.md +++ b/book/src/development/basics.md @@ -1,8 +1,8 @@ # Basics for hacking on Clippy This document explains the basics for hacking on Clippy. Besides others, this -includes how to build and test Clippy. For a more in depth description on -the codebase take a look at [Adding Lints] or [Common Tools]. +includes how to build and test Clippy. For a more in depth description on the +codebase take a look at [Adding Lints] or [Common Tools]. [Adding Lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md [Common Tools]: https://github.com/rust-lang/rust-clippy/blob/master/doc/common_tools_writing_lints.md @@ -62,8 +62,8 @@ TESTNAME="test_" cargo uitest cargo test --test dogfood ``` -If the output of a [UI test] differs from the expected output, you can update the -reference file with: +If the output of a [UI test] differs from the expected output, you can update +the reference file with: ```bash cargo dev bless @@ -72,8 +72,8 @@ cargo dev bless For example, this is necessary, if you fix a typo in an error message of a lint or if you modify a test file to add a test case. -_Note:_ This command may update more files than you intended. In that case only -commit the files you wanted to update. +> _Note:_ This command may update more files than you intended. In that case +> only commit the files you wanted to update. [UI test]: https://rustc-dev-guide.rust-lang.org/tests/adding.html#guide-to-the-ui-tests @@ -96,22 +96,26 @@ cargo dev setup git-hook # (experimental) Setup Clippy to work with IntelliJ-Rust cargo dev setup intellij ``` -More about intellij command usage and reasons [here](../CONTRIBUTING.md#intellij-rust) + +More about intellij command usage and reasons +[here](../CONTRIBUTING.md#intellij-rust) ## lintcheck -`cargo lintcheck` will build and run clippy on a fixed set of crates and generate a log of the results. -You can `git diff` the updated log against its previous version and -see what impact your lint made on a small set of crates. -If you add a new lint, please audit the resulting warnings and make sure -there are no false positives and that the suggestions are valid. + +`cargo lintcheck` will build and run clippy on a fixed set of crates and +generate a log of the results. You can `git diff` the updated log against its +previous version and see what impact your lint made on a small set of crates. +If you add a new lint, please audit the resulting warnings and make sure there +are no false positives and that the suggestions are valid. Refer to the tools [README] for more details. [README]: https://github.com/rust-lang/rust-clippy/blob/master/lintcheck/README.md + ## PR -We follow a rustc no merge-commit policy. -See . +We follow a rustc no merge-commit policy. See +. ## Common Abbreviations @@ -126,27 +130,34 @@ See . | HIR | High-Level Intermediate Representation | | TCX | Type context | -This is a concise list of abbreviations that can come up during Clippy development. An extensive -general list can be found in the [rustc-dev-guide glossary][glossary]. Always feel free to ask if -an abbreviation or meaning is unclear to you. +This is a concise list of abbreviations that can come up during Clippy +development. An extensive general list can be found in the [rustc-dev-guide +glossary][glossary]. Always feel free to ask if an abbreviation or meaning is +unclear to you. ## Install from source -If you are hacking on Clippy and want to install it from source, do the following: +If you are hacking on Clippy and want to install it from source, do the +following: -First, take note of the toolchain [override](https://rust-lang.github.io/rustup/overrides.html) in `/rust-toolchain`. -We will use this override to install Clippy into the right toolchain. +First, take note of the toolchain +[override](https://rust-lang.github.io/rustup/overrides.html) in +`/rust-toolchain`. We will use this override to install Clippy into the right +toolchain. -> Tip: You can view the active toolchain for the current directory with `rustup show active-toolchain`. +> Tip: You can view the active toolchain for the current directory with `rustup +> show active-toolchain`. -From the Clippy project root, run the following command to build the Clippy binaries and copy them into the -toolchain directory. This will override the currently installed Clippy component. +From the Clippy project root, run the following command to build the Clippy +binaries and copy them into the toolchain directory. This will override the +currently installed Clippy component. ```terminal cargo build --release --bin cargo-clippy --bin clippy-driver -Zunstable-options --out-dir "$(rustc --print=sysroot)/bin" ``` -Now you may run `cargo clippy` in any project, using the toolchain where you just installed Clippy. +Now you may run `cargo clippy` in any project, using the toolchain where you +just installed Clippy. ```terminal cd my-project @@ -159,16 +170,19 @@ cargo +nightly-2021-07-01 clippy clippy-driver +nightly-2021-07-01 ``` -If you need to restore the default Clippy installation, run the following (from the Clippy project root). +If you need to restore the default Clippy installation, run the following (from +the Clippy project root). ```terminal rustup component remove clippy rustup component add clippy ``` -> **DO NOT** install using `cargo install --path . --force` since this will overwrite rustup -> [proxies](https://rust-lang.github.io/rustup/concepts/proxies.html). That is, `~/.cargo/bin/cargo-clippy` and -> `~/.cargo/bin/clippy-driver` should be hard or soft links to `~/.cargo/bin/rustup`. You can repair these by running -> `rustup update`. +> **DO NOT** install using `cargo install --path . --force` since this will +> overwrite rustup +> [proxies](https://rust-lang.github.io/rustup/concepts/proxies.html). That is, +> `~/.cargo/bin/cargo-clippy` and `~/.cargo/bin/clippy-driver` should be hard or +> soft links to `~/.cargo/bin/rustup`. You can repair these by running `rustup +> update`. [glossary]: https://rustc-dev-guide.rust-lang.org/appendix/glossary.html diff --git a/doc/common_tools_writing_lints.md b/book/src/development/common_tools_writing_lints.md similarity index 76% rename from doc/common_tools_writing_lints.md rename to book/src/development/common_tools_writing_lints.md index 1d1aee0da2c..e1ed89262f6 100644 --- a/doc/common_tools_writing_lints.md +++ b/book/src/development/common_tools_writing_lints.md @@ -18,15 +18,17 @@ Useful Rustc dev guide links: ## Retrieving the type of an expression -Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for example to answer following questions: +Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for +example to answer following questions: - which type does this expression correspond to (using its [`TyKind`][TyKind])? - is it a sized type? - is it a primitive type? - does it implement a trait? -This operation is performed using the [`expr_ty()`][expr_ty] method from the [`TypeckResults`][TypeckResults] struct, -that gives you access to the underlying structure [`Ty`][Ty]. +This operation is performed using the [`expr_ty()`][expr_ty] method from the +[`TypeckResults`][TypeckResults] struct, that gives you access to the underlying +structure [`Ty`][Ty]. Example of use: ```rust @@ -43,8 +45,8 @@ impl LateLintPass<'_> for MyStructLint { } ``` -Similarly in [`TypeckResults`][TypeckResults] methods, you have the [`pat_ty()`][pat_ty] method -to retrieve a type from a pattern. +Similarly in [`TypeckResults`][TypeckResults] methods, you have the +[`pat_ty()`][pat_ty] method to retrieve a type from a pattern. Two noticeable items here: - `cx` is the lint context [`LateContext`][LateContext]. The two most useful @@ -52,12 +54,13 @@ Two noticeable items here: `LateContext::typeck_results`, allowing us to jump to type definitions and other compilation stages such as HIR. - `typeck_results`'s return value is [`TypeckResults`][TypeckResults] and is - created by type checking step, it includes useful information such as types - of expressions, ways to resolve methods and so on. + created by type checking step, it includes useful information such as types of + expressions, ways to resolve methods and so on. ## Checking if an expr is calling a specific method -Starting with an `expr`, you can check whether it is calling a specific method `some_method`: +Starting with an `expr`, you can check whether it is calling a specific method +`some_method`: ```rust impl<'tcx> LateLintPass<'tcx> for MyStructLint { @@ -77,8 +80,9 @@ impl<'tcx> LateLintPass<'tcx> for MyStructLint { ## Checking for a specific type -There are three ways to check if an expression type is a specific type we want to check for. -All of these methods only check for the base type, generic arguments have to be checked separately. +There are three ways to check if an expression type is a specific type we want +to check for. All of these methods only check for the base type, generic +arguments have to be checked separately. ```rust use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; @@ -115,7 +119,8 @@ Prefer using diagnostic items and lang items where possible. ## Checking if a type implements a specific trait -There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither. +There are three ways to do this, depending on if the target trait has a +diagnostic item, lang item or neither. ```rust use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths}; @@ -151,8 +156,9 @@ impl LateLintPass<'_> for MyStructLint { > Prefer using diagnostic and lang items, if the target trait has one. -We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate. -A list of defined paths for Clippy can be found in [paths.rs][paths] +We access lang items through the type context `tcx`. `tcx` is of type +[`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate. A list of defined +paths for Clippy can be found in [paths.rs][paths] ## Checking if a type defines a specific method @@ -182,14 +188,15 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { ## Dealing with macros and expansions Keep in mind that macros are already expanded and desugaring is already applied -to the code representation that you are working with in Clippy. This unfortunately causes a lot of -false positives because macro expansions are "invisible" unless you actively check for them. -Generally speaking, code with macro expansions should just be ignored by Clippy because that code can be -dynamic in ways that are difficult or impossible to see. -Use the following functions to deal with macros: +to the code representation that you are working with in Clippy. This +unfortunately causes a lot of false positives because macro expansions are +"invisible" unless you actively check for them. Generally speaking, code with +macro expansions should just be ignored by Clippy because that code can be +dynamic in ways that are difficult or impossible to see. Use the following +functions to deal with macros: -- `span.from_expansion()`: detects if a span is from macro expansion or desugaring. - Checking this is a common first step in a lint. +- `span.from_expansion()`: detects if a span is from macro expansion or + desugaring. Checking this is a common first step in a lint. ```rust if expr.span.from_expansion() { @@ -198,45 +205,51 @@ Use the following functions to deal with macros: } ``` -- `span.ctxt()`: the span's context represents whether it is from expansion, and if so, which macro call expanded it. - It is sometimes useful to check if the context of two spans are equal. +- `span.ctxt()`: the span's context represents whether it is from expansion, and + if so, which macro call expanded it. It is sometimes useful to check if the + context of two spans are equal. - ```rust - // expands to `1 + 0`, but don't lint - 1 + mac!() - ``` - ```rust - if left.span.ctxt() != right.span.ctxt() { - // the coder most likely cannot modify this expression - return; - } - ``` - Note: Code that is not from expansion is in the "root" context. So any spans where `from_expansion` returns `true` can - be assumed to have the same context. And so just using `span.from_expansion()` is often good enough. + ```rust + // expands to `1 + 0`, but don't lint + 1 + mac!() + ``` + ```rust + if left.span.ctxt() != right.span.ctxt() { + // the coder most likely cannot modify this expression + return; + } + ``` + > Note: Code that is not from expansion is in the "root" context. So any spans + > where `from_expansion` returns `true` can be assumed to have the same + > context. And so just using `span.from_expansion()` is often good enough. -- `in_external_macro(span)`: detect if the given span is from a macro defined in a foreign crate. - If you want the lint to work with macro-generated code, this is the next line of defense to avoid macros - not defined in the current crate. It doesn't make sense to lint code that the coder can't change. +- `in_external_macro(span)`: detect if the given span is from a macro defined in + a foreign crate. If you want the lint to work with macro-generated code, this + is the next line of defense to avoid macros not defined in the current crate. + It doesn't make sense to lint code that the coder can't change. - You may want to use it for example to not start linting in macros from other crates + You may want to use it for example to not start linting in macros from other + crates - ```rust - #[macro_use] - extern crate a_crate_with_macros; + ```rust + #[macro_use] + extern crate a_crate_with_macros; - // `foo` is defined in `a_crate_with_macros` - foo!("bar"); + // `foo` is defined in `a_crate_with_macros` + foo!("bar"); - // if we lint the `match` of `foo` call and test its span - assert_eq!(in_external_macro(cx.sess(), match_span), true); - ``` + // if we lint the `match` of `foo` call and test its span + assert_eq!(in_external_macro(cx.sess(), match_span), true); + ``` -- `span.ctxt()`: the span's context represents whether it is from expansion, and if so, what expanded it +- `span.ctxt()`: the span's context represents whether it is from expansion, and + if so, what expanded it -One thing `SpanContext` is useful for is to check if two spans are in the same context. For example, -in `a == b`, `a` and `b` have the same context. In a `macro_rules!` with `a == $b`, `$b` is expanded to some -expression with a different context from `a`. + One thing `SpanContext` is useful for is to check if two spans are in the same + context. For example, in `a == b`, `a` and `b` have the same context. In a + `macro_rules!` with `a == $b`, `$b` is expanded to some expression with a + different context from `a`. ```rust macro_rules! m { diff --git a/book/src/development/infrastructure/README.md b/book/src/development/infrastructure/README.md new file mode 100644 index 00000000000..3b2a2539996 --- /dev/null +++ b/book/src/development/infrastructure/README.md @@ -0,0 +1,19 @@ +# Infrastructure + +In order to deploy Clippy over `rustup`, some infrastructure is necessary. This +chapter describes the different parts of the Clippy infrastructure that need to +be maintained to make this possible. + +The most important part is the sync between the `rust-lang/rust` repository and +the Clippy repository that takes place every two weeks. This process is +described in the [Syncing changes between Clippy and `rust-lang/rust`](sync.md) +section. + +A new Clippy release is done together with every Rust release, so every six +weeks. The release process is described in the [Release a new Clippy +Version](release.md) section. During a release cycle a changelog entry for the +next release has to be written. The format of that and how to do that is +documented in the [Changelog Update](changelog_update.md) section. + +> _Note:_ The Clippy CI should also be described in this chapter, but for now is +> left as a TODO. diff --git a/doc/backport.md b/book/src/development/infrastructure/backport.md similarity index 100% rename from doc/backport.md rename to book/src/development/infrastructure/backport.md diff --git a/book/src/development/infrastructure/book.md b/book/src/development/infrastructure/book.md new file mode 100644 index 00000000000..b62314c6735 --- /dev/null +++ b/book/src/development/infrastructure/book.md @@ -0,0 +1,42 @@ +# The Clippy Book + +This document explains how to make additions and changes to the Clippy book, the +guide to Clippy that you're reading right now. The Clippy book is formatted with +[Markdown](https://www.markdownguide.org) and generated by +[mdbook](https://github.com/rust-lang/mdBook). + +- [Get mdbook](#get-mdbook) +- [Make changes](#make-changes) + +## Get mdbook + +While not strictly necessary since the book source is simply Markdown text +files, having mdbook locally will allow you to build, test and serve the book +locally to view changes before you commit them to the repository. You likely +already have `cargo` installed, so the easiest option is to simply: + +```shell +cargo install mdbook +``` + +See the mdbook [installation](https://github.com/rust-lang/mdBook#installation) +instructions for other options. + +## Make changes + +The book's +[src](https://github.com/joshrotenberg/rust-clippy/tree/clippy_guide/book/src) +directory contains all of the markdown files used to generate the book. If you +want to see your changes in real time, you can use the mdbook `serve` command to +run a web server locally that will automatically update changes as they are +made. From the top level of your `rust-clippy` directory: + +```shell +mdbook serve book --open +``` + +Then navigate to `http://localhost:3000` to see the generated book. While the +server is running, changes you make will automatically be updated. + +For more information, see the mdbook +[guide](https://rust-lang.github.io/mdBook/). diff --git a/doc/changelog_update.md b/book/src/development/infrastructure/changelog_update.md similarity index 78% rename from doc/changelog_update.md rename to book/src/development/infrastructure/changelog_update.md index 0cbad2c0924..e560f4c6a3e 100644 --- a/doc/changelog_update.md +++ b/book/src/development/infrastructure/changelog_update.md @@ -1,6 +1,6 @@ # Changelog Update -If you want to help with updating the [changelog][changelog], you're in the right place. +If you want to help with updating the [changelog], you're in the right place. ## When to update @@ -11,8 +11,8 @@ Rust release. For that purpose, the changelog is ideally updated during the week before an upcoming stable release. You can find the release dates on the [Rust Forge][forge]. -Most of the time we only need to update the changelog for minor Rust releases. It's -been very rare that Clippy changes were included in a patch release. +Most of the time we only need to update the changelog for minor Rust releases. +It's been very rare that Clippy changes were included in a patch release. ## Changelog update walkthrough @@ -24,10 +24,12 @@ be found in the `tools` directory of the Rust repository. Depending on the current time and what exactly you want to update, the following bullet points might be helpful: -* When writing the release notes for the **upcoming stable release** you need to check - out the Clippy commit of the current Rust `beta` branch. [Link][rust_beta_tools] -* When writing the release notes for the **upcoming beta release**, you need to check - out the Clippy commit of the current Rust `master`. [Link][rust_master_tools] +* When writing the release notes for the **upcoming stable release** you need to + check out the Clippy commit of the current Rust `beta` branch. + [Link][rust_beta_tools] +* When writing the release notes for the **upcoming beta release**, you need to + check out the Clippy commit of the current Rust `master`. + [Link][rust_master_tools] * When writing the (forgotten) release notes for a **past stable release**, you need to check out the Rust release tag of the stable release. [Link][rust_stable_tools] @@ -35,7 +37,8 @@ bullet points might be helpful: Usually you want to write the changelog of the **upcoming stable release**. Make sure though, that `beta` was already branched in the Rust repository. -To find the commit hash, issue the following command when in a `rust-lang/rust` checkout: +To find the commit hash, issue the following command when in a `rust-lang/rust` +checkout: ``` git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g" ``` @@ -44,7 +47,9 @@ git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into Once you've got the correct commit range, run - util/fetch_prs_between.sh commit1 commit2 > changes.txt +``` +util/fetch_prs_between.sh commit1 commit2 > changes.txt +``` and open that file in your editor of choice. @@ -54,14 +59,14 @@ already correct in the current changelog. ### 3. Authoring the final changelog The above script should have dumped all the relevant PRs to the file you -specified. It should have filtered out most of the irrelevant PRs -already, but it's a good idea to do a manual cleanup pass where you look for -more irrelevant PRs. If you're not sure about some PRs, just leave them in for -the review and ask for feedback. +specified. It should have filtered out most of the irrelevant PRs already, but +it's a good idea to do a manual cleanup pass where you look for more irrelevant +PRs. If you're not sure about some PRs, just leave them in for the review and +ask for feedback. -With the PRs filtered, you can start to take each PR and move the -`changelog: ` content to `CHANGELOG.md`. Adapt the wording as you see fit but -try to keep it somewhat coherent. +With the PRs filtered, you can start to take each PR and move the `changelog: ` +content to `CHANGELOG.md`. Adapt the wording as you see fit but try to keep it +somewhat coherent. The order should roughly be: diff --git a/doc/release.md b/book/src/development/infrastructure/release.md similarity index 85% rename from doc/release.md rename to book/src/development/infrastructure/release.md index c4f8f989384..0572281803e 100644 --- a/doc/release.md +++ b/book/src/development/infrastructure/release.md @@ -1,7 +1,7 @@ # Release a new Clippy Version -_NOTE: This document is probably only relevant to you, if you're a member of the -Clippy team._ +> _NOTE:_ This document is probably only relevant to you, if you're a member of +> the Clippy team. Clippy is released together with stable Rust releases. The dates for these releases can be found at the [Rust Forge]. This document explains the necessary @@ -13,12 +13,11 @@ steps to create a Clippy release. 4. [Tag the stable commit](#tag-the-stable-commit) 5. [Update `CHANGELOG.md`](#update-changelogmd) -_NOTE: This document is for stable Rust releases, not for point releases. For -point releases, step 1. and 2. should be enough._ +> _NOTE:_ This document is for stable Rust releases, not for point releases. For +> point releases, step 1. and 2. should be enough. [Rust Forge]: https://forge.rust-lang.org/ - ## Remerge the `beta` branch This step is only necessary, if since the last release something was backported @@ -29,7 +28,7 @@ tree of the Clippy repository. To find out if this step is necessary run ```bash -# Assumes that the local master branch is up-to-date +# Assumes that the local master branch of rust-lang/rust-clippy is up-to-date $ git fetch upstream $ git branch master --contains upstream/beta ``` @@ -45,9 +44,8 @@ $ git push origin backport_remerge # This can be pushed to your fork ``` After this, open a PR to the master branch. In this PR, the commit hash of the -`HEAD` of the `beta` branch must exists. In addition to that, no files should -be changed by this PR. - +`HEAD` of the `beta` branch must exists. In addition to that, no files should be +changed by this PR. ## Update the `beta` branch @@ -58,7 +56,8 @@ determined. ```bash # Assuming the current directory corresponds to the Rust repository -$ git checkout beta +$ git fetch upstream +$ git checkout upstream/beta $ BETA_SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g") ``` @@ -72,7 +71,6 @@ $ git reset --hard $BETA_SHA $ git push upstream beta ``` - ## Find the Clippy commit The first step is to tag the Clippy commit, that is included in the stable Rust @@ -85,7 +83,6 @@ $ git checkout 1.XX.0 # XX should be exchanged with the corresponding version $ SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g") ``` - ## Tag the stable commit After finding the Clippy commit, it can be tagged with the release number. @@ -112,10 +109,10 @@ tag. Updating the stable branch from here is as easy as: $ git push upstream rust-1.XX.0:stable # `upstream` is the `rust-lang/rust-clippy` remote ``` -_NOTE: Usually there are no stable backports for Clippy, so this update should -be possible without force pushing or anything like this. If there should have -happened a stable backport, make sure to re-merge those changes just as with the -`beta` branch._ +> _NOTE:_ Usually there are no stable backports for Clippy, so this update +> should be possible without force pushing or anything like this. If there +> should have happened a stable backport, make sure to re-merge those changes +> just as with the `beta` branch. ## Update `CHANGELOG.md` @@ -142,4 +139,4 @@ the following parts: Current stable, released 20YY-MM-DD -> Released 20YY-MM-DD ``` -[how to update the changelog]: https://github.com/rust-lang/rust-clippy/blob/master/doc/changelog_update.md +[how to update the changelog]: changelog_update.md diff --git a/book/src/development/infrastructure/sync.md b/book/src/development/infrastructure/sync.md new file mode 100644 index 00000000000..5a0f7409a2e --- /dev/null +++ b/book/src/development/infrastructure/sync.md @@ -0,0 +1,123 @@ +# Syncing changes between Clippy and [`rust-lang/rust`] + +Clippy currently gets built with a pinned nightly version. + +In the `rust-lang/rust` repository, where rustc resides, there's a copy of +Clippy that compiler hackers modify from time to time to adapt to changes in the +unstable API of the compiler. + +We need to sync these changes back to this repository periodically, and the +changes made to this repository in the meantime also need to be synced to the +`rust-lang/rust` repository. + +To avoid flooding the `rust-lang/rust` PR queue, this two-way sync process is +done in a bi-weekly basis if there's no urgent changes. This is done starting on +the day of the Rust stable release and then every other week. That way we +guarantee that we keep this repo up to date with the latest compiler API, and +every feature in Clippy is available for 2 weeks in nightly, before it can get +to beta. For reference, the first sync following this cadence was performed the +2020-08-27. + +This process is described in detail in the following sections. For general +information about `subtree`s in the Rust repository see [Rust's +`CONTRIBUTING.md`][subtree]. + +## Patching git-subtree to work with big repos + +Currently, there's a bug in `git-subtree` that prevents it from working properly +with the [`rust-lang/rust`] repo. There's an open PR to fix that, but it's +stale. Before continuing with the following steps, we need to manually apply +that fix to our local copy of `git-subtree`. + +You can get the patched version of `git-subtree` from [here][gitgitgadget-pr]. +Put this file under `/usr/lib/git-core` (making a backup of the previous file) +and make sure it has the proper permissions: + +```bash +sudo cp --backup /path/to/patched/git-subtree.sh /usr/lib/git-core/git-subtree +sudo chmod --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree +sudo chown --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree +``` + +> _Note:_ The first time running `git subtree push` a cache has to be built. +> This involves going through the complete Clippy history once. For this you +> have to increase the stack limit though, which you can do with `ulimit -s +> 60000`. Make sure to run the `ulimit` command from the same session you call +> git subtree. + +> _Note:_ If you are a Debian user, `dash` is the shell used by default for +> scripts instead of `sh`. This shell has a hardcoded recursion limit set to +> 1000. In order to make this process work, you need to force the script to run +> `bash` instead. You can do this by editing the first line of the `git-subtree` +> script and changing `sh` to `bash`. + +## Defining remotes + +You may want to define remotes, so you don't have to type out the remote +addresses on every sync. You can do this with the following commands (these +commands still have to be run inside the `rust` directory): + +```bash +# Set clippy-upstream remote for pulls +$ git remote add clippy-upstream https://github.com/rust-lang/rust-clippy +# Make sure to not push to the upstream repo +$ git remote set-url --push clippy-upstream DISABLED +# Set a local remote +$ git remote add clippy-local /path/to/rust-clippy +``` + +> Note: The following sections assume that you have set those remotes with the +> above remote names. + +## Performing the sync from [`rust-lang/rust`] to Clippy + +Here is a TL;DR version of the sync process (all of the following commands have +to be run inside the `rust` directory): + +1. Clone the [`rust-lang/rust`] repository or make sure it is up to date. +2. Checkout the commit from the latest available nightly. You can get it using + `rustup check`. +3. Sync the changes to the rust-copy of Clippy to your Clippy fork: + ```bash + # Make sure to change `your-github-name` to your github name in the following command. Also be + # sure to either use a net-new branch, e.g. `sync-from-rust`, or delete the branch beforehand + # because changes cannot be fast forwarded and you have to run this command again. + git subtree push -P src/tools/clippy clippy-local sync-from-rust + ``` + + > _Note:_ Most of the time you have to create a merge commit in the + > `rust-clippy` repo (this has to be done in the Clippy repo, not in the + > rust-copy of Clippy): + ```bash + git fetch upstream # assuming upstream is the rust-lang/rust remote + git checkout sync-from-rust + git merge upstream/master --no-ff + ``` + > Note: This is one of the few instances where a merge commit is allowed in + > a PR. +4. Bump the nightly version in the Clippy repository by changing the date in the + rust-toolchain file to the current date and committing it with the message: + ```bash + git commit -m "Bump nightly version -> YYYY-MM-DD" + ``` +5. Open a PR to `rust-lang/rust-clippy` and wait for it to get merged (to + accelerate the process ping the `@rust-lang/clippy` team in your PR and/or + ask them in the [Zulip] stream.) + +[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy + +## Performing the sync from Clippy to [`rust-lang/rust`] + +All of the following commands have to be run inside the `rust` directory. + +1. Make sure you have checked out the latest `master` of `rust-lang/rust`. +2. Sync the `rust-lang/rust-clippy` master to the rust-copy of Clippy: + ```bash + git checkout -b sync-from-clippy + git subtree pull -P src/tools/clippy clippy-upstream master + ``` +3. Open a PR to [`rust-lang/rust`] + +[gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493 +[subtree]: https://rustc-dev-guide.rust-lang.org/contributing.html#external-dependencies-subtree +[`rust-lang/rust`]: https://github.com/rust-lang/rust diff --git a/book/src/development/proposals/README.md b/book/src/development/proposals/README.md new file mode 100644 index 00000000000..78fe34ebf8f --- /dev/null +++ b/book/src/development/proposals/README.md @@ -0,0 +1,11 @@ +# Proposals + +This chapter is about accepted proposals for changes that should be worked on in +or around Clippy in the long run. + +Besides adding more and more lints and improve the lints that Clippy already +has, Clippy is also interested in making the experience of its users, developers +and maintainers better over time. Projects that address bigger picture things +like this usually take more time and it is useful to have a proposal for those +first. This is the place where such proposals are collected, so that we can +refer to them when working on them. diff --git a/doc/roadmap-2021.md b/book/src/development/proposals/roadmap-2021.md similarity index 100% rename from doc/roadmap-2021.md rename to book/src/development/proposals/roadmap-2021.md diff --git a/book/src/installation.md b/book/src/installation.md new file mode 100644 index 00000000000..b2a28d0be62 --- /dev/null +++ b/book/src/installation.md @@ -0,0 +1,24 @@ +# Installation + +If you're using `rustup` to install and manage you're Rust toolchains, Clippy is +usually **already installed**. In that case you can skip this chapter and go to +the [Usage] chapter. + +> Note: If you used the `minimal` profile when installing a Rust toolchain, +> Clippy is not automatically installed. + +## Using Rustup + +If Clippy was not installed for a toolchain, it can be installed with + +``` +$ rustup component add clippy [--toolchain=] +``` + +## From Source + +Take a look at the [Basics] chapter in the Clippy developer guide to find step +by step instructions on how to build and install Clippy from source. + +[Basics]: development/basics.md#install-from-source +[Usage]: usage.md diff --git a/book/src/lints.md b/book/src/lints.md new file mode 100644 index 00000000000..35e30960b56 --- /dev/null +++ b/book/src/lints.md @@ -0,0 +1,105 @@ +# Clippy's Lints + +Clippy offers a bunch of additional lints, to help its users write more correct +and idiomatic Rust code. A full list of all lints, that can be filtered by +category, lint level or keywords, can be found in the [Clippy lint +documentation]. + +This chapter will give an overview of the different lint categories, which kind +of lints they offer and recommended actions when you should see a lint out of +that category. For examples, see the [Clippy lint documentation] and filter by +category. + +The different lint groups were defined in the [Clippy 1.0 RFC]. + +## Correctness + +The `clippy::correctness` group is the only lint group in Clippy which lints are +deny-by-default and abort the compilation when triggered. This is for good +reason: If you see a `correctness` lint, it means that your code is outright +wrong or useless and you should try to fix it. + +Lints in this category are carefully picked and should be free of false +positives. So just `#[allow]`ing those lints is not recommended. + +## Suspicious + +The `clippy::suspicious` group is similar to the correctness lints in that it +contains lints that trigger on code that is really _sus_ and should be fixed. As +opposed to correctness lints, it might be possible that the linted code is +intentionally written like it is. + +It is still recommended to fix code that is linted by lints out of this group +instead of `#[allow]`ing the lint. In case you intentionally have written code +that offends the lint you should specifically and locally `#[allow]` the lint +and add give a reason why the code is correct as written. + +## Complexity + +The `clippy::complexity` group offers lints that give you suggestions on how to +simplify your code. It mostly focuses on code that can be written in a shorter +and more readable way, while preserving the semantics. + +If you should see a complexity lint, it usually means that you can remove or +replace some code and it is recommended to do so. However, if you need the more +complex code for some expressiveness reason, it is recommended to allow +complexity lints on a case-by-case basis. + +## Perf + +The `clippy::perf` group gives you suggestions on how you can increase the +performance of your code. Those lints are mostly about code that the compiler +can't trivially optimize, but has to be written in a slightly different way to +make the optimizer's job easier. + +Perf lints are usually easy to apply and it is recommended to do so. + +## Style + +The `clippy::style` group is mostly about writing idiomatic code. Because style +is subjective, this lint group is the most opinionated warn-by-default group in +Clippy. + +If you see a style lint, applying the suggestion usually makes your code more +readable and idiomatic. But because we know that this is opinionated, feel free +to sprinkle `#[allow]`s for style lints in your code or `#![allow]` a style lint +on your whole crate if you disagree with the suggested style completely. + +## Pedantic + +The `clippy::pedantic` group makes Clippy even more _pedantic_. You can enable +the whole group with `#![warn(clippy::pedantic)]` in the `lib.rs`/`main.rs` of +your crate. This lint group is for Clippy power users that want an in depth +check of their code. + +> _Note:_ Instead of enabling the whole group (like Clippy itself does), you may +> want to cherry-pick lints out of the pedantic group. + +If you enable this group, expect to also use `#[allow]` attributes generously +throughout your code. Lints in this group are designed to be pedantic and false +positives sometimes are intentional in order to prevent false negatives. + +## Restriction + +The `clippy::restriction` group contains lints that will _restrict_ you from +using certain parts of the Rust language. It is **not** recommended to enable +the whole group, but rather cherry-pick lints that are useful for your code base +and your use case. + +> _Note:_ Clippy will produce a warning if it finds a +> `#![warn(clippy::restriction)]` attribute in your code! + +Lints from this group will restrict you in some way. If you enable a restriction +lint for your crate it is recommended to also fix code that this lint triggers +on. However, those lints are really strict by design and you might want to +`#[allow]` them in some special cases, with a comment justifying that. + +## Cargo + +The `clippy::cargo` group gives you suggestions on how to improve your +`Cargo.toml` file. This might be especially interesting if you want to publish +your crate and are not sure if you have all useful information in your +`Cargo.toml`. + +[Clippy lint documentation]: https://rust-lang.github.io/rust-clippy/ +[Clippy 1.0 RFC]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#lint-audit-and-categories diff --git a/book/src/usage.md b/book/src/usage.md new file mode 100644 index 00000000000..337680aa313 --- /dev/null +++ b/book/src/usage.md @@ -0,0 +1,151 @@ +# Usage + +This chapter describes how to use Clippy to get the most out of it. Clippy can +be used as a `cargo` subcommand or, like `rustc`, directly with the +`clippy-driver` binary. + +> _Note:_ This chapter assumes that you have Clippy installed already. If you're +> not sure, take a look at the [Installation] chapter. + +## Cargo subcommand + +The easiest and most common way to run Clippy is through `cargo`. To do that, +just run + +```bash +cargo clippy +``` + +### Lint configuration + +The above command will run the default set of lints, which are included in the +lint group `clippy::all`. You might want to use even more lints or you might not +agree with every Clippy lint, and for that there are ways to configure lint +levels. + +> _Note:_ Clippy is meant to be used with a generous sprinkling of +> `#[allow(..)]`s through your code. So if you disagree with a lint, don't feel +> bad disabling them for parts of your code or the whole project. + +#### Command line + +You can configure lint levels on the command line by adding +`-A/W/D clippy::lint_name` like this: + +```bash +cargo clippy -- -Aclippy::style -Wclippy::double_neg -Dclippy::perf +``` + +For [CI] all warnings can be elevated to errors which will inturn fail +the build and cause Clippy to exit with a code other than `0`. + +``` +cargo clippy -- -Dwarnings +``` + +> _Note:_ Adding `-D warnings` will cause your build to fail if **any** warnings +> are found in your code. That includes warnings found by rustc (e.g. +> `dead_code`, etc.). + +For more information on configuring lint levels, see the [rustc documentation]. + +[rustc documentation]: https://doc.rust-lang.org/rustc/lints/levels.html#configuring-warning-levels + +#### Even more lints + +Clippy has lint groups which are allow-by-default. This means, that you will +have to enable the lints in those groups manually. + +For a full list of all lints with their description and examples, please refere +to [Clippy's lint list]. The two most important allow-by-default groups are +described below: + +[Clippy's lint list]: https://rust-lang.github.io/rust-clippy/master/index.html + +##### `clippy::pedantic` + +The first group is the `pedantic` group. This group contains really opinionated +lints, that may have some intentional false positives in order to prevent false +negatives. So while this group is ready to be used in production, you can expect +to sprinkle multiple `#[allow(..)]`s in your code. If you find any false +positives, you're still welcome to report them to us for future improvements. + +> FYI: Clippy uses the whole group to lint itself. + +##### `clippy::restriction` + +The second group is the `restriction` group. This group contains lints that +"restrict" the language in some way. For example the `clippy::unwrap` lint from +this group won't allow you to use `.unwrap()` in your code. You may want to look +through the lints in this group and enable the ones that fit your need. + +> _Note:_ You shouldn't enable the whole lint group, but cherry-pick lints from +> this group. Some lints in this group will even contradict other Clippy lints! + +#### Too many lints + +The most opinionated warn-by-default group of Clippy is the `clippy::style` +group. Some people prefer to disable this group completely and then cherry-pick +some lints they like from this group. The same is of course possible with every +other of Clippy's lint groups. + +> _Note:_ We try to keep the warn-by-default groups free from false positives +> (FP). If you find that a lint wrongly triggers, please report it in an issue +> (if there isn't an issue for that FP already) + +#### Source Code + +You can configure lint levels in source code the same way you can configure +`rustc` lints: + +```rust +#![allow(clippy::style)] + +#[warn(clippy::double_neg)] +fn main() { + let x = 1; + let y = --x; + // ^^ warning: double negation +} +``` + +### Automatically applying Clippy suggestions + +Clippy can automatically apply some lint suggestions, just like the compiler. + +```terminal +cargo clippy --fix +``` + +### Workspaces + +All the usual workspace options should work with Clippy. For example the +following command will run Clippy on the `example` crate in your workspace: + +```terminal +cargo clippy -p example +``` + +As with `cargo check`, this includes dependencies that are members of the +workspace, like path dependencies. If you want to run Clippy **only** on the +given crate, use the `--no-deps` option like this: + +```terminal +cargo clippy -p example -- --no-deps +``` + +## Using Clippy without `cargo`: `clippy-driver` + +Clippy can also be used in projects that do not use cargo. To do so, run +`clippy-driver` with the same arguments you use for `rustc`. For example: + +```terminal +clippy-driver --edition 2018 -Cpanic=abort foo.rs +``` + +> _Note:_ `clippy-driver` is designed for running Clippy and should not be used +> as a general replacement for `rustc`. `clippy-driver` may produce artifacts +> that are not optimized as expected, for example. + +[Installation]: installation.md +[CI]: continuous_integration diff --git a/clippy_dev/src/lint.rs b/clippy_dev/src/lint.rs index 9e463aa741c..71005449b4d 100644 --- a/clippy_dev/src/lint.rs +++ b/clippy_dev/src/lint.rs @@ -13,7 +13,7 @@ fn exit_if_err(status: io::Result) { } } -pub fn run<'a>(path: &str, args: impl Iterator) { +pub fn run<'a>(path: &str, args: impl Iterator) { let is_file = match fs::metadata(path) { Ok(metadata) => metadata.is_file(), Err(e) => { diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index ee535b1d3be..2c27a0bcaf9 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -2,7 +2,7 @@ // warn on lints, that are included in `rust-lang/rust`s bootstrap #![warn(rust_2018_idioms, unused_lifetimes)] -use clap::{Arg, ArgMatches, Command}; +use clap::{Arg, ArgAction, ArgMatches, Command, PossibleValue}; use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints}; use indoc::indoc; fn main() { @@ -10,15 +10,15 @@ fn main() { match matches.subcommand() { Some(("bless", matches)) => { - bless::bless(matches.is_present("ignore-timestamp")); + bless::bless(matches.contains_id("ignore-timestamp")); }, Some(("fmt", matches)) => { - fmt::run(matches.is_present("check"), matches.is_present("verbose")); + fmt::run(matches.contains_id("check"), matches.contains_id("verbose")); }, Some(("update_lints", matches)) => { - if matches.is_present("print-only") { + if matches.contains_id("print-only") { update_lints::print_lints(); - } else if matches.is_present("check") { + } else if matches.contains_id("check") { update_lints::update(update_lints::UpdateMode::Check); } else { update_lints::update(update_lints::UpdateMode::Change); @@ -26,10 +26,10 @@ fn main() { }, Some(("new_lint", matches)) => { match new_lint::create( - matches.value_of("pass"), - matches.value_of("name"), - matches.value_of("category"), - matches.is_present("msrv"), + matches.get_one::("pass"), + matches.get_one::("name"), + matches.get_one::("category"), + matches.contains_id("msrv"), ) { Ok(_) => update_lints::update(update_lints::UpdateMode::Change), Err(e) => eprintln!("Unable to create lint: {}", e), @@ -37,28 +37,28 @@ fn main() { }, Some(("setup", sub_command)) => match sub_command.subcommand() { Some(("intellij", matches)) => { - if matches.is_present("remove") { + if matches.contains_id("remove") { setup::intellij::remove_rustc_src(); } else { setup::intellij::setup_rustc_src( matches - .value_of("rustc-repo-path") + .get_one::("rustc-repo-path") .expect("this field is mandatory and therefore always valid"), ); } }, Some(("git-hook", matches)) => { - if matches.is_present("remove") { + if matches.contains_id("remove") { setup::git_hook::remove_hook(); } else { - setup::git_hook::install_hook(matches.is_present("force-override")); + setup::git_hook::install_hook(matches.contains_id("force-override")); } }, Some(("vscode-tasks", matches)) => { - if matches.is_present("remove") { + if matches.contains_id("remove") { setup::vscode::remove_tasks(); } else { - setup::vscode::install_tasks(matches.is_present("force-override")); + setup::vscode::install_tasks(matches.contains_id("force-override")); } }, _ => {}, @@ -70,19 +70,19 @@ fn main() { _ => {}, }, Some(("serve", matches)) => { - let port = matches.value_of("port").unwrap().parse().unwrap(); - let lint = matches.value_of("lint"); + let port = *matches.get_one::("port").unwrap(); + let lint = matches.get_one::("lint"); serve::run(port, lint); }, Some(("lint", matches)) => { - let path = matches.value_of("path").unwrap(); - let args = matches.values_of("args").into_iter().flatten(); + let path = matches.get_one::("path").unwrap(); + let args = matches.get_many::("args").into_iter().flatten(); lint::run(path, args); }, Some(("rename_lint", matches)) => { - let old_name = matches.value_of("old_name").unwrap(); - let new_name = matches.value_of("new_name").unwrap_or(old_name); - let uplift = matches.is_present("uplift"); + let old_name = matches.get_one::("old_name").unwrap(); + let new_name = matches.get_one::("new_name").unwrap_or(old_name); + let uplift = matches.contains_id("uplift"); update_lints::rename(old_name, new_name, uplift); }, _ => {}, @@ -92,98 +92,86 @@ fn main() { fn get_clap_config() -> ArgMatches { Command::new("Clippy developer tooling") .arg_required_else_help(true) - .subcommand( + .subcommands([ Command::new("bless").about("bless the test output changes").arg( Arg::new("ignore-timestamp") .long("ignore-timestamp") .help("Include files updated before clippy was built"), ), - ) - .subcommand( Command::new("fmt") .about("Run rustfmt on all projects and tests") - .arg(Arg::new("check").long("check").help("Use the rustfmt --check option")) - .arg(Arg::new("verbose").short('v').long("verbose").help("Echo commands run")), - ) - .subcommand( + .args([ + Arg::new("check").long("check").help("Use the rustfmt --check option"), + Arg::new("verbose").short('v').long("verbose").help("Echo commands run"), + ]), Command::new("update_lints") .about("Updates lint registration and information from the source code") .long_about( "Makes sure that:\n \ - * the lint count in README.md is correct\n \ - * the changelog contains markdown link references at the bottom\n \ - * all lint groups include the correct lints\n \ - * lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod`\n \ - * all lints are registered in the lint store", + * the lint count in README.md is correct\n \ + * the changelog contains markdown link references at the bottom\n \ + * all lint groups include the correct lints\n \ + * lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod`\n \ + * all lints are registered in the lint store", ) - .arg(Arg::new("print-only").long("print-only").help( - "Print a table of lints to STDOUT. \ - This does not include deprecated and internal lints. \ - (Does not modify any files)", - )) - .arg( + .args([ + Arg::new("print-only").long("print-only").help( + "Print a table of lints to STDOUT. \ + This does not include deprecated and internal lints. \ + (Does not modify any files)", + ), Arg::new("check") .long("check") .help("Checks that `cargo dev update_lints` has been run. Used on CI."), - ), - ) - .subcommand( + ]), Command::new("new_lint") .about("Create new lint and run `cargo dev update_lints`") - .arg( + .args([ Arg::new("pass") .short('p') .long("pass") .help("Specify whether the lint runs during the early or late pass") .takes_value(true) - .possible_values(&["early", "late"]) + .value_parser([PossibleValue::new("early"), PossibleValue::new("late")]) .required(true), - ) - .arg( Arg::new("name") .short('n') .long("name") .help("Name of the new lint in snake case, ex: fn_too_long") .takes_value(true) .required(true), - ) - .arg( Arg::new("category") .short('c') .long("category") .help("What category the lint belongs to") .default_value("nursery") - .possible_values(&[ - "style", - "correctness", - "suspicious", - "complexity", - "perf", - "pedantic", - "restriction", - "cargo", - "nursery", - "internal", - "internal_warn", + .value_parser([ + PossibleValue::new("style"), + PossibleValue::new("correctness"), + PossibleValue::new("suspicious"), + PossibleValue::new("complexity"), + PossibleValue::new("perf"), + PossibleValue::new("pedantic"), + PossibleValue::new("restriction"), + PossibleValue::new("cargo"), + PossibleValue::new("nursery"), + PossibleValue::new("internal"), + PossibleValue::new("internal_warn"), ]) .takes_value(true), - ) - .arg(Arg::new("msrv").long("msrv").help("Add MSRV config code to the lint")), - ) - .subcommand( + Arg::new("msrv").long("msrv").help("Add MSRV config code to the lint"), + ]), Command::new("setup") .about("Support for setting up your personal development environment") .arg_required_else_help(true) - .subcommand( + .subcommands([ Command::new("intellij") .about("Alter dependencies so Intellij Rust can find rustc internals") - .arg( + .args([ Arg::new("remove") .long("remove") .help("Remove the dependencies added with 'cargo dev setup intellij'") .required(false), - ) - .arg( Arg::new("rustc-repo-path") .long("repo-path") .short('r') @@ -192,67 +180,53 @@ fn get_clap_config() -> ArgMatches { .value_name("path") .conflicts_with("remove") .required(true), - ), - ) - .subcommand( + ]), Command::new("git-hook") .about("Add a pre-commit git hook that formats your code to make it look pretty") - .arg( + .args([ Arg::new("remove") .long("remove") .help("Remove the pre-commit hook added with 'cargo dev setup git-hook'") .required(false), - ) - .arg( Arg::new("force-override") .long("force-override") .short('f') .help("Forces the override of an existing git pre-commit hook") .required(false), - ), - ) - .subcommand( + ]), Command::new("vscode-tasks") .about("Add several tasks to vscode for formatting, validation and testing") - .arg( + .args([ Arg::new("remove") .long("remove") .help("Remove the tasks added with 'cargo dev setup vscode-tasks'") .required(false), - ) - .arg( Arg::new("force-override") .long("force-override") .short('f') .help("Forces the override of existing vscode tasks") .required(false), - ), - ), - ) - .subcommand( + ]), + ]), Command::new("remove") .about("Support for undoing changes done by the setup command") .arg_required_else_help(true) - .subcommand(Command::new("git-hook").about("Remove any existing pre-commit git hook")) - .subcommand(Command::new("vscode-tasks").about("Remove any existing vscode tasks")) - .subcommand( + .subcommands([ + Command::new("git-hook").about("Remove any existing pre-commit git hook"), + Command::new("vscode-tasks").about("Remove any existing vscode tasks"), Command::new("intellij").about("Removes rustc source paths added via `cargo dev setup intellij`"), - ), - ) - .subcommand( + ]), Command::new("serve") .about("Launch a local 'ALL the Clippy Lints' website in a browser") - .arg( + .args([ Arg::new("port") .long("port") .short('p') .help("Local port for the http server") .default_value("8000") - .validator_os(serve::validate_port), - ) - .arg(Arg::new("lint").help("Which lint's page to load initially (optional)")), - ) - .subcommand( + .value_parser(clap::value_parser!(u16)), + Arg::new("lint").help("Which lint's page to load initially (optional)"), + ]), Command::new("lint") .about("Manually run clippy on a file or package") .after_help(indoc! {" @@ -271,37 +245,27 @@ fn get_clap_config() -> ArgMatches { cargo dev lint file.rs -- -W clippy::pedantic cargo dev lint ~/my-project -- -- -W clippy::pedantic "}) - .arg( + .args([ Arg::new("path") .required(true) .help("The path to a file or package directory to lint"), - ) - .arg( Arg::new("args") - .multiple_occurrences(true) + .action(ArgAction::Append) .help("Pass extra arguments to cargo/clippy-driver"), - ), - ) - .subcommand( - Command::new("rename_lint") - .about("Renames the given lint") - .arg( - Arg::new("old_name") - .index(1) - .required(true) - .help("The name of the lint to rename"), - ) - .arg( - Arg::new("new_name") - .index(2) - .required_unless_present("uplift") - .help("The new name of the lint"), - ) - .arg( - Arg::new("uplift") - .long("uplift") - .help("This lint will be uplifted into rustc"), - ), - ) + ]), + Command::new("rename_lint").about("Renames the given lint").args([ + Arg::new("old_name") + .index(1) + .required(true) + .help("The name of the lint to rename"), + Arg::new("new_name") + .index(2) + .required_unless_present("uplift") + .help("The new name of the lint"), + Arg::new("uplift") + .long("uplift") + .help("This lint will be uplifted into rustc"), + ]), + ]) .get_matches() } diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 07d19638788..748d73c0801 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -34,7 +34,12 @@ impl Context for io::Result { /// # Errors /// /// This function errors out if the files couldn't be created or written to. -pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>, msrv: bool) -> io::Result<()> { +pub fn create( + pass: Option<&String>, + lint_name: Option<&String>, + category: Option<&String>, + msrv: bool, +) -> io::Result<()> { let lint = LintData { pass: pass.expect("`pass` argument is validated by clap"), name: lint_name.expect("`name` argument is validated by clap"), diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index d55b1a354d0..f15f24da946 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -8,7 +8,7 @@ use std::time::{Duration, SystemTime}; /// # Panics /// /// Panics if the python commands could not be spawned -pub fn run(port: u16, lint: Option<&str>) -> ! { +pub fn run(port: u16, lint: Option<&String>) -> ! { let mut url = Some(match lint { None => format!("http://localhost:{}", port), Some(lint) => format!("http://localhost:{}/#{}", port, lint), diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 5024e63bfa7..1bbd9a45b61 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -58,6 +58,16 @@ fn generate_lint_files( }, ); + replace_region_in_file( + update_mode, + Path::new("book/src/README.md"), + "[There are over ", + " lints included in this crate!]", + |res| { + write!(res, "{}", round_to_fifty(usable_lints.len())).unwrap(); + }, + ); + replace_region_in_file( update_mode, Path::new("CHANGELOG.md"), diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 0a3f04da357..4d5bf47833f 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" [dependencies] cargo_metadata = "0.14" +clippy_dev = { path = "../clippy_dev", optional = true } clippy_utils = { path = "../clippy_utils" } if_chain = "1.0" itertools = "0.10.1" @@ -18,6 +19,7 @@ quine-mc_cluskey = "0.2" regex-syntax = "0.6" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", optional = true } +tempfile = { version = "3.2", optional = true } toml = "0.5" unicode-normalization = "0.1" unicode-script = { version = "0.5", default-features = false } @@ -30,7 +32,7 @@ url = { version = "2.2", features = ["serde"] } [features] deny-warnings = ["clippy_utils/deny-warnings"] # build clippy with internal lints enabled, off by default -internal = ["clippy_utils/internal", "serde_json"] +internal = ["clippy_utils/internal", "serde_json", "tempfile", "clippy_dev"] [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index c82837746bd..2705ffffdcb 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -14,9 +14,6 @@ declare_clippy_lint! { /// Will be optimized out by the compiler or should probably be replaced by a /// `panic!()` or `unreachable!()` /// - /// ### Known problems - /// None - /// /// ### Example /// ```rust,ignore /// assert!(false) diff --git a/clippy_lints/src/async_yields_async.rs b/clippy_lints/src/async_yields_async.rs index 0619490e73c..27c2896e1e5 100644 --- a/clippy_lints/src/async_yields_async.rs +++ b/clippy_lints/src/async_yields_async.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::snippet; use clippy_utils::ty::implements_trait; use rustc_errors::Applicability; @@ -24,6 +24,7 @@ declare_clippy_lint! { /// }; /// } /// ``` + /// /// Use instead: /// ```rust /// async fn foo() {} @@ -63,9 +64,10 @@ impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync { _ => None, }; if let Some(return_expr_span) = return_expr_span { - span_lint_and_then( + span_lint_hir_and_then( cx, ASYNC_YIELDS_ASYNC, + body.value.hir_id, return_expr_span, "an async construct yields a type which is itself awaitable", |db| { diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index 770cb6a3d7b..ed12ad9c367 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -340,7 +340,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes { for lint in lint_list { match item.kind { ItemKind::Use(..) => { - if is_word(lint, sym!(unused_imports)) + if is_word(lint, sym::unused_imports) || is_word(lint, sym::deprecated) || is_word(lint, sym!(unreachable_pub)) || is_word(lint, sym!(unused)) @@ -355,7 +355,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes { } }, ItemKind::ExternCrate(..) => { - if is_word(lint, sym!(unused_imports)) && skip_unused_imports { + if is_word(lint, sym::unused_imports) && skip_unused_imports { return; } if is_word(lint, sym!(unused_extern_crates)) { diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 5b7c4591504..eee5f90d178 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -140,8 +140,6 @@ declare_clippy_lint! { /// from a memory access perspective but will cause bugs at runtime if they /// are held in such a way. /// - /// ### Known problems - /// /// ### Example /// /// ```toml diff --git a/clippy_lints/src/bool_assert_comparison.rs b/clippy_lints/src/bool_assert_comparison.rs index c50e214be28..95abe8aa59f 100644 --- a/clippy_lints/src/bool_assert_comparison.rs +++ b/clippy_lints/src/bool_assert_comparison.rs @@ -17,11 +17,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// assert_eq!("a".is_empty(), false); /// assert_ne!("a".is_empty(), true); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// assert!(!"a".is_empty()); /// ``` #[clippy::version = "1.53.0"] diff --git a/clippy_lints/src/borrow_deref_ref.rs b/clippy_lints/src/borrow_deref_ref.rs index ec2f31cf673..1582ec9ee5c 100644 --- a/clippy_lints/src/borrow_deref_ref.rs +++ b/clippy_lints/src/borrow_deref_ref.rs @@ -18,7 +18,7 @@ declare_clippy_lint! { /// Dereferencing and then borrowing a reference value has no effect in most cases. /// /// ### Known problems - /// false negative on such code: + /// False negative on such code: /// ``` /// let x = &12; /// let addr_x = &x as *const _ as usize; @@ -29,17 +29,20 @@ declare_clippy_lint! { /// /// ### Example /// ```rust + /// fn foo(_x: &str) {} + /// /// let s = &String::new(); /// - /// // Bad /// let a: &String = &* s; /// foo(&*s); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # fn foo(_x: &str) {} + /// # let s = &String::new(); /// let a: &String = s; /// foo(&**s); - /// - /// fn foo(_: &str){ } /// ``` #[clippy::version = "1.59.0"] pub BORROW_DEREF_REF, diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index daf3b7b4ce4..02c2f30a4dd 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -219,13 +219,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// fn fun() -> i32 { 1 } - /// let a = fun as i64; + /// let _ = fun as i64; + /// ``` /// - /// // Good - /// fn fun2() -> i32 { 1 } - /// let a = fun2 as usize; + /// Use instead: + /// ```rust + /// # fn fun() -> i32 { 1 } + /// let _ = fun as usize; /// ``` #[clippy::version = "pre 1.29.0"] pub FN_TO_NUMERIC_CAST, @@ -245,17 +246,19 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// fn fn1() -> i16 { /// 1 /// }; /// let _ = fn1 as i32; + /// ``` /// - /// // Better: Cast to usize first, then comment with the reason for the truncation - /// fn fn2() -> i16 { + /// Use instead: + /// ```rust + /// // Cast to usize first, then comment with the reason for the truncation + /// fn fn1() -> i16 { /// 1 /// }; - /// let fn_ptr = fn2 as usize; + /// let fn_ptr = fn1 as usize; /// let fn_ptr_truncated = fn_ptr as i32; /// ``` #[clippy::version = "pre 1.29.0"] @@ -277,19 +280,24 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad: fn1 is cast as `usize` + /// // fn1 is cast as `usize` /// fn fn1() -> u16 { /// 1 /// }; /// let _ = fn1 as usize; + /// ``` /// - /// // Good: maybe you intended to call the function? + /// Use instead: + /// ```rust + /// // maybe you intended to call the function? /// fn fn2() -> u16 { /// 1 /// }; /// let _ = fn2() as usize; /// - /// // Good: maybe you intended to cast it to a function type? + /// // or + /// + /// // maybe you intended to cast it to a function type? /// fn fn3() -> u16 { /// 1 /// } @@ -406,7 +414,7 @@ declare_clippy_lint! { /// enum E { X = 256 }; /// let _ = E::X as u8; /// ``` - #[clippy::version = "1.60.0"] + #[clippy::version = "1.61.0"] pub CAST_ENUM_TRUNCATION, suspicious, "casts from an enum type to an integral type which will truncate the value" @@ -451,7 +459,7 @@ declare_clippy_lint! { /// println!("{:?}", &*new_ptr); /// } /// ``` - #[clippy::version = "1.60.0"] + #[clippy::version = "1.61.0"] pub CAST_SLICE_DIFFERENT_SIZES, correctness, "casting using `as` between raw pointers to slices of types with different sizes" diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 1010340c712..17fc81951f9 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{meets_msrv, msrvs, SpanlessEq}; +use clippy_utils::{in_constant, meets_msrv, msrvs, SpanlessEq}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -22,18 +22,14 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let foo: u32 = 5; - /// # let _ = - /// foo <= i32::MAX as u32 - /// # ; + /// foo <= i32::MAX as u32; /// ``` /// - /// Could be written: - /// + /// Use instead: /// ```rust /// # let foo = 1; - /// # let _ = - /// i32::try_from(foo).is_ok() - /// # ; + /// # #[allow(unused)] + /// i32::try_from(foo).is_ok(); /// ``` #[clippy::version = "1.37.0"] pub CHECKED_CONVERSIONS, @@ -61,6 +57,7 @@ impl<'tcx> LateLintPass<'tcx> for CheckedConversions { } let result = if_chain! { + if !in_constant(cx, item.hir_id); if !in_external_macro(cx.sess(), item.span); if let ExprKind::Binary(op, left, right) = &item.kind; diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index 3eceb848822..90430b71a0e 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -32,20 +32,20 @@ declare_clippy_lint! { /// makes code look more complex than it really is. /// /// ### Example - /// ```rust,ignore + /// ```rust + /// # let (x, y) = (true, true); /// if x { /// if y { - /// … + /// // … /// } /// } - /// /// ``` /// /// Use instead: - /// - /// ```rust,ignore + /// ```rust + /// # let (x, y) = (true, true); /// if x && y { - /// … + /// // … /// } /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/comparison_chain.rs b/clippy_lints/src/comparison_chain.rs index 913e081af3b..a05b41eb3ab 100644 --- a/clippy_lints/src/comparison_chain.rs +++ b/clippy_lints/src/comparison_chain.rs @@ -35,7 +35,6 @@ declare_clippy_lint! { /// ``` /// /// Use instead: - /// /// ```rust,ignore /// use std::cmp::Ordering; /// # fn a() {} diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 1e9a1153011..1deff9684a1 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -1,18 +1,18 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then}; use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt}; use clippy_utils::{ - both, count_eq, eq_expr_value, get_enclosing_block, get_parent_expr, if_sequence, is_else_clause, is_lint_allowed, - search_same, ContainsName, SpanlessEq, SpanlessHash, + eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause, is_lint_allowed, + search_same, ContainsName, HirEqInterExpr, SpanlessEq, }; -use if_chain::if_chain; -use rustc_data_structures::fx::FxHashSet; -use rustc_errors::{Applicability, Diagnostic}; -use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{Block, Expr, ExprKind, HirId}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::hir::nested_filter; +use core::iter; +use rustc_errors::Applicability; +use rustc_hir::intravisit; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Stmt, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{source_map::Span, symbol::Symbol, BytePos}; +use rustc_span::hygiene::walk_chain; +use rustc_span::source_map::SourceMap; +use rustc_span::{BytePos, Span, Symbol}; use std::borrow::Cow; declare_clippy_lint! { @@ -165,243 +165,315 @@ declare_lint_pass!(CopyAndPaste => [ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if !expr.span.from_expansion() { - if let ExprKind::If(_, _, _) = expr.kind { - // skip ifs directly in else, it will be checked in the parent if - if let Some(&Expr { - kind: ExprKind::If(_, _, Some(else_expr)), - .. - }) = get_parent_expr(cx, expr) - { - if else_expr.hir_id == expr.hir_id { - return; - } - } - - let (conds, blocks) = if_sequence(expr); - // Conditions - lint_same_cond(cx, &conds); - lint_same_fns_in_if_cond(cx, &conds); - // Block duplication - lint_same_then_else(cx, &conds, &blocks, conds.len() == blocks.len(), expr); + if !expr.span.from_expansion() && matches!(expr.kind, ExprKind::If(..)) && !is_else_clause(cx.tcx, expr) { + let (conds, blocks) = if_sequence(expr); + lint_same_cond(cx, &conds); + lint_same_fns_in_if_cond(cx, &conds); + let all_same = + !is_lint_allowed(cx, IF_SAME_THEN_ELSE, expr.hir_id) && lint_if_same_then_else(cx, &conds, &blocks); + if !all_same && conds.len() != blocks.len() { + lint_branches_sharing_code(cx, &conds, &blocks, expr); } } } } -/// Implementation of `BRANCHES_SHARING_CODE` and `IF_SAME_THEN_ELSE` if the blocks are equal. -fn lint_same_then_else<'tcx>( - cx: &LateContext<'tcx>, - conds: &[&'tcx Expr<'_>], - blocks: &[&Block<'tcx>], - has_conditional_else: bool, - expr: &'tcx Expr<'_>, -) { - // We only lint ifs with multiple blocks - if blocks.len() < 2 || is_else_clause(cx.tcx, expr) { - return; - } - - // Check if each block has shared code - let has_expr = blocks[0].expr.is_some(); - - let (start_eq, mut end_eq, expr_eq) = if let Some(block_eq) = scan_block_for_eq(cx, conds, blocks) { - (block_eq.start_eq, block_eq.end_eq, block_eq.expr_eq) - } else { - return; - }; - - // BRANCHES_SHARING_CODE prerequisites - if has_conditional_else || (start_eq == 0 && end_eq == 0 && (has_expr && !expr_eq)) { - return; - } - - // Only the start is the same - if start_eq != 0 && end_eq == 0 && (!has_expr || !expr_eq) { - let block = blocks[0]; - let start_stmts = block.stmts.split_at(start_eq).0; - - let mut start_walker = UsedValueFinderVisitor::new(cx); - for stmt in start_stmts { - intravisit::walk_stmt(&mut start_walker, stmt); - } - - emit_branches_sharing_code_lint( - cx, - start_eq, - 0, - false, - check_for_warn_of_moved_symbol(cx, &start_walker.def_symbols, expr), - blocks, - expr, - ); - } else if end_eq != 0 || (has_expr && expr_eq) { - let block = blocks[blocks.len() - 1]; - let (start_stmts, block_stmts) = block.stmts.split_at(start_eq); - let (block_stmts, end_stmts) = block_stmts.split_at(block_stmts.len() - end_eq); - - // Scan start - let mut start_walker = UsedValueFinderVisitor::new(cx); - for stmt in start_stmts { - intravisit::walk_stmt(&mut start_walker, stmt); - } - let mut moved_syms = start_walker.def_symbols; - - // Scan block - let mut block_walker = UsedValueFinderVisitor::new(cx); - for stmt in block_stmts { - intravisit::walk_stmt(&mut block_walker, stmt); - } - let mut block_defs = block_walker.defs; - - // Scan moved stmts - let mut moved_start: Option = None; - let mut end_walker = UsedValueFinderVisitor::new(cx); - for (index, stmt) in end_stmts.iter().enumerate() { - intravisit::walk_stmt(&mut end_walker, stmt); - - for value in &end_walker.uses { - // Well we can't move this and all prev statements. So reset - if block_defs.contains(value) { - moved_start = Some(index + 1); - end_walker.defs.drain().for_each(|x| { - block_defs.insert(x); - }); - - end_walker.def_symbols.clear(); - } - } - - end_walker.uses.clear(); - } - - if let Some(moved_start) = moved_start { - end_eq -= moved_start; - } - - let end_linable = block.expr.map_or_else( - || end_eq != 0, - |expr| { - intravisit::walk_expr(&mut end_walker, expr); - end_walker.uses.iter().any(|x| !block_defs.contains(x)) - }, - ); - - if end_linable { - end_walker.def_symbols.drain().for_each(|x| { - moved_syms.insert(x); - }); - } - - emit_branches_sharing_code_lint( - cx, - start_eq, - end_eq, - end_linable, - check_for_warn_of_moved_symbol(cx, &moved_syms, expr), - blocks, - expr, - ); +/// Checks if the given expression is a let chain. +fn contains_let(e: &Expr<'_>) -> bool { + match e.kind { + ExprKind::Let(..) => true, + ExprKind::Binary(op, lhs, rhs) if op.node == BinOpKind::And => { + matches!(lhs.kind, ExprKind::Let(..)) || contains_let(rhs) + }, + _ => false, } } -struct BlockEqual { - /// The amount statements that are equal from the start - start_eq: usize, - /// The amount statements that are equal from the end - end_eq: usize, - /// An indication if the block expressions are the same. This will also be true if both are - /// `None` - expr_eq: bool, -} - -/// This function can also trigger the `IF_SAME_THEN_ELSE` in which case it'll return `None` to -/// abort any further processing and avoid duplicate lint triggers. -fn scan_block_for_eq(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<'_>]) -> Option { - let mut start_eq = usize::MAX; - let mut end_eq = usize::MAX; - let mut expr_eq = true; - let mut iter = blocks.windows(2).enumerate(); - while let Some((i, &[block0, block1])) = iter.next() { - let l_stmts = block0.stmts; - let r_stmts = block1.stmts; - - // `SpanlessEq` now keeps track of the locals and is therefore context sensitive clippy#6752. - // The comparison therefore needs to be done in a way that builds the correct context. - let mut evaluator = SpanlessEq::new(cx); - let mut evaluator = evaluator.inter_expr(); - - let current_start_eq = count_eq(&mut l_stmts.iter(), &mut r_stmts.iter(), |l, r| evaluator.eq_stmt(l, r)); - - let current_end_eq = { - // We skip the middle statements which can't be equal - let end_comparison_count = l_stmts.len().min(r_stmts.len()) - current_start_eq; - let it1 = l_stmts.iter().skip(l_stmts.len() - end_comparison_count); - let it2 = r_stmts.iter().skip(r_stmts.len() - end_comparison_count); - it1.zip(it2) - .fold(0, |acc, (l, r)| if evaluator.eq_stmt(l, r) { acc + 1 } else { 0 }) - }; - let block_expr_eq = both(&block0.expr, &block1.expr, |l, r| evaluator.eq_expr(l, r)); - - // IF_SAME_THEN_ELSE - if_chain! { - if block_expr_eq; - if l_stmts.len() == r_stmts.len(); - if l_stmts.len() == current_start_eq; - // `conds` may have one last item than `blocks`. - // Any `i` from `blocks.windows(2)` will exist in `conds`, but `i+1` may not exist on the last iteration. - if !matches!(conds[i].kind, ExprKind::Let(..)); - if !matches!(conds.get(i + 1).map(|e| &e.kind), Some(ExprKind::Let(..))); - if !is_lint_allowed(cx, IF_SAME_THEN_ELSE, block0.hir_id); - if !is_lint_allowed(cx, IF_SAME_THEN_ELSE, block1.hir_id); - then { +fn lint_if_same_then_else(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<'_>]) -> bool { + let mut eq = SpanlessEq::new(cx); + blocks + .array_windows::<2>() + .enumerate() + .fold(true, |all_eq, (i, &[lhs, rhs])| { + if eq.eq_block(lhs, rhs) && !contains_let(conds[i]) && conds.get(i + 1).map_or(true, |e| !contains_let(e)) { span_lint_and_note( cx, IF_SAME_THEN_ELSE, - block0.span, + lhs.span, "this `if` has identical blocks", - Some(block1.span), + Some(rhs.span), "same as this", ); - - return None; + all_eq + } else { + false } - } - - start_eq = start_eq.min(current_start_eq); - end_eq = end_eq.min(current_end_eq); - expr_eq &= block_expr_eq; - } - - if !expr_eq { - end_eq = 0; - } - - // Check if the regions are overlapping. Set `end_eq` to prevent the overlap - let min_block_size = blocks.iter().map(|x| x.stmts.len()).min().unwrap(); - if (start_eq + end_eq) > min_block_size { - end_eq = min_block_size - start_eq; - } - - Some(BlockEqual { - start_eq, - end_eq, - expr_eq, - }) + }) } -fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &FxHashSet, if_expr: &Expr<'_>) -> bool { +fn lint_branches_sharing_code<'tcx>( + cx: &LateContext<'tcx>, + conds: &[&'tcx Expr<'_>], + blocks: &[&Block<'tcx>], + expr: &'tcx Expr<'_>, +) { + // We only lint ifs with multiple blocks + let &[first_block, ref blocks @ ..] = blocks else { + return; + }; + let &[.., last_block] = blocks else { + return; + }; + + let res = scan_block_for_eq(cx, conds, first_block, blocks); + let sm = cx.tcx.sess.source_map(); + let start_suggestion = res.start_span(first_block, sm).map(|span| { + let first_line_span = first_line_of_span(cx, expr.span); + let replace_span = first_line_span.with_hi(span.hi()); + let cond_span = first_line_span.until(first_block.span); + let cond_snippet = reindent_multiline(snippet(cx, cond_span, "_"), false, None); + let cond_indent = indent_of(cx, cond_span); + let moved_snippet = reindent_multiline(snippet(cx, span, "_"), true, None); + let suggestion = moved_snippet.to_string() + "\n" + &cond_snippet + "{"; + let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, cond_indent); + (replace_span, suggestion.to_string()) + }); + let end_suggestion = res.end_span(last_block, sm).map(|span| { + let moved_snipped = reindent_multiline(snippet(cx, span, "_"), true, None); + let indent = indent_of(cx, expr.span.shrink_to_hi()); + let suggestion = "}\n".to_string() + &moved_snipped; + let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, indent); + + let span = span.with_hi(last_block.span.hi()); + // Improve formatting if the inner block has indention (i.e. normal Rust formatting) + let test_span = Span::new(span.lo() - BytePos(4), span.lo(), span.ctxt(), span.parent()); + let span = if snippet_opt(cx, test_span).map_or(false, |snip| snip == " ") { + span.with_lo(test_span.lo()) + } else { + span + }; + (span, suggestion.to_string()) + }); + + let (span, msg, end_span) = match (&start_suggestion, &end_suggestion) { + (&Some((span, _)), &Some((end_span, _))) => ( + span, + "all if blocks contain the same code at both the start and the end", + Some(end_span), + ), + (&Some((span, _)), None) => (span, "all if blocks contain the same code at the start", None), + (None, &Some((span, _))) => (span, "all if blocks contain the same code at the end", None), + (None, None) => return, + }; + span_lint_and_then(cx, BRANCHES_SHARING_CODE, span, msg, |diag| { + if let Some(span) = end_span { + diag.span_note(span, "this code is shared at the end"); + } + if let Some((span, sugg)) = start_suggestion { + diag.span_suggestion( + span, + "consider moving these statements before the if", + sugg, + Applicability::Unspecified, + ); + } + if let Some((span, sugg)) = end_suggestion { + diag.span_suggestion( + span, + "consider moving these statements after the if", + sugg, + Applicability::Unspecified, + ); + if !cx.typeck_results().expr_ty(expr).is_unit() { + diag.note("the end suggestion probably needs some adjustments to use the expression result correctly"); + } + } + if check_for_warn_of_moved_symbol(cx, &res.moved_locals, expr) { + diag.warn("some moved values might need to be renamed to avoid wrong references"); + } + }); +} + +struct BlockEq { + /// The end of the range of equal stmts at the start. + start_end_eq: usize, + /// The start of the range of equal stmts at the end. + end_begin_eq: Option, + /// The name and id of every local which can be moved at the beginning and the end. + moved_locals: Vec<(HirId, Symbol)>, +} +impl BlockEq { + fn start_span(&self, b: &Block<'_>, sm: &SourceMap) -> Option { + match &b.stmts[..self.start_end_eq] { + [first, .., last] => Some(sm.stmt_span(first.span, b.span).to(sm.stmt_span(last.span, b.span))), + [s] => Some(sm.stmt_span(s.span, b.span)), + [] => None, + } + } + + fn end_span(&self, b: &Block<'_>, sm: &SourceMap) -> Option { + match (&b.stmts[b.stmts.len() - self.end_begin_eq?..], b.expr) { + ([first, .., last], None) => Some(sm.stmt_span(first.span, b.span).to(sm.stmt_span(last.span, b.span))), + ([first, ..], Some(last)) => Some(sm.stmt_span(first.span, b.span).to(sm.stmt_span(last.span, b.span))), + ([s], None) => Some(sm.stmt_span(s.span, b.span)), + ([], Some(e)) => Some(walk_chain(e.span, b.span.ctxt())), + ([], None) => None, + } + } +} + +/// If the statement is a local, checks if the bound names match the expected list of names. +fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool { + if let StmtKind::Local(l) = s.kind { + let mut i = 0usize; + let mut res = true; + l.pat.each_binding_or_first(&mut |_, _, _, name| { + if names.get(i).map_or(false, |&(_, n)| n == name.name) { + i += 1; + } else { + res = false; + } + }); + res && i == names.len() + } else { + false + } +} + +/// Checks if the given statement should be considered equal to the statement in the same position +/// for each block. +fn eq_stmts( + stmt: &Stmt<'_>, + blocks: &[&Block<'_>], + get_stmt: impl for<'a> Fn(&'a Block<'a>) -> Option<&'a Stmt<'a>>, + eq: &mut HirEqInterExpr<'_, '_, '_>, + moved_bindings: &mut Vec<(HirId, Symbol)>, +) -> bool { + (if let StmtKind::Local(l) = stmt.kind { + let old_count = moved_bindings.len(); + l.pat.each_binding_or_first(&mut |_, id, _, name| { + moved_bindings.push((id, name.name)); + }); + let new_bindings = &moved_bindings[old_count..]; + blocks + .iter() + .all(|b| get_stmt(b).map_or(false, |s| eq_binding_names(s, new_bindings))) + } else { + true + }) && blocks + .iter() + .all(|b| get_stmt(b).map_or(false, |s| eq.eq_stmt(s, stmt))) +} + +fn scan_block_for_eq(cx: &LateContext<'_>, _conds: &[&Expr<'_>], block: &Block<'_>, blocks: &[&Block<'_>]) -> BlockEq { + let mut eq = SpanlessEq::new(cx); + let mut eq = eq.inter_expr(); + let mut moved_locals = Vec::new(); + + let start_end_eq = block + .stmts + .iter() + .enumerate() + .find(|&(i, stmt)| !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals)) + .map_or(block.stmts.len(), |(i, _)| i); + + // Walk backwards through the final expression/statements so long as their hashes are equal. Note + // `SpanlessHash` treats all local references as equal allowing locals declared earlier in the block + // to match those in other blocks. e.g. If each block ends with the following the hash value will be + // the same even though each `x` binding will have a different `HirId`: + // let x = foo(); + // x + 50 + let expr_hash_eq = if let Some(e) = block.expr { + let hash = hash_expr(cx, e); + blocks + .iter() + .all(|b| b.expr.map_or(false, |e| hash_expr(cx, e) == hash)) + } else { + blocks.iter().all(|b| b.expr.is_none()) + }; + if !expr_hash_eq { + return BlockEq { + start_end_eq, + end_begin_eq: None, + moved_locals, + }; + } + let end_search_start = block.stmts[start_end_eq..] + .iter() + .rev() + .enumerate() + .find(|&(offset, stmt)| { + let hash = hash_stmt(cx, stmt); + blocks.iter().any(|b| { + b.stmts + // the bounds check will catch the underflow + .get(b.stmts.len().wrapping_sub(offset + 1)) + .map_or(true, |s| hash != hash_stmt(cx, s)) + }) + }) + .map_or(block.stmts.len() - start_end_eq, |(i, _)| i); + + let moved_locals_at_start = moved_locals.len(); + let mut i = end_search_start; + let end_begin_eq = block.stmts[block.stmts.len() - end_search_start..] + .iter() + .zip(iter::repeat_with(move || { + let x = i; + i -= 1; + x + })) + .fold(end_search_start, |init, (stmt, offset)| { + if eq_stmts( + stmt, + blocks, + |b| b.stmts.get(b.stmts.len() - offset), + &mut eq, + &mut moved_locals, + ) { + init + } else { + // Clear out all locals seen at the end so far. None of them can be moved. + let stmts = &blocks[0].stmts; + for stmt in &stmts[stmts.len() - init..=stmts.len() - offset] { + if let StmtKind::Local(l) = stmt.kind { + l.pat.each_binding_or_first(&mut |_, id, _, _| { + eq.locals.remove(&id); + }); + } + } + moved_locals.truncate(moved_locals_at_start); + offset - 1 + } + }); + if let Some(e) = block.expr { + for block in blocks { + if block.expr.map_or(false, |expr| !eq.eq_expr(expr, e)) { + moved_locals.truncate(moved_locals_at_start); + return BlockEq { + start_end_eq, + end_begin_eq: None, + moved_locals, + }; + } + } + } + + BlockEq { + start_end_eq, + end_begin_eq: Some(end_begin_eq), + moved_locals, + } +} + +fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbol)], if_expr: &Expr<'_>) -> bool { get_enclosing_block(cx, if_expr.hir_id).map_or(false, |block| { let ignore_span = block.span.shrink_to_lo().to(if_expr.span); symbols .iter() - .filter(|sym| !sym.as_str().starts_with('_')) - .any(move |sym| { - let mut walker = ContainsName { - name: *sym, - result: false, - }; + .filter(|&&(_, name)| !name.as_str().starts_with('_')) + .any(|&(_, name)| { + let mut walker = ContainsName { name, result: false }; // Scan block block @@ -419,194 +491,9 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &FxHashSet, - start_stmts: usize, - end_stmts: usize, - lint_end: bool, - warn_about_moved_symbol: bool, - blocks: &[&Block<'_>], - if_expr: &Expr<'_>, -) { - if start_stmts == 0 && !lint_end { - return; - } - - // (help, span, suggestion) - let mut suggestions: Vec<(&str, Span, String)> = vec![]; - let mut add_expr_note = false; - - // Construct suggestions - let sm = cx.sess().source_map(); - if start_stmts > 0 { - let block = blocks[0]; - let span_start = first_line_of_span(cx, if_expr.span).shrink_to_lo(); - let span_end = sm.stmt_span(block.stmts[start_stmts - 1].span, block.span); - - let cond_span = first_line_of_span(cx, if_expr.span).until(block.span); - let cond_snippet = reindent_multiline(snippet(cx, cond_span, "_"), false, None); - let cond_indent = indent_of(cx, cond_span); - let moved_span = block.stmts[0].span.source_callsite().to(span_end); - let moved_snippet = reindent_multiline(snippet(cx, moved_span, "_"), true, None); - let suggestion = moved_snippet.to_string() + "\n" + &cond_snippet + "{"; - let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, cond_indent); - - let span = span_start.to(span_end); - suggestions.push(("start", span, suggestion.to_string())); - } - - if lint_end { - let block = blocks[blocks.len() - 1]; - let span_end = block.span.shrink_to_hi(); - - let moved_start = if end_stmts == 0 && block.expr.is_some() { - block.expr.unwrap().span.source_callsite() - } else { - sm.stmt_span(block.stmts[block.stmts.len() - end_stmts].span, block.span) - }; - let moved_end = block.expr.map_or_else( - || sm.stmt_span(block.stmts[block.stmts.len() - 1].span, block.span), - |expr| expr.span.source_callsite(), - ); - - let moved_span = moved_start.to(moved_end); - let moved_snipped = reindent_multiline(snippet(cx, moved_span, "_"), true, None); - let indent = indent_of(cx, if_expr.span.shrink_to_hi()); - let suggestion = "}\n".to_string() + &moved_snipped; - let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, indent); - - let mut span = moved_start.to(span_end); - // Improve formatting if the inner block has indention (i.e. normal Rust formatting) - let test_span = Span::new(span.lo() - BytePos(4), span.lo(), span.ctxt(), span.parent()); - if snippet_opt(cx, test_span) - .map(|snip| snip == " ") - .unwrap_or_default() - { - span = span.with_lo(test_span.lo()); - } - - suggestions.push(("end", span, suggestion.to_string())); - add_expr_note = !cx.typeck_results().expr_ty(if_expr).is_unit(); - } - - let add_optional_msgs = |diag: &mut Diagnostic| { - if add_expr_note { - diag.note("The end suggestion probably needs some adjustments to use the expression result correctly"); - } - - if warn_about_moved_symbol { - diag.warn("Some moved values might need to be renamed to avoid wrong references"); - } - }; - - // Emit lint - if suggestions.len() == 1 { - let (place_str, span, sugg) = suggestions.pop().unwrap(); - let msg = format!("all if blocks contain the same code at the {}", place_str); - let help = format!("consider moving the {} statements out like this", place_str); - span_lint_and_then(cx, BRANCHES_SHARING_CODE, span, msg.as_str(), |diag| { - diag.span_suggestion(span, help.as_str(), sugg, Applicability::Unspecified); - - add_optional_msgs(diag); - }); - } else if suggestions.len() == 2 { - let (_, end_span, end_sugg) = suggestions.pop().unwrap(); - let (_, start_span, start_sugg) = suggestions.pop().unwrap(); - span_lint_and_then( - cx, - BRANCHES_SHARING_CODE, - start_span, - "all if blocks contain the same code at the start and the end. Here at the start", - move |diag| { - diag.span_note(end_span, "and here at the end"); - - diag.span_suggestion( - start_span, - "consider moving the start statements out like this", - start_sugg, - Applicability::Unspecified, - ); - - diag.span_suggestion( - end_span, - "and consider moving the end statements out like this", - end_sugg, - Applicability::Unspecified, - ); - - add_optional_msgs(diag); - }, - ); - } -} - -/// This visitor collects `HirId`s and Symbols of defined symbols and `HirId`s of used values. -struct UsedValueFinderVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - - /// The `HirId`s of defined values in the scanned statements - defs: FxHashSet, - - /// The Symbols of the defined symbols in the scanned statements - def_symbols: FxHashSet, - - /// The `HirId`s of the used values - uses: FxHashSet, -} - -impl<'a, 'tcx> UsedValueFinderVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>) -> Self { - UsedValueFinderVisitor { - cx, - defs: FxHashSet::default(), - def_symbols: FxHashSet::default(), - uses: FxHashSet::default(), - } - } -} - -impl<'a, 'tcx> Visitor<'tcx> for UsedValueFinderVisitor<'a, 'tcx> { - type NestedFilter = nested_filter::All; - - fn nested_visit_map(&mut self) -> Self::Map { - self.cx.tcx.hir() - } - - fn visit_local(&mut self, l: &'tcx rustc_hir::Local<'tcx>) { - let local_id = l.pat.hir_id; - self.defs.insert(local_id); - - if let Some(sym) = l.pat.simple_ident() { - self.def_symbols.insert(sym.name); - } - - if let Some(expr) = l.init { - intravisit::walk_expr(self, expr); - } - } - - fn visit_qpath(&mut self, qpath: &'tcx rustc_hir::QPath<'tcx>, id: HirId, _span: rustc_span::Span) { - if let rustc_hir::QPath::Resolved(_, path) = *qpath { - if path.segments.len() == 1 { - if let rustc_hir::def::Res::Local(var) = self.cx.qpath_res(qpath, id) { - self.uses.insert(var); - } - } - } - } -} - /// Implementation of `IFS_SAME_COND`. fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { - let hash: &dyn Fn(&&Expr<'_>) -> u64 = &|expr| -> u64 { - let mut h = SpanlessHash::new(cx); - h.hash_expr(expr); - h.finish() - }; - - let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { eq_expr_value(cx, lhs, rhs) }; - - for (i, j) in search_same(conds, hash, eq) { + for (i, j) in search_same(conds, |e| hash_expr(cx, e), |lhs, rhs| eq_expr_value(cx, lhs, rhs)) { span_lint_and_note( cx, IFS_SAME_COND, @@ -620,12 +507,6 @@ fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { /// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`. fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { - let hash: &dyn Fn(&&Expr<'_>) -> u64 = &|expr| -> u64 { - let mut h = SpanlessHash::new(cx); - h.hash_expr(expr); - h.finish() - }; - let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { // Do not lint if any expr originates from a macro if lhs.span.from_expansion() || rhs.span.from_expansion() { @@ -638,7 +519,7 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { SpanlessEq::new(cx).eq_expr(lhs, rhs) }; - for (i, j) in search_same(conds, hash, eq) { + for (i, j) in search_same(conds, |e| hash_expr(cx, e), eq) { span_lint_and_note( cx, SAME_FUNCTIONS_IN_IF_CONDITION, diff --git a/clippy_lints/src/create_dir.rs b/clippy_lints/src/create_dir.rs index 6bc4054a5ab..18d34370a7b 100644 --- a/clippy_lints/src/create_dir.rs +++ b/clippy_lints/src/create_dir.rs @@ -15,12 +15,12 @@ declare_clippy_lint! { /// Sometimes `std::fs::create_dir` is mistakenly chosen over `std::fs::create_dir_all`. /// /// ### Example - /// - /// ```rust + /// ```rust,ignore /// std::fs::create_dir("foo"); /// ``` + /// /// Use instead: - /// ```rust + /// ```rust,ignore /// std::fs::create_dir_all("foo"); /// ``` #[clippy::version = "1.48.0"] diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index 17deccf8c39..fe9f4f9ae3c 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -18,10 +18,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// dbg!(true) + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore /// true /// ``` #[clippy::version = "1.34.0"] diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 243dfd3a461..d99a1aa2969 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -18,15 +18,16 @@ declare_clippy_lint! { /// Checks for literal calls to `Default::default()`. /// /// ### Why is this bad? - /// It's more clear to the reader to use the name of the type whose default is - /// being gotten than the generic `Default`. + /// It's easier for the reader if the name of the type is used, rather than the + /// generic `Default`. /// /// ### Example /// ```rust - /// // Bad /// let s: String = Default::default(); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let s = String::default(); /// ``` #[clippy::version = "pre 1.29.0"] @@ -47,13 +48,13 @@ declare_clippy_lint! { /// Assignments to patterns that are of tuple type are not linted. /// /// ### Example - /// Bad: /// ``` /// # #[derive(Default)] /// # struct A { i: i32 } /// let mut a: A = Default::default(); /// a.i = 42; /// ``` + /// /// Use instead: /// ``` /// # #[derive(Default)] diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 3d9f9ed41ce..fb418a3251f 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::numeric_literal; use clippy_utils::source::snippet_opt; use if_chain::if_chain; @@ -76,7 +76,7 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { } /// Check whether a passed literal has potential to cause fallback or not. - fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>) { + fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>, emit_hir_id: HirId) { if_chain! { if !in_external_macro(self.cx.sess(), lit.span); if let Some(ty_bound) = self.ty_bounds.last(); @@ -101,14 +101,15 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { } }; let sugg = numeric_literal::format(&src, Some(suffix), is_float); - span_lint_and_sugg( + span_lint_hir_and_then( self.cx, DEFAULT_NUMERIC_FALLBACK, + emit_hir_id, lit.span, "default numeric fallback might occur", - "consider adding suffix", - sugg, - Applicability::MaybeIncorrect, + |diag| { + diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect); + } ); } } @@ -179,7 +180,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { ExprKind::Lit(lit) => { let ty = self.cx.typeck_results().expr_ty(expr); - self.check_lit(lit, ty); + self.check_lit(lit, ty, expr.hir_id); return; }, diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 527529965a9..b47441eff37 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::ty::peel_mid_ty_refs; @@ -30,13 +30,14 @@ declare_clippy_lint! { /// let a: &mut String = &mut String::from("foo"); /// let b: &str = a.deref(); /// ``` - /// Could be written as: + /// + /// Use instead: /// ```rust /// let a: &mut String = &mut String::from("foo"); /// let b = &*a; /// ``` /// - /// This lint excludes + /// This lint excludes: /// ```rust,ignore /// let _ = d.unwrap().deref(); /// ``` @@ -59,11 +60,13 @@ declare_clippy_lint! { /// ```rust /// fn fun(_a: &i32) {} /// - /// // Bad /// let x: &i32 = &&&&&&5; /// fun(&x); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # fn fun(_a: &i32) {} /// let x: &i32 = &5; /// fun(x); /// ``` @@ -82,13 +85,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let x = Some(""); /// if let Some(ref x) = x { /// // use `x` here /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let x = Some(""); /// if let Some(x) = x { /// // use `&x` here @@ -131,6 +135,7 @@ pub struct Dereferencing { struct StateData { /// Span of the top level expression span: Span, + hir_id: HirId, } enum State { @@ -165,6 +170,8 @@ struct RefPat { app: Applicability, /// All the replacements which need to be made. replacements: Vec<(Span, String)>, + /// The [`HirId`] that the lint should be emitted at. + hir_id: HirId, } impl<'tcx> LateLintPass<'tcx> for Dereferencing { @@ -218,7 +225,10 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)), target_mut, }, - StateData { span: expr.span }, + StateData { + span: expr.span, + hir_id: expr.hir_id, + }, )); }, RefOp::AddrOf => { @@ -290,7 +300,10 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { required_precedence, msg, }, - StateData { span: expr.span }, + StateData { + span: expr.span, + hir_id: expr.hir_id, + }, )); } }, @@ -383,6 +396,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { spans: vec![pat.span], app, replacements: vec![(pat.span, snip.into())], + hir_id: pat.hir_id }), ); } @@ -395,13 +409,15 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) { let replacements = pat.replacements; let app = pat.app; - span_lint_and_then( + let lint = if pat.always_deref { + NEEDLESS_BORROW + } else { + REF_BINDING_TO_REFERENCE + }; + span_lint_hir_and_then( cx, - if pat.always_deref { - NEEDLESS_BORROW - } else { - REF_BINDING_TO_REFERENCE - }, + lint, + pat.hir_id, pat.spans, "this pattern creates a reference to a reference", |diag| { @@ -638,19 +654,14 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: S } => { let mut app = Applicability::MachineApplicable; let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0; - span_lint_and_sugg( - cx, - NEEDLESS_BORROW, - data.span, - msg, - "change this to", - if required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) { + span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, msg, |diag| { + let sugg = if required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) { format!("({})", snip) } else { snip.into() - }, - app, - ); + }; + diag.span_suggestion(data.span, "change this to", sugg, app); + }); }, } } diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index e98691fd5bb..4d7f4076d7b 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -30,8 +30,7 @@ declare_clippy_lint! { /// } /// ``` /// - /// Could be written as: - /// + /// Use instead: /// ```rust /// #[derive(Default)] /// struct Foo { @@ -45,7 +44,6 @@ declare_clippy_lint! { /// specialized than what derive will produce. This lint can't detect the manual `impl` /// has exactly equal bounds, and therefore this lint is disabled for types with /// generic parameters. - /// #[clippy::version = "1.57.0"] pub DERIVABLE_IMPLS, complexity, diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 99347ebadc6..28f218a8e34 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -4,14 +4,19 @@ use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy}; use clippy_utils::{is_lint_allowed, match_def_path}; use if_chain::if_chain; use rustc_errors::Applicability; +use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor}; use rustc_hir::{ - self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, UnsafeSource, Unsafety, + self as hir, BlockCheckMode, BodyId, Constness, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, UnsafeSource, + Unsafety, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; -use rustc_middle::ty::subst::GenericArg; -use rustc_middle::ty::{self, BoundConstness, ImplPolarity, ParamEnv, PredicateKind, TraitPredicate, TraitRef, Ty}; +use rustc_middle::traits::Reveal; +use rustc_middle::ty::{ + self, Binder, BoundConstness, GenericParamDefKind, ImplPolarity, ParamEnv, PredicateKind, TraitPredicate, TraitRef, + Ty, TyCtxt, Visibility, +}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::sym; @@ -459,50 +464,18 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { if_chain! { if let ty::Adt(adt, substs) = ty.kind(); + if cx.tcx.visibility(adt.did()) == Visibility::Public; if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq); - if let Some(peq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::PartialEq); if let Some(def_id) = trait_ref.trait_def_id(); if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id); - // New `ParamEnv` replacing `T: PartialEq` with `T: Eq` - let param_env = ParamEnv::new( - cx.tcx.mk_predicates(cx.param_env.caller_bounds().iter().map(|p| { - let kind = p.kind(); - match kind.skip_binder() { - PredicateKind::Trait(p) - if p.trait_ref.def_id == peq_trait_def_id - && p.trait_ref.substs.get(0) == p.trait_ref.substs.get(1) - && matches!(p.trait_ref.self_ty().kind(), ty::Param(_)) - && p.constness == BoundConstness::NotConst - && p.polarity == ImplPolarity::Positive => - { - cx.tcx.mk_predicate(kind.rebind(PredicateKind::Trait(TraitPredicate { - trait_ref: TraitRef::new( - eq_trait_def_id, - cx.tcx.mk_substs([GenericArg::from(p.trait_ref.self_ty())].into_iter()), - ), - constness: BoundConstness::NotConst, - polarity: ImplPolarity::Positive, - }))) - }, - _ => p, - } - })), - cx.param_env.reveal(), - cx.param_env.constness(), - ); - if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, substs); + let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id); + if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]); + // If all of our fields implement `Eq`, we can implement `Eq` too + if adt + .all_fields() + .map(|f| f.ty(cx.tcx, substs)) + .all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[])); then { - // If all of our fields implement `Eq`, we can implement `Eq` too - for variant in adt.variants() { - for field in &variant.fields { - let ty = field.ty(cx.tcx, substs); - - if !implements_trait(cx, ty, eq_trait_def_id, substs) { - return; - } - } - } - span_lint_and_sugg( cx, DERIVE_PARTIAL_EQ_WITHOUT_EQ, @@ -515,3 +488,41 @@ fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_r } } } + +/// Creates the `ParamEnv` used for the give type's derived `Eq` impl. +fn param_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ParamEnv<'_> { + // Initial map from generic index to param def. + // Vec<(param_def, needs_eq)> + let mut params = tcx + .generics_of(did) + .params + .iter() + .map(|p| (p, matches!(p.kind, GenericParamDefKind::Type { .. }))) + .collect::>(); + + let ty_predicates = tcx.predicates_of(did).predicates; + for (p, _) in ty_predicates { + if let PredicateKind::Trait(p) = p.kind().skip_binder() + && p.trait_ref.def_id == eq_trait_id + && let ty::Param(self_ty) = p.trait_ref.self_ty().kind() + && p.constness == BoundConstness::NotConst + { + // Flag types which already have an `Eq` bound. + params[self_ty.index as usize].1 = false; + } + } + + ParamEnv::new( + tcx.mk_predicates(ty_predicates.iter().map(|&(p, _)| p).chain( + params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| { + tcx.mk_predicate(Binder::dummy(PredicateKind::Trait(TraitPredicate { + trait_ref: TraitRef::new(eq_trait_id, tcx.mk_substs([tcx.mk_param_from_def(param)].into_iter())), + constness: BoundConstness::NotConst, + polarity: ImplPolarity::Positive, + }))) + }), + )), + Reveal::UserFacing, + Constness::NotConst, + ) +} diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs index aaec88f50c7..da111e7378e 100644 --- a/clippy_lints/src/doc.rs +++ b/clippy_lints/src/doc.rs @@ -163,7 +163,7 @@ declare_clippy_lint! { /// } /// } /// ``` - #[clippy::version = "1.52.0"] + #[clippy::version = "1.51.0"] pub MISSING_PANICS_DOC, pedantic, "`pub fn` may panic without `# Panics` in doc comment" @@ -178,7 +178,7 @@ declare_clippy_lint! { /// if the `fn main()` is left implicit. /// /// ### Examples - /// ``````rust + /// ```rust /// /// An example of a doctest with a `main()` function /// /// /// /// # Examples @@ -191,7 +191,7 @@ declare_clippy_lint! { /// fn needless_main() { /// unimplemented!(); /// } - /// `````` + /// ``` #[clippy::version = "1.40.0"] pub NEEDLESS_DOCTEST_MAIN, style, diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs index 263a5b573c9..23b75104570 100644 --- a/clippy_lints/src/enum_variants.rs +++ b/clippy_lints/src/enum_variants.rs @@ -60,7 +60,8 @@ declare_clippy_lint! { /// struct BlackForestCake; /// } /// ``` - /// Could be written as: + /// + /// Use instead: /// ```rust /// mod cake { /// struct BlackForest; diff --git a/clippy_lints/src/eq_op.rs b/clippy_lints/src/eq_op.rs index c3176d987c6..2f4c90d07cf 100644 --- a/clippy_lints/src/eq_op.rs +++ b/clippy_lints/src/eq_op.rs @@ -52,15 +52,13 @@ declare_clippy_lint! { /// ### Why is this bad? /// It is more idiomatic to dereference the other argument. /// - /// ### Known problems - /// None - /// /// ### Example - /// ```ignore - /// // Bad + /// ```rust,ignore /// &x == y + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore /// x == *y /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 197cac86a57..a5a763c37d1 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -34,14 +34,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// xs.map(|x| foo(x)) + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore + /// // where `foo(_)` is a plain function that takes the exact argument type of `x`. /// xs.map(foo) /// ``` - /// where `foo(_)` is a plain function that takes the exact argument type of - /// `x`. #[clippy::version = "pre 1.29.0"] pub REDUNDANT_CLOSURE, style, diff --git a/clippy_lints/src/excessive_bools.rs b/clippy_lints/src/excessive_bools.rs index a2af10e2ba5..f7a92bc0795 100644 --- a/clippy_lints/src/excessive_bools.rs +++ b/clippy_lints/src/excessive_bools.rs @@ -54,12 +54,11 @@ declare_clippy_lint! { /// API easier to use. /// /// ### Example - /// Bad: /// ```rust,ignore /// fn f(is_round: bool, is_hot: bool) { ... } /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// enum Shape { /// Round, diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index f850ea31f4d..f2e07980963 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -45,10 +45,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let _: f32 = 16_777_217.0; // 16_777_216.0 + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let _: f32 = 16_777_216.0; /// let _: f64 = 16_777_217.0; /// ``` diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index ad031cbc09d..73261fb8a44 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -76,12 +76,13 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// pub fn foo(x: *const u8) { /// println!("{}", unsafe { *x }); /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore /// pub unsafe fn foo(x: *const u8) { /// println!("{}", unsafe { *x }); /// } diff --git a/clippy_lints/src/get_first.rs b/clippy_lints/src/get_first.rs index 0748ab45252..529f7babaa5 100644 --- a/clippy_lints/src/get_first.rs +++ b/clippy_lints/src/get_first.rs @@ -20,13 +20,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let x = vec![2, 3, 5]; /// let first_element = x.get(0); /// ``` + /// /// Use instead: /// ```rust - /// // Good /// let x = vec![2, 3, 5]; /// let first_element = x.first(); /// ``` diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index ae4158662d4..46654bc61e0 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -16,17 +16,21 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// let end: u32 = 10; - /// let start: u32 = 5; - /// + /// # let end: u32 = 10; + /// # let start: u32 = 5; /// let mut i: u32 = end - start; /// - /// // Bad /// if i != 0 { /// i -= 1; /// } + /// ``` + /// + /// Use instead: + /// ```rust + /// # let end: u32 = 10; + /// # let start: u32 = 5; + /// let mut i: u32 = end - start; /// - /// // Good /// i = i.saturating_sub(1); /// ``` #[clippy::version = "1.44.0"] @@ -48,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub { // Check if the conditional expression is a binary operation if let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind; - // Ensure that the binary operator is >, != and < + // Ensure that the binary operator is >, !=, or < if BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node; // Check if assign operation is done diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs index 9ce5b8e17a9..d0c6495e35a 100644 --- a/clippy_lints/src/index_refutable_slice.rs +++ b/clippy_lints/src/index_refutable_slice.rs @@ -45,7 +45,7 @@ declare_clippy_lint! { /// println!("{}", first); /// } /// ``` - #[clippy::version = "1.58.0"] + #[clippy::version = "1.59.0"] pub INDEX_REFUTABLE_SLICE, nursery, "avoid indexing on slices which could be destructed" diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 4ba7477add8..4a375752e1d 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -17,19 +17,20 @@ declare_clippy_lint! { /// ### Why is this bad? /// This will always panic at runtime. /// - /// ### Known problems - /// Hopefully none. - /// /// ### Example - /// ```no_run + /// ```rust,no_run /// # #![allow(const_err)] /// let x = [1, 2, 3, 4]; /// - /// // Bad /// x[9]; /// &x[2..9]; + /// ``` + /// + /// Use instead: + /// ```rust + /// # let x = [1, 2, 3, 4]; + /// // Index within bounds /// - /// // Good /// x[0]; /// x[3]; /// ``` @@ -49,42 +50,32 @@ declare_clippy_lint! { /// Indexing and slicing can panic at runtime and there are /// safe alternatives. /// - /// ### Known problems - /// Hopefully none. - /// /// ### Example /// ```rust,no_run /// // Vector /// let x = vec![0; 5]; /// - /// // Bad /// x[2]; /// &x[2..100]; - /// &x[2..]; - /// &x[..100]; - /// - /// // Good - /// x.get(2); - /// x.get(2..100); - /// x.get(2..); - /// x.get(..100); /// /// // Array /// let y = [0, 1, 2, 3]; /// - /// // Bad /// &y[10..100]; /// &y[10..]; - /// &y[..100]; + /// ``` + /// + /// Use instead: + /// ```rust + /// # #![allow(unused)] + /// + /// # let x = vec![0; 5]; + /// # let y = [0, 1, 2, 3]; + /// x.get(2); + /// x.get(2..100); /// - /// // Good - /// &y[2..]; - /// &y[..2]; - /// &y[0..3]; /// y.get(10); /// y.get(10..100); - /// y.get(10..); - /// y.get(..100); /// ``` #[clippy::version = "pre 1.29.0"] pub INDEXING_SLICING, diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index 41e1fc4e3c2..78b5ec8ec1e 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -41,6 +41,7 @@ declare_clippy_lint! { /// ### Example /// ```rust /// let infinite_iter = 0..; + /// # #[allow(unused)] /// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5)); /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/inherent_to_string.rs b/clippy_lints/src/inherent_to_string.rs index 55c04a1186f..39f68a8a1b4 100644 --- a/clippy_lints/src/inherent_to_string.rs +++ b/clippy_lints/src/inherent_to_string.rs @@ -14,12 +14,8 @@ declare_clippy_lint! { /// ### Why is this bad? /// This method is also implicitly defined if a type implements the `Display` trait. As the functionality of `Display` is much more versatile, it should be preferred. /// - /// ### Known problems - /// None - /// /// ### Example /// ```rust - /// // Bad /// pub struct A; /// /// impl A { @@ -29,8 +25,8 @@ declare_clippy_lint! { /// } /// ``` /// + /// Use instead: /// ```rust - /// // Good /// use std::fmt; /// /// pub struct A; @@ -54,12 +50,8 @@ declare_clippy_lint! { /// ### Why is this bad? /// This method is also implicitly defined if a type implements the `Display` trait. The less versatile inherent method will then shadow the implementation introduced by `Display`. /// - /// ### Known problems - /// None - /// /// ### Example /// ```rust - /// // Bad /// use std::fmt; /// /// pub struct A; @@ -77,8 +69,8 @@ declare_clippy_lint! { /// } /// ``` /// + /// Use instead: /// ```rust - /// // Good /// use std::fmt; /// /// pub struct A; diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs index 8db7b307ddb..9a944def3eb 100644 --- a/clippy_lints/src/int_plus_one.rs +++ b/clippy_lints/src/int_plus_one.rs @@ -21,8 +21,7 @@ declare_clippy_lint! { /// if x >= y + 1 {} /// ``` /// - /// Could be written as: - /// + /// Use instead: /// ```rust /// # let x = 1; /// # let y = 1; diff --git a/clippy_lints/src/integer_division.rs b/clippy_lints/src/integer_division.rs index fa786205678..3effba56826 100644 --- a/clippy_lints/src/integer_division.rs +++ b/clippy_lints/src/integer_division.rs @@ -15,11 +15,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let x = 3 / 2; /// println!("{}", x); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let x = 3f32 / 2f32; /// println!("{}", x); /// ``` diff --git a/clippy_lints/src/items_after_statements.rs b/clippy_lints/src/items_after_statements.rs index cdefe627efd..46d439b4497 100644 --- a/clippy_lints/src/items_after_statements.rs +++ b/clippy_lints/src/items_after_statements.rs @@ -17,7 +17,6 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// fn foo() { /// println!("cake"); /// } @@ -31,8 +30,8 @@ declare_clippy_lint! { /// } /// ``` /// + /// Use instead: /// ```rust - /// // Good /// fn foo() { /// println!("cake"); /// } diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index e10993ba7dd..289755bfec6 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -20,10 +20,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// pub const a = [0u32; 1_000_000]; + /// ``` /// - /// // Good + /// Use instead: + /// ```rust.ignore /// pub static a = [0u32; 1_000_000]; /// ``` #[clippy::version = "1.44.0"] diff --git a/clippy_lints/src/large_enum_variant.rs b/clippy_lints/src/large_enum_variant.rs index 63ac092dfaf..9be057bcf90 100644 --- a/clippy_lints/src/large_enum_variant.rs +++ b/clippy_lints/src/large_enum_variant.rs @@ -38,12 +38,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// enum Test { /// A(i32), /// B([i32; 8000]), /// } + /// ``` /// + /// Use instead: + /// ```rust /// // Possibly better /// enum Test2 { /// A(i32), diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index 8bef13c682d..84dd61a1e4b 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -22,10 +22,11 @@ declare_clippy_lint! { /// let included_bytes = include_bytes!("very_large_file.txt"); /// ``` /// - /// Instead, you can load the file at runtime: + /// Use instead: /// ```rust,ignore /// use std::fs; /// + /// // You can load the file at runtime /// let string = fs::read_to_string("very_large_file.txt")?; /// let bytes = fs::read("very_large_file.txt")?; /// ``` diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index cb1ef01f5ba..26c540e2223 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -45,13 +45,11 @@ declare_clippy_lint! { /// `std::mem::drop` conveys your intention better and is less error-prone. /// /// ### Example - /// - /// Bad: /// ```rust,ignore /// let _ = mutex.lock(); /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// let _lock = mutex.lock(); /// ``` @@ -75,24 +73,20 @@ declare_clippy_lint! { /// better and is less error-prone. /// /// ### Example - /// - /// Bad: - /// ```rust,ignore - /// struct Droppable; - /// impl Drop for Droppable { - /// fn drop(&mut self) {} - /// } + /// ```rust + /// # struct DroppableItem; /// { - /// let _ = Droppable; - /// // ^ dropped here + /// let _ = DroppableItem; + /// // ^ dropped here /// /* more code */ /// } /// ``` /// - /// Good: - /// ```rust,ignore + /// Use instead: + /// ```rust + /// # struct DroppableItem; /// { - /// let _droppable = Droppable; + /// let _droppable = DroppableItem; /// /* more code */ /// // dropped at end of scope /// } diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index d4ec046d0bb..8a2cfbff953 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -242,6 +242,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(needless_bool::NEEDLESS_BOOL), LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE), LintId::of(needless_late_init::NEEDLESS_LATE_INIT), + LintId::of(needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS), LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK), LintId::of(needless_update::NEEDLESS_UPDATE), LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), @@ -270,6 +271,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(ranges::RANGE_ZIP_WITH_LEN), LintId::of(ranges::REVERSED_EMPTY_RANGES), LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT), + LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC), LintId::of(redundant_clone::REDUNDANT_CLONE), LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL), LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES), diff --git a/clippy_lints/src/lib.register_correctness.rs b/clippy_lints/src/lib.register_correctness.rs index 50cdd0af923..92a3a0aabf1 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -55,6 +55,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(ptr::INVALID_NULL_PTR_USAGE), LintId::of(ptr::MUT_FROM_REF), LintId::of(ranges::REVERSED_EMPTY_RANGES), + LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC), LintId::of(regex::INVALID_REGEX), LintId::of(self_assignment::SELF_ASSIGNMENT), LintId::of(serde_api::SERDE_API_MISUSE), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index b927ba3b17c..8ad984c68b8 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -408,6 +408,7 @@ store.register_lints(&[ needless_continue::NEEDLESS_CONTINUE, needless_for_each::NEEDLESS_FOR_EACH, needless_late_init::NEEDLESS_LATE_INIT, + needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS, needless_pass_by_value::NEEDLESS_PASS_BY_VALUE, needless_question_mark::NEEDLESS_QUESTION_MARK, needless_update::NEEDLESS_UPDATE, @@ -458,6 +459,7 @@ store.register_lints(&[ ranges::RANGE_ZIP_WITH_LEN, ranges::REVERSED_EMPTY_RANGES, rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT, + read_zero_byte_vec::READ_ZERO_BYTE_VEC, redundant_clone::REDUNDANT_CLONE, redundant_closure_call::REDUNDANT_CLOSURE_CALL, redundant_else::REDUNDANT_ELSE, diff --git a/clippy_lints/src/lib.register_style.rs b/clippy_lints/src/lib.register_style.rs index 35575351784..b6992ae0ad2 100644 --- a/clippy_lints/src/lib.register_style.rs +++ b/clippy_lints/src/lib.register_style.rs @@ -91,6 +91,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK), LintId::of(mut_reference::UNNECESSARY_MUT_PASSED), LintId::of(needless_late_init::NEEDLESS_LATE_INIT), + LintId::of(needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS), LintId::of(neg_multiply::NEG_MULTIPLY), LintId::of(new_without_default::NEW_WITHOUT_DEFAULT), LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ee0416fc0ff..84898eae05a 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1,5 +1,3 @@ -// error-pattern:cargo-clippy - #![feature(array_windows)] #![feature(binary_heap_into_iter_sorted)] #![feature(box_patterns)] @@ -88,10 +86,11 @@ use rustc_session::Session; /// /// /// /// ### Example /// /// ```rust -/// /// // Bad /// /// Insert a short example of code that triggers the lint +/// /// ``` /// /// -/// /// // Good +/// /// Use instead: +/// /// ```rust /// /// Insert a short example of improved code that doesn't trigger the lint /// /// ``` /// pub LINT_NAME, @@ -315,6 +314,7 @@ mod needless_borrowed_ref; mod needless_continue; mod needless_for_each; mod needless_late_init; +mod needless_parens_on_range_literals; mod needless_pass_by_value; mod needless_question_mark; mod needless_update; @@ -348,6 +348,7 @@ mod pub_use; mod question_mark; mod ranges; mod rc_clone_in_vec_init; +mod read_zero_byte_vec; mod redundant_clone; mod redundant_closure_call; mod redundant_else; @@ -746,6 +747,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| Box::new(collapsible_if::CollapsibleIf)); store.register_early_pass(|| Box::new(items_after_statements::ItemsAfterStatements)); store.register_early_pass(|| Box::new(precedence::Precedence)); + store.register_late_pass(|| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals)); store.register_early_pass(|| Box::new(needless_continue::NeedlessContinue)); store.register_early_pass(|| Box::new(redundant_else::RedundantElse)); store.register_late_pass(|| Box::new(create_dir::CreateDir)); @@ -907,6 +909,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(swap_ptr_to_ref::SwapPtrToRef)); store.register_late_pass(|| Box::new(mismatching_type_param_order::TypeParamMismatch)); store.register_late_pass(|| Box::new(as_underscore::AsUnderscore)); + store.register_late_pass(|| Box::new(read_zero_byte_vec::ReadZeroByteVec)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 070c7e59142..93f5663312f 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -36,12 +36,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad: unnecessary lifetime annotations + /// // Unnecessary lifetime annotations /// fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 { /// x /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// fn elided(x: &u8, y: u8) -> &u8 { /// x /// } @@ -65,12 +67,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad: unnecessary lifetimes + /// // unnecessary lifetimes /// fn unused_lifetime<'a>(x: u8) { /// // .. /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// fn no_lifetime(x: u8) { /// // ... /// } diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index 9998712b852..fb2104861c8 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -22,11 +22,16 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let x: u64 = 61864918973511; + /// # let _: u64 = + /// 61864918973511 + /// # ; + /// ``` /// - /// // Good - /// let x: u64 = 61_864_918_973_511; + /// Use instead: + /// ```rust + /// # let _: u64 = + /// 61_864_918_973_511 + /// # ; /// ``` #[clippy::version = "pre 1.29.0"] pub UNREADABLE_LITERAL, @@ -46,6 +51,7 @@ declare_clippy_lint! { /// - Does not match on `_127` since that is a valid grouping for decimal and octal numbers /// /// ### Example + /// ```ignore /// `2_32` => `2_i32` /// `250_8 => `250_u8` /// ``` @@ -66,11 +72,16 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let x: u64 = 618_64_9189_73_511; + /// # let _: u64 = + /// 618_64_9189_73_511 + /// # ; + /// ``` /// - /// // Good - /// let x: u64 = 61_864_918_973_511; + /// Use instead: + /// ```rust + /// # let _: u64 = + /// 61_864_918_973_511 + /// # ; /// ``` #[clippy::version = "pre 1.29.0"] pub INCONSISTENT_DIGIT_GROUPING, @@ -125,9 +136,11 @@ declare_clippy_lint! { /// readable than a decimal representation. /// /// ### Example + /// ```text /// `255` => `0xFF` /// `65_535` => `0xFFFF` /// `4_042_322_160` => `0xF0F0_F0F0` + /// ``` #[clippy::version = "pre 1.29.0"] pub DECIMAL_LITERAL_REPRESENTATION, restriction, diff --git a/clippy_lints/src/loops/for_loops_over_fallibles.rs b/clippy_lints/src/loops/for_loops_over_fallibles.rs index 90530ebf003..77de90fd7b9 100644 --- a/clippy_lints/src/loops/for_loops_over_fallibles.rs +++ b/clippy_lints/src/loops/for_loops_over_fallibles.rs @@ -7,9 +7,22 @@ use rustc_lint::LateContext; use rustc_span::symbol::sym; /// Checks for `for` loops over `Option`s and `Result`s. -pub(super) fn check(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { +pub(super) fn check(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, method_name: Option<&str>) { let ty = cx.typeck_results().expr_ty(arg); if is_type_diagnostic_item(cx, ty, sym::Option) { + let help_string = if let Some(method_name) = method_name { + format!( + "consider replacing `for {0} in {1}.{method_name}()` with `if let Some({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ) + } else { + format!( + "consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ) + }; span_lint_and_help( cx, FOR_LOOPS_OVER_FALLIBLES, @@ -20,13 +33,22 @@ pub(super) fn check(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { snippet(cx, arg.span, "_") ), None, - &format!( - "consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`", - snippet(cx, pat.span, "_"), - snippet(cx, arg.span, "_") - ), + &help_string, ); } else if is_type_diagnostic_item(cx, ty, sym::Result) { + let help_string = if let Some(method_name) = method_name { + format!( + "consider replacing `for {0} in {1}.{method_name}()` with `if let Ok({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ) + } else { + format!( + "consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ) + }; span_lint_and_help( cx, FOR_LOOPS_OVER_FALLIBLES, @@ -37,11 +59,7 @@ pub(super) fn check(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { snippet(cx, arg.span, "_") ), None, - &format!( - "consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`", - snippet(cx, pat.span, "_"), - snippet(cx, arg.span, "_") - ), + &help_string, ); } } diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index d61be78895f..391de922e1e 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -42,7 +42,8 @@ declare_clippy_lint! { /// dst[i + 64] = src[i]; /// } /// ``` - /// Could be written as: + /// + /// Use instead: /// ```rust /// # let src = vec![1]; /// # let mut dst = vec![0; 65]; @@ -70,7 +71,8 @@ declare_clippy_lint! { /// println!("{}", vec[i]); /// } /// ``` - /// Could be written as: + /// + /// Use instead: /// ```rust /// let vec = vec!['a', 'b', 'c']; /// for i in vec { @@ -103,7 +105,8 @@ declare_clippy_lint! { /// // .. /// } /// ``` - /// can be rewritten to + /// + /// Use instead: /// ```rust /// # let y = vec![1]; /// for x in &y { @@ -188,6 +191,10 @@ declare_clippy_lint! { /// for x in &res { /// // .. /// } + /// + /// for x in res.iter() { + /// // .. + /// } /// ``` /// /// Use instead: @@ -282,7 +289,8 @@ declare_clippy_lint! { /// i += 1; /// } /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// # let v = vec![1]; /// # fn bar(bar: usize, baz: usize) {} @@ -469,7 +477,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// This kind of operation can be expressed more succinctly with - /// `vec![item;SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also + /// `vec![item; SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also /// have better performance. /// /// ### Example @@ -484,7 +492,8 @@ declare_clippy_lint! { /// vec.push(item2); /// } /// ``` - /// could be written as + /// + /// Use instead: /// ```rust /// let item1 = 2; /// let item2 = 3; @@ -512,7 +521,8 @@ declare_clippy_lint! { /// println!("{}", item); /// } /// ``` - /// could be written as + /// + /// Use instead: /// ```rust /// let item1 = 2; /// let item = &item1; @@ -586,7 +596,7 @@ declare_clippy_lint! { /// std::hint::spin_loop() /// } /// ``` - #[clippy::version = "1.59.0"] + #[clippy::version = "1.61.0"] pub MISSING_SPIN_LOOP, perf, "An empty busy waiting loop" @@ -695,10 +705,14 @@ fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { let method_name = method.ident.as_str(); // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x match method_name { - "iter" | "iter_mut" => explicit_iter_loop::check(cx, self_arg, arg, method_name), + "iter" | "iter_mut" => { + explicit_iter_loop::check(cx, self_arg, arg, method_name); + for_loops_over_fallibles::check(cx, pat, self_arg, Some(method_name)); + }, "into_iter" => { explicit_iter_loop::check(cx, self_arg, arg, method_name); explicit_into_iter_loop::check(cx, self_arg, arg); + for_loops_over_fallibles::check(cx, pat, self_arg, Some(method_name)); }, "next" => { next_loop_linted = iter_next_loop::check(cx, arg); @@ -708,6 +722,6 @@ fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { } if !next_loop_linted { - for_loops_over_fallibles::check(cx, pat, arg); + for_loops_over_fallibles::check(cx, pat, arg, None); } } diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 99d21466935..32de20f6531 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -117,13 +117,20 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { | ExprKind::Type(e, _) | ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) - | ExprKind::Struct(_, _, Some(e)) | ExprKind::Repeat(e, _) | ExprKind::DropTemps(e) => never_loop_expr(e, main_loop_id), ExprKind::Let(let_expr) => never_loop_expr(let_expr.init, main_loop_id), ExprKind::Array(es) | ExprKind::MethodCall(_, es, _) | ExprKind::Tup(es) => { never_loop_expr_all(&mut es.iter(), main_loop_id) }, + ExprKind::Struct(_, fields, base) => { + let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), main_loop_id); + if let Some(base) = base { + combine_both(fields, never_loop_expr(base, main_loop_id)) + } else { + fields + } + }, ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), main_loop_id), ExprKind::Binary(_, e1, e2) | ExprKind::Assign(e1, e2, _) @@ -180,8 +187,7 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { | InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise, }) .fold(NeverLoopResult::Otherwise, combine_both), - ExprKind::Struct(_, _, None) - | ExprKind::Yield(_, _) + ExprKind::Yield(_, _) | ExprKind::Closure { .. } | ExprKind::Path(_) | ExprKind::ConstBlock(_) diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index 4f8baf7efb0..16fefea5520 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -110,14 +110,9 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { arm1.span, "this match arm has an identical body to the `_` wildcard arm", |diag| { - diag.span_suggestion( - arm1.span, - "try removing the arm", - "", - Applicability::MaybeIncorrect, - ) - .help("or try changing either arm body") - .span_note(arm2.span, "`_` wildcard arm here"); + diag.span_suggestion(arm1.span, "try removing the arm", "", Applicability::MaybeIncorrect) + .help("or try changing either arm body") + .span_note(arm2.span, "`_` wildcard arm here"); }, ); } else { diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index d1e42f39e47..3e765173fb9 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -43,13 +43,16 @@ declare_clippy_lint! { /// ```rust /// # fn bar(stool: &str) {} /// # let x = Some("abc"); - /// // Bad /// match x { /// Some(ref foo) => bar(foo), /// _ => (), /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # fn bar(stool: &str) {} + /// # let x = Some("abc"); /// if let Some(ref foo) = x { /// bar(foo); /// } @@ -114,14 +117,15 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// match x { /// &A(ref y) => foo(y), /// &B => bar(), /// _ => frob(&x), /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore /// match *x { /// A(ref y) => foo(y), /// B => bar(), @@ -227,13 +231,16 @@ declare_clippy_lint! { /// ```rust /// let x: Option<()> = None; /// - /// // Bad /// let r: Option<&()> = match x { /// None => None, /// Some(ref v) => Some(v), /// }; + /// ``` + /// + /// Use instead: + /// ```rust + /// let x: Option<()> = None; /// - /// // Good /// let r: Option<&()> = x.as_ref(); /// ``` #[clippy::version = "pre 1.29.0"] @@ -257,13 +264,16 @@ declare_clippy_lint! { /// ```rust /// # enum Foo { A(usize), B(usize) } /// # let x = Foo::B(1); - /// // Bad /// match x { /// Foo::A(_) => {}, /// _ => {}, /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # enum Foo { A(usize), B(usize) } + /// # let x = Foo::B(1); /// match x { /// Foo::A(_) => {}, /// Foo::B(_) => {}, @@ -290,14 +300,17 @@ declare_clippy_lint! { /// ```rust /// # enum Foo { A, B, C } /// # let x = Foo::B; - /// // Bad /// match x { /// Foo::A => {}, /// Foo::B => {}, /// _ => {}, /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # enum Foo { A, B, C } + /// # let x = Foo::B; /// match x { /// Foo::A => {}, /// Foo::B => {}, @@ -320,14 +333,17 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// match "foo" { + /// # let s = "foo"; + /// match s { /// "a" => {}, /// "bar" | _ => {}, /// } + /// ``` /// - /// // Good - /// match "foo" { + /// Use instead: + /// ```rust + /// # let s = "foo"; + /// match s { /// "a" => {}, /// _ => {}, /// } @@ -389,15 +405,17 @@ declare_clippy_lint! { /// ```rust /// # let a = 1; /// # let b = 2; - /// - /// // Bad /// match (a, b) { /// (c, d) => { /// // useless match /// } /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let a = 1; + /// # let b = 2; /// let (c, d) = (a, b); /// ``` #[clippy::version = "1.43.0"] @@ -419,13 +437,16 @@ declare_clippy_lint! { /// # struct A { a: i32 } /// let a = A { a: 5 }; /// - /// // Bad /// match a { /// A { a: 5, .. } => {}, /// _ => {}, /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # struct A { a: i32 } + /// # let a = A { a: 5 }; /// match a { /// A { a: 5 } => {}, /// _ => {}, @@ -509,7 +530,6 @@ declare_clippy_lint! { /// ```rust /// let x = Some(5); /// - /// // Bad /// let a = match x { /// Some(0) => true, /// _ => false, @@ -520,8 +540,11 @@ declare_clippy_lint! { /// } else { /// false /// }; + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// let x = Some(5); /// let a = matches!(x, Some(0)); /// ``` #[clippy::version = "1.47.0"] @@ -695,19 +718,18 @@ declare_clippy_lint! { /// let arr = vec![0, 1, 2, 3]; /// let idx = 1; /// - /// // Bad /// match arr[idx] { /// 0 => println!("{}", 0), /// 1 => println!("{}", 3), /// _ => {}, /// } /// ``` + /// /// Use instead: /// ```rust, no_run /// let arr = vec![0, 1, 2, 3]; /// let idx = 1; /// - /// // Good /// match arr.get(idx) { /// Some(0) => println!("{}", 0), /// Some(1) => println!("{}", 3), diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 3efccd703a6..58c3e52e138 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -6,7 +6,7 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::def::Res; -use rustc_hir::{Expr, ExprKind, PatKind, QPath, UnOp}; +use rustc_hir::{Expr, ExprKind, PatKind, PathSegment, QPath, UnOp}; use rustc_lint::LateContext; use rustc_span::source_map::Span; use rustc_span::symbol::{sym, Symbol}; @@ -155,7 +155,15 @@ pub(super) fn check<'tcx>( } false }; - if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg); + + if match map_arg.kind { + ExprKind::MethodCall(method, [original_arg], _) => { + acceptable_methods(method) + && SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, original_arg) + }, + _ => SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg) + }; + then { let span = filter_span.with_hi(expr.span.hi()); let (filter_name, lint) = if is_find { @@ -171,3 +179,18 @@ pub(super) fn check<'tcx>( } } } + +fn acceptable_methods(method: &PathSegment<'_>) -> bool { + let methods: [Symbol; 8] = [ + sym::clone, + sym::as_ref, + sym!(copied), + sym!(cloned), + sym!(as_deref), + sym!(as_mut), + sym!(as_deref_mut), + sym!(to_owned), + ]; + + methods.contains(&method.ident.name) +} diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints/src/methods/iter_overeager_cloned.rs index 54c9ca435a4..06a39c5997e 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -1,70 +1,59 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; -use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy}; -use itertools::Itertools; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_opt; +use clippy_utils::ty::{get_associated_type, implements_trait, is_copy}; use rustc_errors::Applicability; -use rustc_hir as hir; +use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::sym; -use std::ops::Not; use super::ITER_OVEREAGER_CLONED; use crate::redundant_clone::REDUNDANT_CLONE; -/// lint overeager use of `cloned()` for `Iterator`s pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - recv: &'tcx hir::Expr<'_>, - name: &str, - map_arg: &[hir::Expr<'_>], + expr: &'tcx Expr<'_>, + cloned_call: &'tcx Expr<'_>, + cloned_recv: &'tcx Expr<'_>, + is_count: bool, + needs_into_iter: bool, ) { - // Check if it's iterator and get type associated with `Item`. - let inner_ty = if_chain! { - if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator); - let recv_ty = cx.typeck_results().expr_ty(recv); - if implements_trait(cx, recv_ty, iterator_trait_id, &[]); - if let Some(inner_ty) = get_iterator_item_ty(cx, cx.typeck_results().expr_ty_adjusted(recv)); - then { - inner_ty - } else { + let typeck = cx.typeck_results(); + if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator) + && let Some(method_id) = typeck.type_dependent_def_id(expr.hir_id) + && cx.tcx.trait_of_item(method_id) == Some(iter_id) + && let Some(method_id) = typeck.type_dependent_def_id(cloned_call.hir_id) + && cx.tcx.trait_of_item(method_id) == Some(iter_id) + && let cloned_recv_ty = typeck.expr_ty_adjusted(cloned_recv) + && let Some(iter_assoc_ty) = get_associated_type(cx, cloned_recv_ty, iter_id, "Item") + && matches!(*iter_assoc_ty.kind(), ty::Ref(_, ty, _) if !is_copy(cx, ty)) + { + if needs_into_iter + && let Some(into_iter_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator) + && !implements_trait(cx, iter_assoc_ty, into_iter_id, &[]) + { return; } - }; - match inner_ty.kind() { - ty::Ref(_, ty, _) if !is_copy(cx, *ty) => {}, - _ => return, - }; + let (lint, msg, trailing_clone) = if is_count { + (REDUNDANT_CLONE, "unneeded cloning of iterator items", "") + } else { + (ITER_OVEREAGER_CLONED, "unnecessarily eager cloning of iterator items", ".cloned()") + }; - let (lint, preserve_cloned) = match name { - "count" => (REDUNDANT_CLONE, false), - _ => (ITER_OVEREAGER_CLONED, true), - }; - let wildcard_params = map_arg.is_empty().not().then(|| "...").unwrap_or_default(); - let msg = format!( - "called `cloned().{}({})` on an `Iterator`. It may be more efficient to call `{}({}){}` instead", - name, - wildcard_params, - name, - wildcard_params, - preserve_cloned.then(|| ".cloned()").unwrap_or_default(), - ); - - span_lint_and_sugg( - cx, - lint, - expr.span, - &msg, - "try this", - format!( - "{}.{}({}){}", - snippet(cx, recv.span, ".."), - name, - map_arg.iter().map(|a| snippet(cx, a.span, "..")).join(", "), - preserve_cloned.then(|| ".cloned()").unwrap_or_default(), - ), - Applicability::MachineApplicable, - ); + span_lint_and_then( + cx, + lint, + expr.span, + msg, + |diag| { + let method_span = expr.span.with_lo(cloned_call.span.hi()); + if let Some(mut snip) = snippet_opt(cx, method_span) { + snip.push_str(trailing_clone); + let replace_span = expr.span.with_lo(cloned_recv.span.hi()); + diag.span_suggestion(replace_span, "try this", snip, Applicability::MachineApplicable); + } + } + ); + } } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 7308e74c323..9bb7bb7a7ab 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -124,28 +124,24 @@ declare_clippy_lint! { /// It's often inefficient to clone all elements of an iterator, when eventually, only some /// of them will be consumed. /// - /// ### Examples - /// ```rust - /// # let vec = vec!["string".to_string()]; - /// - /// // Bad - /// vec.iter().cloned().take(10); - /// - /// // Good - /// vec.iter().take(10).cloned(); - /// - /// // Bad - /// vec.iter().cloned().last(); - /// - /// // Good - /// vec.iter().last().cloned(); - /// - /// ``` /// ### Known Problems /// This `lint` removes the side of effect of cloning items in the iterator. /// A code that relies on that side-effect could fail. /// - #[clippy::version = "1.59.0"] + /// ### Examples + /// ```rust + /// # let vec = vec!["string".to_string()]; + /// vec.iter().cloned().take(10); + /// vec.iter().cloned().last(); + /// ``` + /// + /// Use instead: + /// ```rust + /// # let vec = vec!["string".to_string()]; + /// vec.iter().take(10).cloned(); + /// vec.iter().last().cloned(); + /// ``` + #[clippy::version = "1.60.0"] pub ITER_OVEREAGER_CLONED, perf, "using `cloned()` early with `Iterator::iter()` can lead to some performance inefficiencies" @@ -342,11 +338,12 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let x = Ok::<_, ()>(()); - /// - /// // Bad /// x.ok().expect("why did I do this again?"); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let x = Ok::<_, ()>(()); /// x.expect("why did I do this again?"); /// ``` #[clippy::version = "pre 1.29.0"] @@ -390,12 +387,13 @@ declare_clippy_lint! { /// ### Examples /// ```rust /// # let x = Some(1); - /// - /// // Bad /// x.unwrap_or_else(Default::default); /// x.unwrap_or_else(u32::default); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let x = Some(1); /// x.unwrap_or_default(); /// ``` #[clippy::version = "1.56.0"] @@ -453,11 +451,12 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let opt = Some(1); - /// - /// // Bad /// opt.map_or(None, |a| Some(a + 1)); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let opt = Some(1); /// opt.and_then(|a| Some(a + 1)); /// ``` #[clippy::version = "pre 1.29.0"] @@ -475,13 +474,12 @@ declare_clippy_lint! { /// `_.ok()`. /// /// ### Example - /// Bad: /// ```rust /// # let r: Result = Ok(1); /// assert_eq!(Some(1), r.map_or(None, Some)); /// ``` /// - /// Good: + /// Use instead: /// ```rust /// # let r: Result = Ok(1); /// assert_eq!(Some(1), r.ok()); @@ -538,7 +536,8 @@ declare_clippy_lint! { /// # let vec = vec![1]; /// vec.iter().filter(|x| **x == 0).next(); /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// # let vec = vec![1]; /// vec.iter().find(|x| **x == 0); @@ -562,7 +561,8 @@ declare_clippy_lint! { /// # let vec = vec![1]; /// vec.iter().skip_while(|x| **x == 0).next(); /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// # let vec = vec![1]; /// vec.iter().find(|x| **x != 0); @@ -586,11 +586,14 @@ declare_clippy_lint! { /// let vec = vec![vec![1]]; /// let opt = Some(5); /// - /// // Bad /// vec.iter().map(|x| x.iter()).flatten(); /// opt.map(|x| Some(x * 2)).flatten(); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let vec = vec![vec![1]]; + /// # let opt = Some(5); /// vec.iter().flat_map(|x| x.iter()); /// opt.and_then(|x| Some(x * 2)); /// ``` @@ -610,15 +613,16 @@ declare_clippy_lint! { /// less performant. /// /// ### Example - /// Bad: /// ```rust + /// # #![allow(unused)] /// (0_i32..10) /// .filter(|n| n.checked_add(1).is_some()) /// .map(|n| n.checked_add(1).unwrap()); /// ``` /// - /// Good: + /// Use instead: /// ```rust + /// # #[allow(unused)] /// (0_i32..10).filter_map(|n| n.checked_add(1)); /// ``` #[clippy::version = "1.51.0"] @@ -637,14 +641,13 @@ declare_clippy_lint! { /// less performant. /// /// ### Example - /// Bad: /// ```rust /// (0_i32..10) /// .find(|n| n.checked_add(1).is_some()) /// .map(|n| n.checked_add(1).unwrap()); /// ``` /// - /// Good: + /// Use instead: /// ```rust /// (0_i32..10).find_map(|n| n.checked_add(1)); /// ``` @@ -712,17 +715,20 @@ declare_clippy_lint! { /// /// ### Example /// ```rust + /// # #![allow(unused)] /// let vec = vec![1]; /// vec.iter().find(|x| **x == 0).is_some(); /// - /// let _ = "hello world".find("world").is_none(); + /// "hello world".find("world").is_none(); /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// let vec = vec![1]; /// vec.iter().any(|x| *x == 0); /// - /// let _ = !"hello world".contains("world"); + /// # #[allow(unused)] + /// !"hello world".contains("world"); /// ``` #[clippy::version = "pre 1.29.0"] pub SEARCH_IS_SOME, @@ -744,7 +750,8 @@ declare_clippy_lint! { /// let name = "foo"; /// if name.chars().next() == Some('_') {}; /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// let name = "foo"; /// if name.starts_with('_') {}; @@ -899,10 +906,13 @@ declare_clippy_lint! { /// # use std::rc::Rc; /// let x = Rc::new(1); /// - /// // Bad /// x.clone(); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # use std::rc::Rc; + /// # let x = Rc::new(1); /// Rc::clone(&x); /// ``` #[clippy::version = "pre 1.29.0"] @@ -1034,11 +1044,13 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// _.split("x"); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore /// _.split('x'); + /// ``` #[clippy::version = "pre 1.29.0"] pub SINGLE_CHAR_PATTERN, perf, @@ -1099,12 +1111,14 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # use std::collections::HashSet; - /// // Bad /// # let mut s = HashSet::new(); /// # s.insert(1); /// let x = s.iter().nth(0); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # use std::collections::HashSet; /// # let mut s = HashSet::new(); /// # s.insert(1); /// let x = s.iter().next(); @@ -1210,11 +1224,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let x = vec![2, 3, 5]; /// let last_element = x.get(x.len() - 1); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let x = vec![2, 3, 5]; /// let last_element = x.last(); /// ``` @@ -1273,10 +1288,14 @@ declare_clippy_lint! { /// let mut a = vec![1, 2, 3]; /// let mut b = vec![4, 5, 6]; /// - /// // Bad /// a.extend(b.drain(..)); + /// ``` + /// + /// Use instead: + /// ```rust + /// let mut a = vec![1, 2, 3]; + /// let mut b = vec![4, 5, 6]; /// - /// // Good /// a.append(&mut b); /// ``` #[clippy::version = "1.55.0"] @@ -1351,11 +1370,12 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let name = "_"; - /// - /// // Bad /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-'); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let name = "_"; /// name.ends_with('_') || name.ends_with('-'); /// ``` #[clippy::version = "pre 1.29.0"] @@ -1401,11 +1421,13 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// let _ = (0..3).fold(false, |acc, x| acc || x > 2); + /// # #[allow(unused)] + /// (0..3).fold(false, |acc, x| acc || x > 2); /// ``` - /// This could be written as: + /// + /// Use instead: /// ```rust - /// let _ = (0..3).any(|x| x > 2); + /// (0..3).any(|x| x > 2); /// ``` #[clippy::version = "pre 1.29.0"] pub UNNECESSARY_FOLD, @@ -1485,11 +1507,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let _ = (&vec![3, 4, 5]).into_iter(); + /// # let vec = vec![3, 4, 5]; + /// (&vec).into_iter(); + /// ``` /// - /// // Good - /// let _ = (&vec![3, 4, 5]).iter(); + /// Use instead: + /// ```rust + /// # let vec = vec![3, 4, 5]; + /// (&vec).iter(); /// ``` #[clippy::version = "1.32.0"] pub INTO_ITER_ON_REF, @@ -1704,13 +1729,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// let mut string = String::new(); + /// # let mut string = String::new(); /// string.insert_str(0, "R"); /// string.push_str("R"); /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust - /// let mut string = String::new(); + /// # let mut string = String::new(); /// string.insert(0, 'R'); /// string.push('R'); /// ``` @@ -1881,7 +1907,7 @@ declare_clippy_lint! { /// let x = [1, 2, 3]; /// let y: Vec<_> = x.iter().map(|x| 2*x).collect(); /// ``` - #[clippy::version = "1.52.0"] + #[clippy::version = "1.47.0"] pub MAP_IDENTITY, complexity, "using iterator.map(|x| x)" @@ -1897,11 +1923,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let _ = "Hello".bytes().nth(3); + /// # #[allow(unused)] + /// "Hello".bytes().nth(3); + /// ``` /// - /// // Good - /// let _ = "Hello".as_bytes().get(3); + /// Use instead: + /// ```rust + /// # #[allow(unused)] + /// "Hello".as_bytes().get(3); /// ``` #[clippy::version = "1.52.0"] pub BYTES_NTH, @@ -1945,15 +1974,19 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad + /// # #![allow(unused)] /// let some_vec = vec![0, 1, 2, 3]; - /// let _ = some_vec.iter().count(); - /// let _ = &some_vec[..].iter().count(); /// - /// // Good + /// some_vec.iter().count(); + /// &some_vec[..].iter().count(); + /// ``` + /// + /// Use instead: + /// ```rust /// let some_vec = vec![0, 1, 2, 3]; - /// let _ = some_vec.len(); - /// let _ = &some_vec[..].len(); + /// + /// some_vec.len(); + /// &some_vec[..].len(); /// ``` #[clippy::version = "1.52.0"] pub ITER_COUNT, @@ -1973,16 +2006,17 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let s = ""; + /// # let s = ""; /// for x in s.splitn(1, ":") { - /// // use x + /// // .. /// } + /// ``` /// - /// // Good - /// let s = ""; + /// Use instead: + /// ```rust + /// # let s = ""; /// for x in s.splitn(2, ":") { - /// // use x + /// // .. /// } /// ``` #[clippy::version = "1.54.0"] @@ -2000,10 +2034,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let x: String = std::iter::repeat('x').take(10).collect(); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let x: String = "x".repeat(10); /// ``` #[clippy::version = "1.54.0"] @@ -2021,7 +2056,6 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// let s = "key=value=add"; /// let (key, value) = s.splitn(2, '=').next_tuple()?; /// let value = s.splitn(2, '=').nth(1)?; @@ -2030,9 +2064,9 @@ declare_clippy_lint! { /// let key = parts.next()?; /// let value = parts.next()?; /// ``` + /// /// Use instead: /// ```rust,ignore - /// // Good /// let s = "key=value=add"; /// let (key, value) = s.split_once('=')?; /// let value = s.split_once('=')?.1; @@ -2057,17 +2091,16 @@ declare_clippy_lint! { /// that both functions return a lazy iterator. /// ### Example /// ```rust - /// // Bad /// let str = "key=value=add"; /// let _ = str.splitn(3, '=').next().unwrap(); /// ``` + /// /// Use instead: /// ```rust - /// // Good /// let str = "key=value=add"; /// let _ = str.split('=').next().unwrap(); /// ``` - #[clippy::version = "1.58.0"] + #[clippy::version = "1.59.0"] pub NEEDLESS_SPLITN, complexity, "usages of `str::splitn` that can be replaced with `str::split`" @@ -2098,7 +2131,7 @@ declare_clippy_lint! { /// foo(&path.to_string_lossy()); /// fn foo(s: &str) {} /// ``` - #[clippy::version = "1.58.0"] + #[clippy::version = "1.59.0"] pub UNNECESSARY_TO_OWNED, perf, "unnecessary calls to `to_owned`-like functions" @@ -2149,7 +2182,8 @@ declare_clippy_lint! { /// let a = Some(&1); /// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32> /// ``` - /// Could be written as: + /// + /// Use instead: /// ```rust /// let a = Some(&1); /// let b = a; @@ -2583,8 +2617,8 @@ impl Methods { }, _ => {}, }, - (name @ "count", args @ []) => match method_call(recv) { - Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), + ("count", []) => match method_call(recv) { + Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, true, false), Some((name2 @ ("into_iter" | "iter" | "iter_mut"), [recv2], _)) => { iter_count::check(cx, expr, recv2, name2); }, @@ -2614,9 +2648,9 @@ impl Methods { flat_map_identity::check(cx, expr, arg, span); flat_map_option::check(cx, expr, arg, span); }, - (name @ "flatten", args @ []) => match method_call(recv) { + ("flatten", []) => match method_call(recv) { Some(("map", [recv, map_arg], map_span)) => map_flatten::check(cx, expr, recv, map_arg, map_span), - Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), + Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, true), _ => {}, }, ("fold", [init, acc]) => unnecessary_fold::check(cx, expr, init, acc, span), @@ -2636,10 +2670,10 @@ impl Methods { unnecessary_join::check(cx, expr, recv, join_arg, span); } }, - ("last", args @ []) | ("skip", args @ [_]) => { + ("last", []) | ("skip", [_]) => { if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) { if let ("cloned", []) = (name2, args2) { - iter_overeager_cloned::check(cx, expr, recv2, name, args); + iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); } } }, @@ -2660,10 +2694,10 @@ impl Methods { map_identity::check(cx, expr, recv, m_arg, name, span); }, ("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map), - (name @ "next", args @ []) => { + ("next", []) => { if let Some((name2, [recv2, args2 @ ..], _)) = method_call(recv) { match (name2, args2) { - ("cloned", []) => iter_overeager_cloned::check(cx, expr, recv2, name, args), + ("cloned", []) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false), ("filter", [arg]) => filter_next::check(cx, expr, recv2, arg), ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv), ("iter", []) => iter_next_slice::check(cx, expr, recv2), @@ -2673,9 +2707,9 @@ impl Methods { } } }, - ("nth", args @ [n_arg]) => match method_call(recv) { + ("nth", [n_arg]) => match method_call(recv) { Some(("bytes", [recv2], _)) => bytes_nth::check(cx, expr, recv2, n_arg), - Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), + Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false), Some(("iter", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false), Some(("iter_mut", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true), _ => iter_nth_zero::check(cx, expr, recv, n_arg), @@ -2698,10 +2732,10 @@ impl Methods { } }, ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg), - ("take", args @ [_arg]) => { + ("take", [_arg]) => { if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) { if let ("cloned", []) = (name2, args2) { - iter_overeager_cloned::check(cx, expr, recv2, name, args); + iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); } } }, diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index 55665699453..01bf871198a 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -45,16 +45,13 @@ declare_clippy_lint! { /// dereferences, e.g., changing `*x` to `x` within the function. /// /// ### Example - /// ```rust,ignore - /// // Bad - /// fn foo(ref x: u8) -> bool { - /// true - /// } + /// ```rust + /// fn foo(ref _x: u8) {} + /// ``` /// - /// // Good - /// fn foo(x: &u8) -> bool { - /// true - /// } + /// Use instead: + /// ```rust + /// fn foo(_x: &u8) {} /// ``` #[clippy::version = "pre 1.29.0"] pub TOPLEVEL_REF_ARG, @@ -73,11 +70,12 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let x = 1.0; - /// - /// // Bad /// if x == f32::NAN { } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let x = 1.0f32; /// if x.is_nan() { } /// ``` #[clippy::version = "pre 1.29.0"] @@ -139,7 +137,8 @@ declare_clippy_lint! { /// # let y = String::from("foo"); /// if x.to_owned() == y {} /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// # let x = "foo"; /// # let y = String::from("foo"); @@ -232,10 +231,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let a = 0 as *const u32; + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let a = std::ptr::null::(); /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/misc_early/mod.rs b/clippy_lints/src/misc_early/mod.rs index 6860b60acbd..704918c0b97 100644 --- a/clippy_lints/src/misc_early/mod.rs +++ b/clippy_lints/src/misc_early/mod.rs @@ -34,13 +34,21 @@ declare_clippy_lint! { /// # } /// let f = Foo { a: 0, b: 0, c: 0 }; /// - /// // Bad /// match f { /// Foo { a: _, b: 0, .. } => {}, /// Foo { a: _, b: _, c: _ } => {}, /// } + /// ``` + /// + /// Use instead: + /// ```rust + /// # struct Foo { + /// # a: i32, + /// # b: i32, + /// # c: i32, + /// # } + /// let f = Foo { a: 0, b: 0, c: 0 }; /// - /// // Good /// match f { /// Foo { b: 0, .. } => {}, /// Foo { .. } => {}, @@ -62,10 +70,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// fn foo(a: i32, _a: i32) {} + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// fn bar(a: i32, _b: i32) {} /// ``` #[clippy::version = "pre 1.29.0"] @@ -103,11 +112,16 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let y = 0x1a9BAcD; + /// # let _ = + /// 0x1a9BAcD + /// # ; + /// ``` /// - /// // Good - /// let y = 0x1A9BACD; + /// Use instead: + /// ```rust + /// # let _ = + /// 0x1A9BACD + /// # ; /// ``` #[clippy::version = "pre 1.29.0"] pub MIXED_CASE_HEX_LITERALS, @@ -127,11 +141,16 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let y = 123832i32; + /// # let _ = + /// 123832i32 + /// # ; + /// ``` /// - /// // Good - /// let y = 123832_i32; + /// Use instead: + /// ```rust + /// # let _ = + /// 123832_i32 + /// # ; /// ``` #[clippy::version = "pre 1.29.0"] pub UNSEPARATED_LITERAL_SUFFIX, @@ -150,11 +169,16 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let y = 123832_i32; + /// # let _ = + /// 123832_i32 + /// # ; + /// ``` /// - /// // Good - /// let y = 123832i32; + /// Use instead: + /// ```rust + /// # let _ = + /// 123832i32 + /// # ; /// ``` #[clippy::version = "1.58.0"] pub SEPARATED_LITERAL_SUFFIX, @@ -234,14 +258,15 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let v = Some("abc"); - /// - /// // Bad /// match v { /// Some(x) => (), /// y @ _ => (), /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let v = Some("abc"); /// match v { /// Some(x) => (), /// y => (), @@ -262,6 +287,7 @@ declare_clippy_lint! { /// means there are 0 or more elements left. This can make a difference /// when refactoring, but shouldn't result in errors in the refactored code, /// since the wildcard pattern isn't used anyway. + /// /// ### Why is this bad? /// The wildcard pattern is unneeded as the rest pattern /// can match that element as well. @@ -270,13 +296,16 @@ declare_clippy_lint! { /// ```rust /// # struct TupleStruct(u32, u32, u32); /// # let t = TupleStruct(1, 2, 3); - /// // Bad /// match t { /// TupleStruct(0, .., _) => (), /// _ => (), /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # struct TupleStruct(u32, u32, u32); + /// # let t = TupleStruct(1, 2, 3); /// match t { /// TupleStruct(0, ..) => (), /// _ => (), diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index c3b850fbb9d..a2419c277e9 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -25,14 +25,16 @@ declare_clippy_lint! { /// ```rust /// let mut x = 0; /// - /// // Bad /// let a = { /// x = 1; /// 1 /// } + x; /// // Unclear whether a is 1 or 2. + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let mut x = 0; /// let tmp = { /// x = 1; /// 1 diff --git a/clippy_lints/src/mut_reference.rs b/clippy_lints/src/mut_reference.rs index 9d8f8999ce4..f434a655f8a 100644 --- a/clippy_lints/src/mut_reference.rs +++ b/clippy_lints/src/mut_reference.rs @@ -16,12 +16,17 @@ declare_clippy_lint! { /// the value. Also the code misleads about the intent of the call site. /// /// ### Example - /// ```ignore - /// // Bad - /// my_vec.push(&mut value) + /// ```rust + /// # let mut vec = Vec::new(); + /// # let mut value = 5; + /// vec.push(&mut value); + /// ``` /// - /// // Good - /// my_vec.push(&value) + /// Use instead: + /// ```rust + /// # let mut vec = Vec::new(); + /// # let value = 5; + /// vec.push(&value); /// ``` #[clippy::version = "pre 1.29.0"] pub UNNECESSARY_MUT_PASSED, diff --git a/clippy_lints/src/mutex_atomic.rs b/clippy_lints/src/mutex_atomic.rs index 73823779e49..a98577093ed 100644 --- a/clippy_lints/src/mutex_atomic.rs +++ b/clippy_lints/src/mutex_atomic.rs @@ -27,12 +27,13 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let y = true; - /// - /// // Bad /// # use std::sync::Mutex; /// let x = Mutex::new(&y); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let y = true; /// # use std::sync::atomic::AtomicBool; /// let x = AtomicBool::new(y); /// ``` @@ -60,8 +61,10 @@ declare_clippy_lint! { /// ```rust /// # use std::sync::Mutex; /// let x = Mutex::new(0usize); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// # use std::sync::atomic::AtomicUsize; /// let x = AtomicUsize::new(0usize); /// ``` diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index 778d49cb4b6..a4eec95b371 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -30,16 +30,21 @@ declare_clippy_lint! { /// shorter code. /// /// ### Example - /// ```rust,ignore + /// ```rust + /// # let x = true; /// if x { /// false /// } else { /// true /// } + /// # ; /// ``` - /// Could be written as - /// ```rust,ignore + /// + /// Use instead: + /// ```rust + /// # let x = true; /// !x + /// # ; /// ``` #[clippy::version = "pre 1.29.0"] pub NEEDLESS_BOOL, diff --git a/clippy_lints/src/needless_borrowed_ref.rs b/clippy_lints/src/needless_borrowed_ref.rs index 0fcc419e722..05c012b92e8 100644 --- a/clippy_lints/src/needless_borrowed_ref.rs +++ b/clippy_lints/src/needless_borrowed_ref.rs @@ -27,16 +27,17 @@ declare_clippy_lint! { /// ``` /// /// ### Example - /// Bad: /// ```rust /// let mut v = Vec::::new(); - /// let _ = v.iter_mut().filter(|&ref a| a.is_empty()); + /// # #[allow(unused)] + /// v.iter_mut().filter(|&ref a| a.is_empty()); /// ``` /// - /// Good: + /// Use instead: /// ```rust /// let mut v = Vec::::new(); - /// let _ = v.iter_mut().filter(|a| a.is_empty()); + /// # #[allow(unused)] + /// v.iter_mut().filter(|a| a.is_empty()); /// ``` #[clippy::version = "pre 1.29.0"] pub NEEDLESS_BORROWED_REFERENCE, diff --git a/clippy_lints/src/needless_late_init.rs b/clippy_lints/src/needless_late_init.rs index 1f8c4c85cc2..ff2999b1f4a 100644 --- a/clippy_lints/src/needless_late_init.rs +++ b/clippy_lints/src/needless_late_init.rs @@ -56,7 +56,7 @@ declare_clippy_lint! { /// -1 /// }; /// ``` - #[clippy::version = "1.58.0"] + #[clippy::version = "1.59.0"] pub NEEDLESS_LATE_INIT, style, "late initializations that can be replaced by a `let` statement with an initializer" @@ -185,14 +185,14 @@ fn assignment_suggestions<'tcx>( let suggestions = assignments .iter() - .map(|assignment| Some((assignment.span.until(assignment.rhs_span), String::new()))) - .chain(assignments.iter().map(|assignment| { - Some(( + .flat_map(|assignment| { + [ + assignment.span.until(assignment.rhs_span), assignment.rhs_span.shrink_to_hi().with_hi(assignment.span.hi()), - String::new(), - )) - })) - .collect::>>()?; + ] + }) + .map(|span| (span, String::new())) + .collect::>(); match suggestions.len() { // All of `exprs` are never types diff --git a/clippy_lints/src/needless_parens_on_range_literals.rs b/clippy_lints/src/needless_parens_on_range_literals.rs new file mode 100644 index 00000000000..6e54b243c03 --- /dev/null +++ b/clippy_lints/src/needless_parens_on_range_literals.rs @@ -0,0 +1,87 @@ +use clippy_utils::{ + diagnostics::span_lint_and_then, + higher, + source::{snippet, snippet_with_applicability}, +}; + +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; + +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// The lint checks for parenthesis on literals in range statements that are + /// superfluous. + /// + /// ### Why is this bad? + /// Having superfluous parenthesis makes the code less readable + /// overhead when reading. + /// + /// ### Example + /// + /// ```rust + /// for i in (0)..10 { + /// println!("{i}"); + /// } + /// ``` + /// + /// Use instead: + /// + /// ```rust + /// for i in 0..10 { + /// println!("{i}"); + /// } + /// ``` + #[clippy::version = "1.63.0"] + pub NEEDLESS_PARENS_ON_RANGE_LITERALS, + style, + "needless parenthesis on range literals can be removed" +} + +declare_lint_pass!(NeedlessParensOnRangeLiterals => [NEEDLESS_PARENS_ON_RANGE_LITERALS]); + +fn snippet_enclosed_in_parenthesis(snippet: &str) -> bool { + snippet.starts_with('(') && snippet.ends_with(')') +} + +fn check_for_parens(cx: &LateContext<'_>, e: &Expr<'_>, is_start: bool) { + if is_start && + let ExprKind::Lit(ref literal) = e.kind && + let ast::LitKind::Float(_sym, ast::LitFloatType::Unsuffixed) = literal.node + { + // don't check floating point literals on the start expression of a range + return; + } + if_chain! { + if let ExprKind::Lit(ref literal) = e.kind; + // the indicator that parenthesis surround the literal is that the span of the expression and the literal differ + if (literal.span.data().hi - literal.span.data().lo) != (e.span.data().hi - e.span.data().lo); + // inspect the source code of the expression for parenthesis + if snippet_enclosed_in_parenthesis(&snippet(cx, e.span, "")); + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_then(cx, NEEDLESS_PARENS_ON_RANGE_LITERALS, e.span, + "needless parenthesis on range literals can be removed", + |diag| { + let suggestion = snippet_with_applicability(cx, literal.span, "_", &mut applicability); + diag.span_suggestion(e.span, "try", suggestion, applicability); + }); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for NeedlessParensOnRangeLiterals { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some(higher::Range { start, end, .. }) = higher::Range::hir(expr) { + if let Some(start) = start { + check_for_parens(cx, start, true); + } + if let Some(end) = end { + check_for_parens(cx, end, false); + } + } + } +} diff --git a/clippy_lints/src/needless_update.rs b/clippy_lints/src/needless_update.rs index c87c174ef73..0bd29d1776b 100644 --- a/clippy_lints/src/needless_update.rs +++ b/clippy_lints/src/needless_update.rs @@ -24,16 +24,17 @@ declare_clippy_lint! { /// # z: i32, /// # } /// # let zero_point = Point { x: 0, y: 0, z: 0 }; - /// - /// // Bad /// Point { /// x: 1, /// y: 1, /// z: 1, /// ..zero_point /// }; + /// ``` /// - /// // Ok + /// Use instead: + /// ```rust,ignore + /// // Missing field `z` /// Point { /// x: 1, /// y: 1, diff --git a/clippy_lints/src/neg_cmp_op_on_partial_ord.rs b/clippy_lints/src/neg_cmp_op_on_partial_ord.rs index efe31a15441..a7e0e35787c 100644 --- a/clippy_lints/src/neg_cmp_op_on_partial_ord.rs +++ b/clippy_lints/src/neg_cmp_op_on_partial_ord.rs @@ -19,17 +19,17 @@ declare_clippy_lint! { /// /// ### Example /// ```rust + /// let a = 1.0; + /// let b = f64::NAN; + /// + /// let not_less_or_equal = !(a <= b); + /// ``` + /// + /// Use instead: + /// ```rust /// use std::cmp::Ordering; - /// - /// // Bad - /// let a = 1.0; - /// let b = f64::NAN; - /// - /// let _not_less_or_equal = !(a <= b); - /// - /// // Good - /// let a = 1.0; - /// let b = f64::NAN; + /// # let a = 1.0; + /// # let b = f64::NAN; /// /// let _not_less_or_equal = match a.partial_cmp(&b) { /// None | Some(Ordering::Greater) => true, diff --git a/clippy_lints/src/neg_multiply.rs b/clippy_lints/src/neg_multiply.rs index 707f3b2181a..ce6bb38b7c0 100644 --- a/clippy_lints/src/neg_multiply.rs +++ b/clippy_lints/src/neg_multiply.rs @@ -19,12 +19,13 @@ declare_clippy_lint! { /// This only catches integers (for now). /// /// ### Example - /// ```ignore - /// // Bad + /// ```rust,ignore /// let a = x * -1; + /// ``` /// - /// // Good - /// let b = -x; + /// Use instead: + /// ```rust,ignore + /// let a = -x; /// ``` #[clippy::version = "pre 1.29.0"] pub NEG_MULTIPLY, diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 7163cfe5e3a..1727275a4e0 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -59,12 +59,14 @@ declare_clippy_lint! { /// ```rust /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; /// - /// // Bad. /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12); /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct + /// ``` /// - /// // Good. + /// Use instead: + /// ```rust + /// # use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; /// static STATIC_ATOM: AtomicUsize = AtomicUsize::new(15); /// STATIC_ATOM.store(9, SeqCst); /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance @@ -105,11 +107,15 @@ declare_clippy_lint! { /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12); /// - /// // Bad. /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct + /// ``` + /// + /// Use instead: + /// ```rust + /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; + /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12); /// - /// // Good. /// static STATIC_ATOM: AtomicUsize = CONST_ATOM; /// STATIC_ATOM.store(9, SeqCst); /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance diff --git a/clippy_lints/src/octal_escapes.rs b/clippy_lints/src/octal_escapes.rs index e8532db4f71..3ab4b6c4f6f 100644 --- a/clippy_lints/src/octal_escapes.rs +++ b/clippy_lints/src/octal_escapes.rs @@ -33,15 +33,16 @@ declare_clippy_lint! { /// /// # Example /// ```rust - /// // Bad /// let one = "\033[1m Bold? \033[0m"; // \033 intended as escape /// let two = "\033\0"; // \033 intended as null-3-3 + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let one = "\x1b[1mWill this be bold?\x1b[0m"; /// let two = "\x0033\x00"; /// ``` - #[clippy::version = "1.58.0"] + #[clippy::version = "1.59.0"] pub OCTAL_ESCAPES, suspicious, "string escape sequences looking like octal characters" diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index de5f77f3ad9..677ac998b56 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -87,7 +87,7 @@ declare_clippy_lint! { /// # print!("{}", f(1)); /// # } /// ``` - #[clippy::version = "1.60.0"] + #[clippy::version = "1.61.0"] pub ONLY_USED_IN_RECURSION, nursery, "arguments that is only used in recursion can be removed" diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index 5a93431f25a..05ab62786f4 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -57,12 +57,11 @@ declare_clippy_lint! { /// ### Example /// /// ```rust - /// // Bad /// fn foo(v: &u32) {} /// ``` /// + /// Use instead: /// ```rust - /// // Better /// fn foo(v: u32) {} /// ``` #[clippy::version = "pre 1.29.0"] @@ -89,14 +88,13 @@ declare_clippy_lint! { /// #[derive(Clone, Copy)] /// struct TooLarge([u8; 2048]); /// - /// // Bad /// fn foo(v: TooLarge) {} /// ``` - /// ```rust - /// #[derive(Clone, Copy)] - /// struct TooLarge([u8; 2048]); /// - /// // Good + /// Use instead: + /// ```rust + /// # #[derive(Clone, Copy)] + /// # struct TooLarge([u8; 2048]); /// fn foo(v: &TooLarge) {} /// ``` #[clippy::version = "1.49.0"] diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 0b96f6ff683..b06eba13d2f 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -48,10 +48,11 @@ declare_clippy_lint! { /// /// ### Example /// ```ignore - /// // Bad /// fn foo(&Vec) { .. } + /// ``` /// - /// // Good + /// Use instead: + /// ```ignore /// fn foo(&[u32]) { .. } /// ``` #[clippy::version = "pre 1.29.0"] @@ -70,15 +71,18 @@ declare_clippy_lint! { /// method instead /// /// ### Example - /// ```ignore - /// // Bad - /// if x == ptr::null { - /// .. - /// } + /// ```rust,ignore + /// use std::ptr; /// - /// // Good + /// if x == ptr::null { + /// // .. + /// } + /// ``` + /// + /// Use instead: + /// ```rust,ignore /// if x.is_null() { - /// .. + /// // .. /// } /// ``` #[clippy::version = "pre 1.29.0"] @@ -129,12 +133,12 @@ declare_clippy_lint! { /// /// ### Example /// ```ignore - /// // Bad. Undefined behavior + /// // Undefined behavior /// unsafe { std::slice::from_raw_parts(ptr::null(), 0); } /// ``` /// + /// Use instead: /// ```ignore - /// // Good /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); } /// ``` #[clippy::version = "1.53.0"] diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index eea036178b8..547d4da8187 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -27,12 +27,13 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let x = vec![1]; - /// x.iter().zip(0..x.len()); + /// let _ = x.iter().zip(0..x.len()); /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// # let x = vec![1]; - /// x.iter().enumerate(); + /// let _ = x.iter().enumerate(); /// ``` #[clippy::version = "pre 1.29.0"] pub RANGE_ZIP_WITH_LEN, @@ -65,12 +66,21 @@ declare_clippy_lint! { /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)). /// /// ### Example - /// ```rust,ignore - /// for x..(y+1) { .. } + /// ```rust + /// # let x = 0; + /// # let y = 1; + /// for i in x..(y+1) { + /// // .. + /// } /// ``` - /// Could be written as - /// ```rust,ignore - /// for x..=y { .. } + /// + /// Use instead: + /// ```rust + /// # let x = 0; + /// # let y = 1; + /// for i in x..=y { + /// // .. + /// } /// ``` #[clippy::version = "pre 1.29.0"] pub RANGE_PLUS_ONE, @@ -94,12 +104,21 @@ declare_clippy_lint! { /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)). /// /// ### Example - /// ```rust,ignore - /// for x..=(y-1) { .. } + /// ```rust + /// # let x = 0; + /// # let y = 1; + /// for i in x..=(y-1) { + /// // .. + /// } /// ``` - /// Could be written as - /// ```rust,ignore - /// for x..y { .. } + /// + /// Use instead: + /// ```rust + /// # let x = 0; + /// # let y = 1; + /// for i in x..y { + /// // .. + /// } /// ``` #[clippy::version = "pre 1.29.0"] pub RANGE_MINUS_ONE, diff --git a/clippy_lints/src/read_zero_byte_vec.rs b/clippy_lints/src/read_zero_byte_vec.rs new file mode 100644 index 00000000000..9538a810473 --- /dev/null +++ b/clippy_lints/src/read_zero_byte_vec.rs @@ -0,0 +1,142 @@ +use clippy_utils::{ + diagnostics::{span_lint, span_lint_and_sugg}, + higher::{get_vec_init_kind, VecInitKind}, + source::snippet, + visitors::expr_visitor_no_bodies, +}; +use hir::{intravisit::Visitor, ExprKind, Local, PatKind, PathSegment, QPath, StmtKind}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// This lint catches reads into a zero-length `Vec`. + /// Especially in the case of a call to `with_capacity`, this lint warns that read + /// gets the number of bytes from the `Vec`'s length, not its capacity. + /// + /// ### Why is this bad? + /// Reading zero bytes is almost certainly not the intended behavior. + /// + /// ### Known problems + /// In theory, a very unusual read implementation could assign some semantic meaning + /// to zero-byte reads. But it seems exceptionally unlikely that code intending to do + /// a zero-byte read would allocate a `Vec` for it. + /// + /// ### Example + /// ```rust + /// use std::io; + /// fn foo(mut f: F) { + /// let mut data = Vec::with_capacity(100); + /// f.read(&mut data).unwrap(); + /// } + /// ``` + /// Use instead: + /// ```rust + /// use std::io; + /// fn foo(mut f: F) { + /// let mut data = Vec::with_capacity(100); + /// data.resize(100, 0); + /// f.read(&mut data).unwrap(); + /// } + /// ``` + #[clippy::version = "1.63.0"] + pub READ_ZERO_BYTE_VEC, + correctness, + "checks for reads into a zero-length `Vec`" +} +declare_lint_pass!(ReadZeroByteVec => [READ_ZERO_BYTE_VEC]); + +impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &hir::Block<'tcx>) { + for (idx, stmt) in block.stmts.iter().enumerate() { + if !stmt.span.from_expansion() + // matches `let v = Vec::new();` + && let StmtKind::Local(local) = stmt.kind + && let Local { pat, init: Some(init), .. } = local + && let PatKind::Binding(_, _, ident, _) = pat.kind + && let Some(vec_init_kind) = get_vec_init_kind(cx, init) + { + // finds use of `_.read(&mut v)` + let mut read_found = false; + let mut visitor = expr_visitor_no_bodies(|expr| { + if let ExprKind::MethodCall(path, [_self, arg], _) = expr.kind + && let PathSegment { ident: read_or_read_exact, .. } = *path + && matches!(read_or_read_exact.as_str(), "read" | "read_exact") + && let ExprKind::AddrOf(_, hir::Mutability::Mut, inner) = arg.kind + && let ExprKind::Path(QPath::Resolved(None, inner_path)) = inner.kind + && let [inner_seg] = inner_path.segments + && ident.name == inner_seg.ident.name + { + read_found = true; + } + !read_found + }); + + let next_stmt_span; + if idx == block.stmts.len() - 1 { + // case { .. stmt; expr } + if let Some(e) = block.expr { + visitor.visit_expr(e); + next_stmt_span = e.span; + } else { + return; + } + } else { + // case { .. stmt; stmt; .. } + let next_stmt = &block.stmts[idx + 1]; + visitor.visit_stmt(next_stmt); + next_stmt_span = next_stmt.span; + } + drop(visitor); + + if read_found && !next_stmt_span.from_expansion() { + let applicability = Applicability::MaybeIncorrect; + match vec_init_kind { + VecInitKind::WithConstCapacity(len) => { + span_lint_and_sugg( + cx, + READ_ZERO_BYTE_VEC, + next_stmt_span, + "reading zero byte data to `Vec`", + "try", + format!("{}.resize({}, 0); {}", + ident.as_str(), + len, + snippet(cx, next_stmt_span, "..") + ), + applicability, + ); + } + VecInitKind::WithExprCapacity(hir_id) => { + let e = cx.tcx.hir().expect_expr(hir_id); + span_lint_and_sugg( + cx, + READ_ZERO_BYTE_VEC, + next_stmt_span, + "reading zero byte data to `Vec`", + "try", + format!("{}.resize({}, 0); {}", + ident.as_str(), + snippet(cx, e.span, ".."), + snippet(cx, next_stmt_span, "..") + ), + applicability, + ); + } + _ => { + span_lint( + cx, + READ_ZERO_BYTE_VEC, + next_stmt_span, + "reading zero byte data to `Vec`", + ); + + } + } + } + } + } + } +} diff --git a/clippy_lints/src/redundant_closure_call.rs b/clippy_lints/src/redundant_closure_call.rs index 4c2016fe3f7..65ed798867d 100644 --- a/clippy_lints/src/redundant_closure_call.rs +++ b/clippy_lints/src/redundant_closure_call.rs @@ -23,12 +23,13 @@ declare_clippy_lint! { /// complexity. /// /// ### Example - /// ```rust,ignore - /// // Bad - /// let a = (|| 42)() + /// ```rust + /// let a = (|| 42)(); + /// ``` /// - /// // Good - /// let a = 42 + /// Use instead: + /// ```rust + /// let a = 42; /// ``` #[clippy::version = "pre 1.29.0"] pub REDUNDANT_CLOSURE_CALL, diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index 25a9072ef6e..db6c97f3739 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -60,7 +60,7 @@ declare_clippy_lint! { /// let vec = vec![1, 2, 3]; /// let slice = &*vec; /// ``` - #[clippy::version = "1.60.0"] + #[clippy::version = "1.61.0"] pub DEREF_BY_SLICING, restriction, "slicing instead of dereferencing" diff --git a/clippy_lints/src/reference.rs b/clippy_lints/src/reference.rs index f789cec6d6a..a642e2da3ba 100644 --- a/clippy_lints/src/reference.rs +++ b/clippy_lints/src/reference.rs @@ -21,11 +21,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// let a = f(*&mut b); /// let c = *&d; + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore /// let a = f(b); /// let c = d; /// ``` diff --git a/clippy_lints/src/return_self_not_must_use.rs b/clippy_lints/src/return_self_not_must_use.rs index 91e5e1e8b28..60be6bd335f 100644 --- a/clippy_lints/src/return_self_not_must_use.rs +++ b/clippy_lints/src/return_self_not_must_use.rs @@ -26,19 +26,20 @@ declare_clippy_lint! { /// if it was added on constructors for example. /// /// ### Example - /// Missing attribute /// ```rust /// pub struct Bar; /// impl Bar { - /// // Bad + /// // Missing attribute /// pub fn bar(&self) -> Self { /// Self /// } /// } /// ``` /// - /// It's better to have the `#[must_use]` attribute on the method like this: + /// Use instead: /// ```rust + /// # { + /// // It's better to have the `#[must_use]` attribute on the method like this: /// pub struct Bar; /// impl Bar { /// #[must_use] @@ -46,10 +47,10 @@ declare_clippy_lint! { /// Self /// } /// } - /// ``` + /// # } /// - /// Or on the type definition like this: - /// ```rust + /// # { + /// // Or on the type definition like this: /// #[must_use] /// pub struct Bar; /// impl Bar { @@ -57,6 +58,7 @@ declare_clippy_lint! { /// Self /// } /// } + /// # } /// ``` #[clippy::version = "1.59.0"] pub RETURN_SELF_NOT_MUST_USE, diff --git a/clippy_lints/src/same_name_method.rs b/clippy_lints/src/same_name_method.rs index c5c174cc8f6..20184d54b76 100644 --- a/clippy_lints/src/same_name_method.rs +++ b/clippy_lints/src/same_name_method.rs @@ -1,7 +1,7 @@ -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_hir_and_then; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind}; +use rustc_hir::{HirId, Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::AssocKind; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -42,11 +42,12 @@ declare_clippy_lint! { declare_lint_pass!(SameNameMethod => [SAME_NAME_METHOD]); struct ExistingName { - impl_methods: BTreeMap, + impl_methods: BTreeMap, trait_methods: BTreeMap>, } impl<'tcx> LateLintPass<'tcx> for SameNameMethod { + #[expect(clippy::too_many_lines)] fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { let mut map = FxHashMap::::default(); @@ -97,10 +98,11 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { }; let mut check_trait_method = |method_name: Symbol, trait_method_span: Span| { - if let Some(impl_span) = existing_name.impl_methods.get(&method_name) { - span_lint_and_then( + if let Some((impl_span, hir_id)) = existing_name.impl_methods.get(&method_name) { + span_lint_hir_and_then( cx, SAME_NAME_METHOD, + *hir_id, *impl_span, "method's name is the same as an existing method in a trait", |diag| { @@ -136,10 +138,12 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { }) { let method_name = impl_item_ref.ident.name; let impl_span = impl_item_ref.span; + let hir_id = impl_item_ref.id.hir_id(); if let Some(trait_spans) = existing_name.trait_methods.get(&method_name) { - span_lint_and_then( + span_lint_hir_and_then( cx, SAME_NAME_METHOD, + hir_id, impl_span, "method's name is the same as an existing method in a trait", |diag| { @@ -152,7 +156,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { }, ); } - existing_name.impl_methods.insert(method_name, impl_span); + existing_name.impl_methods.insert(method_name, (impl_span, hir_id)); } }, } diff --git a/clippy_lints/src/shadow.rs b/clippy_lints/src/shadow.rs index 4f74c1e44c2..bf318c055da 100644 --- a/clippy_lints/src/shadow.rs +++ b/clippy_lints/src/shadow.rs @@ -23,10 +23,12 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let x = 1; - /// // Bad /// let x = &x; + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let x = 1; /// let y = &x; // use different variable name /// ``` #[clippy::version = "pre 1.29.0"] @@ -79,11 +81,14 @@ declare_clippy_lint! { /// # let y = 1; /// # let z = 2; /// let x = y; - /// - /// // Bad /// let x = z; // shadows the earlier binding + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let y = 1; + /// # let z = 2; + /// let x = y; /// let w = z; // use different variable name /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/single_char_lifetime_names.rs b/clippy_lints/src/single_char_lifetime_names.rs index aa306a630c4..3dc995e2fa5 100644 --- a/clippy_lints/src/single_char_lifetime_names.rs +++ b/clippy_lints/src/single_char_lifetime_names.rs @@ -33,7 +33,7 @@ declare_clippy_lint! { /// source: &'src str, /// } /// ``` - #[clippy::version = "1.59.0"] + #[clippy::version = "1.60.0"] pub SINGLE_CHAR_LIFETIME_NAMES, restriction, "warns against single-character lifetime names" diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index b4ad5dcbe3e..975a0a06e38 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -23,15 +23,16 @@ declare_clippy_lint! { /// ```rust /// # use core::iter::repeat; /// # let len = 4; - /// - /// // Bad /// let mut vec1 = Vec::with_capacity(len); /// vec1.resize(len, 0); /// /// let mut vec2 = Vec::with_capacity(len); /// vec2.extend(repeat(0).take(len)); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let len = 4; /// let mut vec1 = vec![0; len]; /// let mut vec2 = vec![0; len]; /// ``` diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 7c196ccaa8c..71f3e6b6a6e 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -99,11 +99,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let bs = "a byte string".as_bytes(); + /// let bstr = "a byte string".as_bytes(); + /// ``` /// - /// // Good - /// let bs = b"a byte string"; + /// Use instead: + /// ```rust + /// let bstr = b"a byte string"; /// ``` #[clippy::version = "pre 1.29.0"] pub STRING_LIT_AS_BYTES, @@ -223,11 +224,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]).unwrap(); + /// std::str::from_utf8(&"Hello World!".as_bytes()[6..11]).unwrap(); /// ``` - /// could be written as + /// + /// Use instead: /// ```rust - /// let _ = &"Hello World!"[6..11]; + /// &"Hello World!"[6..11]; /// ``` #[clippy::version = "1.50.0"] pub STRING_FROM_UTF8_AS_BYTES, diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 1e5b646f5f0..ac63d182337 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -29,8 +29,7 @@ declare_clippy_lint! { /// pub fn foo(t: T) where T: Copy, T: Clone {} /// ``` /// - /// Could be written as: - /// + /// Use instead: /// ```rust /// pub fn foo(t: T) where T: Copy + Clone {} /// ``` diff --git a/clippy_lints/src/transmute/useless_transmute.rs b/clippy_lints/src/transmute/useless_transmute.rs index fc9227b76f0..8ea985a8984 100644 --- a/clippy_lints/src/transmute/useless_transmute.rs +++ b/clippy_lints/src/transmute/useless_transmute.rs @@ -61,12 +61,7 @@ pub(super) fn check<'tcx>( "transmute from an integer to a pointer", |diag| { if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) { - diag.span_suggestion( - e.span, - "try", - arg.as_ty(&to_ty.to_string()), - Applicability::Unspecified, - ); + diag.span_suggestion(e.span, "try", arg.as_ty(&to_ty.to_string()), Applicability::Unspecified); } }, ); diff --git a/clippy_lints/src/unicode.rs b/clippy_lints/src/unicode.rs index afd7be89a4e..cc64d17be05 100644 --- a/clippy_lints/src/unicode.rs +++ b/clippy_lints/src/unicode.rs @@ -41,7 +41,8 @@ declare_clippy_lint! { /// ```rust /// let x = String::from("€"); /// ``` - /// Could be written as: + /// + /// Use instead: /// ```rust /// let x = String::from("\u{20ac}"); /// ``` diff --git a/clippy_lints/src/unused_async.rs b/clippy_lints/src/unused_async.rs index 41333bb2add..c8ec4442ab1 100644 --- a/clippy_lints/src/unused_async.rs +++ b/clippy_lints/src/unused_async.rs @@ -17,13 +17,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// async fn get_random_number() -> i64 { /// 4 // Chosen by fair dice roll. Guaranteed to be random. /// } /// let number_future = get_random_number(); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// fn get_random_number_improved() -> i64 { /// 4 // Chosen by fair dice roll. Guaranteed to be random. /// } diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 4a3b5383c89..fe29bf29d0c 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -21,11 +21,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// // format!() returns a `String` /// let s: String = format!("hello").into(); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let s: String = format!("hello"); /// ``` #[clippy::version = "1.45.0"] diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index b5c5d35135f..38e5c5e5b73 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -9,6 +9,29 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{cmp, env, fmt, fs, io, iter}; +#[rustfmt::skip] +const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ + "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", + "DirectX", + "ECMAScript", + "GPLv2", "GPLv3", + "GitHub", "GitLab", + "IPv4", "IPv6", + "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", + "NaN", "NaNs", + "OAuth", "GraphQL", + "OCaml", + "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", + "WebGL", + "TensorFlow", + "TrueType", + "iOS", "macOS", "FreeBSD", + "TeX", "LaTeX", "BibTeX", "BibLaTeX", + "MinGW", + "CamelCase", +]; +const DEFAULT_BLACKLISTED_NAMES: &[&str] = &["foo", "baz", "quux"]; + /// Holds information used by `MISSING_ENFORCED_IMPORT_RENAMES` lint. #[derive(Clone, Debug, Deserialize)] pub struct Rename { @@ -178,8 +201,10 @@ define_Conf! { (msrv: Option = None), /// Lint: BLACKLISTED_NAME. /// - /// The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses - (blacklisted_names: Vec = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()), + /// The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses. The value + /// `".."` can be used as part of the list to indicate, that the configured values should be appended to the + /// default configuration of Clippy. By default any configuraction will replace the default value. + (blacklisted_names: Vec = super::DEFAULT_BLACKLISTED_NAMES.iter().map(ToString::to_string).collect()), /// Lint: COGNITIVE_COMPLEXITY. /// /// The maximum cognitive complexity a function can have @@ -191,27 +216,14 @@ define_Conf! { (cyclomatic_complexity_threshold: Option = None), /// Lint: DOC_MARKDOWN. /// - /// The list of words this lint should not consider as identifiers needing ticks - (doc_valid_idents: Vec = [ - "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", - "DirectX", - "ECMAScript", - "GPLv2", "GPLv3", - "GitHub", "GitLab", - "IPv4", "IPv6", - "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", - "NaN", "NaNs", - "OAuth", "GraphQL", - "OCaml", - "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", - "WebGL", - "TensorFlow", - "TrueType", - "iOS", "macOS", "FreeBSD", - "TeX", "LaTeX", "BibTeX", "BibLaTeX", - "MinGW", - "CamelCase", - ].iter().map(ToString::to_string).collect()), + /// The list of words this lint should not consider as identifiers needing ticks. The value + /// `".."` can be used as part of the list to indicate, that the configured values should be appended to the + /// default configuration of Clippy. By default any configuraction will replace the default value. For example: + /// * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. + /// * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. + /// + /// Default list: + (doc_valid_idents: Vec = super::DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect()), /// Lint: TOO_MANY_ARGUMENTS. /// /// The maximum number of argument a function or method can have @@ -401,7 +413,21 @@ pub fn read(path: &Path) -> TryConf { Err(e) => return TryConf::from_error(e), Ok(content) => content, }; - toml::from_str(&content).unwrap_or_else(TryConf::from_error) + match toml::from_str::(&content) { + Ok(mut conf) => { + extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS); + extend_vec_if_indicator_present(&mut conf.conf.blacklisted_names, DEFAULT_BLACKLISTED_NAMES); + + conf + }, + Err(e) => TryConf::from_error(e), + } +} + +fn extend_vec_if_indicator_present(vec: &mut Vec, default: &[&str]) { + if vec.contains(&"..".to_string()) { + vec.extend(default.iter().map(ToString::to_string)); + } } const SEPARATOR_WIDTH: usize = 4; diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index 60f98876994..b885e5132f1 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -89,12 +89,11 @@ declare_clippy_lint! { /// warning/error messages. /// /// ### Example - /// Bad: /// ```rust,ignore /// cx.span_lint(LINT_NAME, "message"); /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// utils::span_lint(cx, LINT_NAME, "message"); /// ``` @@ -112,12 +111,11 @@ declare_clippy_lint! { /// `cx.outer_expn_data()` is faster and more concise. /// /// ### Example - /// Bad: /// ```rust,ignore /// expr.span.ctxt().outer().expn_data() /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// expr.span.ctxt().outer_expn_data() /// ``` @@ -135,7 +133,6 @@ declare_clippy_lint! { /// ICE in large quantities can damage your teeth /// /// ### Example - /// Bad: /// ```rust,ignore /// 🍦🍦🍦🍦🍦 /// ``` @@ -153,12 +150,11 @@ declare_clippy_lint! { /// Indicates that the lint is not finished. /// /// ### Example - /// Bad: /// ```rust,ignore /// declare_lint! { pub COOL_LINT, nursery, "default lint description" } /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" } /// ``` @@ -183,7 +179,6 @@ declare_clippy_lint! { /// convenient, readable and less error prone. /// /// ### Example - /// Bad: /// ```rust,ignore /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { /// diag.span_suggestion( @@ -207,7 +202,7 @@ declare_clippy_lint! { /// }); /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// span_lint_and_sugg( /// cx, @@ -237,12 +232,11 @@ declare_clippy_lint! { /// `utils::is_type_diagnostic_item()` does not require hardcoded paths. /// /// ### Example - /// Bad: /// ```rust,ignore /// utils::match_type(cx, ty, &paths::VEC) /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// utils::is_type_diagnostic_item(cx, ty, sym::Vec) /// ``` @@ -273,12 +267,11 @@ declare_clippy_lint! { /// It's faster and easier to use the symbol constant. /// /// ### Example - /// Bad: /// ```rust,ignore /// let _ = sym!(f32); /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// let _ = sym::f32; /// ``` @@ -295,12 +288,11 @@ declare_clippy_lint! { /// It's faster use symbols directly instead of strings. /// /// ### Example - /// Bad: /// ```rust,ignore /// symbol.as_str() == "clippy"; /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// symbol == sym::clippy; /// ``` @@ -672,8 +664,8 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { if let ExprKind::Call(func, and_then_args) = expr.kind; if is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"]); if and_then_args.len() == 5; - if let ExprKind::Closure(_, _, body_id, _, _) = &and_then_args[4].kind; - let body = cx.tcx.hir().body(*body_id); + if let ExprKind::Closure { body, .. } = &and_then_args[4].kind; + let body = cx.tcx.hir().body(*body); let only_expr = peel_blocks_with_stmt(&body.value); if let ExprKind::MethodCall(ps, span_call_args, _) = &only_expr.kind; then { diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index cf2de6a42af..99e9e3275ab 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -31,6 +31,8 @@ use std::fmt::Write as _; use std::fs::{self, OpenOptions}; use std::io::prelude::*; use std::path::Path; +use std::path::PathBuf; +use std::process::Command; /// This is the output file of the lint collector. const OUTPUT_FILE: &str = "../util/gh-pages/lints.json"; @@ -180,6 +182,7 @@ pub struct MetadataCollector { lints: BinaryHeap, applicability_info: FxHashMap, config: Vec, + clippy_project_root: PathBuf, } impl MetadataCollector { @@ -188,6 +191,7 @@ impl MetadataCollector { lints: BinaryHeap::::default(), applicability_info: FxHashMap::::default(), config: collect_configs(), + clippy_project_root: clippy_dev::clippy_project_root(), } } @@ -215,11 +219,13 @@ impl Drop for MetadataCollector { // Mapping the final data let mut lints = std::mem::take(&mut self.lints).into_sorted_vec(); - collect_renames(&mut lints); for x in &mut lints { x.applicability = Some(applicability_info.remove(&x.id).unwrap_or_default()); + replace_produces(&x.id, &mut x.docs, &self.clippy_project_root); } + collect_renames(&mut lints); + // Outputting if Path::new(OUTPUT_FILE).exists() { fs::remove_file(OUTPUT_FILE).unwrap(); @@ -263,14 +269,193 @@ impl LintMetadata { } } +fn replace_produces(lint_name: &str, docs: &mut String, clippy_project_root: &Path) { + let mut doc_lines = docs.lines().map(ToString::to_string).collect::>(); + let mut lines = doc_lines.iter_mut(); + + 'outer: loop { + // Find the start of the example + + // ```rust + loop { + match lines.next() { + Some(line) if line.trim_start().starts_with("```rust") => { + if line.contains("ignore") || line.contains("no_run") { + // A {{produces}} marker may have been put on a ignored code block by mistake, + // just seek to the end of the code block and continue checking. + if lines.any(|line| line.trim_start().starts_with("```")) { + continue; + } + + panic!("lint `{}` has an unterminated code block", lint_name) + } + + break; + }, + Some(line) if line.trim_start() == "{{produces}}" => { + panic!( + "lint `{}` has marker {{{{produces}}}} with an ignored or missing code block", + lint_name + ) + }, + Some(line) => { + let line = line.trim(); + // These are the two most common markers of the corrections section + if line.eq_ignore_ascii_case("Use instead:") || line.eq_ignore_ascii_case("Could be written as:") { + break 'outer; + } + }, + None => break 'outer, + } + } + + // Collect the example + let mut example = Vec::new(); + loop { + match lines.next() { + Some(line) if line.trim_start() == "```" => break, + Some(line) => example.push(line), + None => panic!("lint `{}` has an unterminated code block", lint_name), + } + } + + // Find the {{produces}} and attempt to generate the output + loop { + match lines.next() { + Some(line) if line.is_empty() => {}, + Some(line) if line.trim() == "{{produces}}" => { + let output = get_lint_output(lint_name, &example, clippy_project_root); + line.replace_range( + .., + &format!( + "
\ + Produces\n\ + \n\ + ```text\n\ + {}\n\ + ```\n\ +
", + output + ), + ); + + break; + }, + // No {{produces}}, we can move on to the next example + Some(_) => break, + None => break 'outer, + } + } + } + + *docs = cleanup_docs(&doc_lines); +} + +fn get_lint_output(lint_name: &str, example: &[&mut String], clippy_project_root: &Path) -> String { + let dir = tempfile::tempdir().unwrap_or_else(|e| panic!("failed to create temp dir: {e}")); + let file = dir.path().join("lint_example.rs"); + + let mut source = String::new(); + let unhidden = example + .iter() + .map(|line| line.trim_start().strip_prefix("# ").unwrap_or(line)); + + // Get any attributes + let mut lines = unhidden.peekable(); + while let Some(line) = lines.peek() { + if line.starts_with("#!") { + source.push_str(line); + source.push('\n'); + lines.next(); + } else { + break; + } + } + + let needs_main = !example.iter().any(|line| line.contains("fn main")); + if needs_main { + source.push_str("fn main() {\n"); + } + + for line in lines { + source.push_str(line); + source.push('\n'); + } + + if needs_main { + source.push_str("}\n"); + } + + if let Err(e) = fs::write(&file, &source) { + panic!("failed to write to `{}`: {e}", file.as_path().to_string_lossy()); + } + + let prefixed_name = format!("{}{lint_name}", CLIPPY_LINT_GROUP_PREFIX); + + let mut cmd = Command::new("cargo"); + + cmd.current_dir(clippy_project_root) + .env("CARGO_INCREMENTAL", "0") + .env("CLIPPY_ARGS", "") + .env("CLIPPY_DISABLE_DOCS_LINKS", "1") + // We need to disable this to enable all lints + .env("ENABLE_METADATA_COLLECTION", "0") + .args(["run", "--bin", "clippy-driver"]) + .args(["--target-dir", "./clippy_lints/target"]) + .args(["--", "--error-format=json"]) + .args(["--edition", "2021"]) + .arg("-Cdebuginfo=0") + .args(["-A", "clippy::all"]) + .args(["-W", &prefixed_name]) + .args(["-L", "./target/debug"]) + .args(["-Z", "no-codegen"]); + + let output = cmd + .arg(file.as_path()) + .output() + .unwrap_or_else(|e| panic!("failed to run `{:?}`: {e}", cmd)); + + let tmp_file_path = file.to_string_lossy(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + let msgs = stderr + .lines() + .filter(|line| line.starts_with('{')) + .map(|line| serde_json::from_str(line).unwrap()) + .collect::>(); + + let mut rendered = String::new(); + let iter = msgs + .iter() + .filter(|msg| matches!(&msg["code"]["code"], serde_json::Value::String(s) if s == &prefixed_name)); + + for message in iter { + let rendered_part = message["rendered"].as_str().expect("rendered field should exist"); + rendered.push_str(rendered_part); + } + + if rendered.is_empty() { + let rendered: Vec<&str> = msgs.iter().filter_map(|msg| msg["rendered"].as_str()).collect(); + let non_json: Vec<&str> = stderr.lines().filter(|line| !line.starts_with('{')).collect(); + panic!( + "did not find lint `{}` in output of example, got:\n{}\n{}", + lint_name, + non_json.join("\n"), + rendered.join("\n") + ); + } + + // The reader doesn't need to see `/tmp/.tmpfiy2Qd/lint_example.rs` :) + rendered.trim_end().replace(&*tmp_file_path, "lint_example.rs") +} + #[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)] struct SerializableSpan { path: String, line: usize, } -impl std::fmt::Display for SerializableSpan { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for SerializableSpan { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}", self.path.rsplit('/').next().unwrap_or_default(), self.line) } } @@ -435,10 +620,10 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { if !BLACK_LISTED_LINTS.contains(&lint_name.as_str()); // metadata extraction if let Some((group, level)) = get_lint_group_and_level_or_lint(cx, &lint_name, item); - if let Some(mut docs) = extract_attr_docs_or_lint(cx, item); + if let Some(mut raw_docs) = extract_attr_docs_or_lint(cx, item); then { if let Some(configuration_section) = self.get_lint_configs(&lint_name) { - docs.push_str(&configuration_section); + raw_docs.push_str(&configuration_section); } let version = get_lint_version(cx, item); @@ -448,7 +633,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { group, level, version, - docs, + raw_docs, )); } } @@ -459,7 +644,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase(); if !BLACK_LISTED_LINTS.contains(&lint_name.as_str()); // Metadata the little we can get from a deprecated lint - if let Some(docs) = extract_attr_docs_or_lint(cx, item); + if let Some(raw_docs) = extract_attr_docs_or_lint(cx, item); then { let version = get_lint_version(cx, item); @@ -469,7 +654,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { DEPRECATED_LINT_GROUP_STR.to_string(), DEPRECATED_LINT_LEVEL, version, - docs, + raw_docs, )); } } @@ -535,22 +720,28 @@ fn extract_attr_docs_or_lint(cx: &LateContext<'_>, item: &Item<'_>) -> Option, item: &Item<'_>) -> Option { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let mut lines = attrs.iter().filter_map(ast::Attribute::doc_str); + + if let Some(line) = lines.next() { + let raw_docs = lines.fold(String::from(line.as_str()) + "\n", |s, line| s + line.as_str() + "\n"); + return Some(raw_docs); + } + + None +} + /// This function may modify the doc comment to ensure that the string can be displayed using a /// markdown viewer in Clippy's lint list. The following modifications could be applied: /// * Removal of leading space after a new line. (Important to display tables) /// * Ensures that code blocks only contain language information -fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option { - let attrs = cx.tcx.hir().attrs(item.hir_id()); - let mut lines = attrs.iter().filter_map(ast::Attribute::doc_str); - let mut docs = String::from(lines.next()?.as_str()); +fn cleanup_docs(docs_collection: &Vec) -> String { let mut in_code_block = false; let mut is_code_block_rust = false; - for line in lines { - let line = line.as_str(); + let mut docs = String::new(); + for line in docs_collection { // Rustdoc hides code lines starting with `# ` and this removes them from Clippy's lint list :) if is_code_block_rust && line.trim_start().starts_with("# ") { continue; @@ -583,7 +774,8 @@ fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option { docs.push_str(line); } } - Some(docs) + + docs } fn get_lint_version(cx: &LateContext<'_>, item: &Item<'_>) -> String { @@ -762,9 +954,9 @@ fn resolve_applicability<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hi } fn check_is_multi_part<'hir>(cx: &LateContext<'hir>, closure_expr: &'hir hir::Expr<'hir>) -> bool { - if let ExprKind::Closure(_, _, body_id, _, _) = closure_expr.kind { + if let ExprKind::Closure { body, .. } = closure_expr.kind { let mut scanner = IsMultiSpanScanner::new(cx); - intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body_id)); + intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body)); return scanner.is_multi_part(); } else if let Some(local) = get_parent_local(cx, closure_expr) { if let Some(local_init) = local.init { diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/vec.rs index ba1ff65479d..297a80e5767 100644 --- a/clippy_lints/src/vec.rs +++ b/clippy_lints/src/vec.rs @@ -28,12 +28,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// # fn foo(my_vec: &[u8]) {} + /// fn foo(_x: &[u8]) {} /// - /// // Bad /// foo(&vec![1, 2]); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # fn foo(_x: &[u8]) {} /// foo(&[1, 2]); /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index 2f74eaf3cf5..5418eca382d 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -26,13 +26,18 @@ declare_clippy_lint! { /// still around. /// /// ### Example - /// ```rust,ignore - /// // Bad + /// ```rust /// use std::cmp::Ordering::*; - /// foo(Less); /// - /// // Good + /// # fn foo(_: std::cmp::Ordering) {} + /// foo(Less); + /// ``` + /// + /// Use instead: + /// ```rust /// use std::cmp::Ordering; + /// + /// # fn foo(_: Ordering) {} /// foo(Ordering::Less) /// ``` #[clippy::version = "pre 1.29.0"] @@ -76,14 +81,13 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// use crate1::*; /// /// foo(); /// ``` /// + /// Use instead: /// ```rust,ignore - /// // Good /// use crate1::foo; /// /// foo(); diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index d2493c055a5..67b2bc8c3f3 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -25,10 +25,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// println!(""); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// println!(); /// ``` #[clippy::version = "pre 1.29.0"] @@ -177,10 +178,13 @@ declare_clippy_lint! { /// ```rust /// # use std::fmt::Write; /// # let mut buf = String::new(); - /// // Bad /// writeln!(buf, ""); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); /// writeln!(buf); /// ``` #[clippy::version = "pre 1.29.0"] @@ -204,10 +208,14 @@ declare_clippy_lint! { /// # use std::fmt::Write; /// # let mut buf = String::new(); /// # let name = "World"; - /// // Bad /// write!(buf, "Hello {}!\n", name); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// # let name = "World"; /// writeln!(buf, "Hello {}!", name); /// ``` #[clippy::version = "pre 1.29.0"] @@ -233,10 +241,13 @@ declare_clippy_lint! { /// ```rust /// # use std::fmt::Write; /// # let mut buf = String::new(); - /// // Bad /// writeln!(buf, "{}", "foo"); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); /// writeln!(buf, "foo"); /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/zero_div_zero.rs b/clippy_lints/src/zero_div_zero.rs index 641681185a2..50d3c079fe6 100644 --- a/clippy_lints/src/zero_div_zero.rs +++ b/clippy_lints/src/zero_div_zero.rs @@ -14,10 +14,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let nan = 0.0f32 / 0.0; + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let nan = f32::NAN; /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 5d0ce6cc620..6d4a48b53de 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -622,7 +622,7 @@ pub fn miri_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::ConstantKind<'tcx>) - ty::Float(FloatTy::F32) => match len.to_valtree().try_to_machine_usize(tcx) { Some(len) => alloc .inner() - .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize)) + .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * usize::try_from(len).unwrap())) .to_owned() .chunks(4) .map(|chunk| { @@ -637,7 +637,7 @@ pub fn miri_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::ConstantKind<'tcx>) - ty::Float(FloatTy::F64) => match len.to_valtree().try_to_machine_usize(tcx) { Some(len) => alloc .inner() - .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize)) + .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * usize::try_from(len).unwrap())) .to_owned() .chunks(8) .map(|chunk| { diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 12931c56df6..af62c4afd5a 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -91,7 +91,7 @@ pub struct HirEqInterExpr<'a, 'b, 'tcx> { // When binding are declared, the binding ID in the left expression is mapped to the one on the // right. For example, when comparing `{ let x = 1; x + 2 }` and `{ let y = 1; y + 2 }`, // these blocks are considered equal since `x` is mapped to `y`. - locals: HirIdMap, + pub locals: HirIdMap, } impl HirEqInterExpr<'_, '_, '_> { @@ -998,3 +998,15 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } } } + +pub fn hash_stmt(cx: &LateContext<'_>, s: &Stmt<'_>) -> u64 { + let mut h = SpanlessHash::new(cx); + h.hash_stmt(s); + h.finish() +} + +pub fn hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 { + let mut h = SpanlessHash::new(cx); + h.hash_expr(e); + h.finish() +} diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 0cf23ca626c..73c1bdd0e3f 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -58,7 +58,9 @@ pub mod usage; pub mod visitors; pub use self::attrs::*; -pub use self::hir_utils::{both, count_eq, eq_expr_value, over, SpanlessEq, SpanlessHash}; +pub use self::hir_utils::{ + both, count_eq, eq_expr_value, hash_expr, hash_stmt, over, HirEqInterExpr, SpanlessEq, SpanlessHash, +}; use std::collections::hash_map::Entry; use std::hash::BuildHasherDefault; diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index a10515d2fec..227e97d37ec 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -78,9 +78,9 @@ pub fn get_associated_type<'tcx>( cx.tcx .associated_items(trait_id) .find_by_name_and_kind(cx.tcx, Ident::from_str(name), ty::AssocKind::Type, trait_id) - .map(|assoc| { + .and_then(|assoc| { let proj = cx.tcx.mk_projection(assoc.def_id, cx.tcx.mk_substs_trait(ty, &[])); - cx.tcx.normalize_erasing_regions(cx.param_env, proj) + cx.tcx.try_normalize_erasing_regions(cx.param_env, proj).ok() }) } diff --git a/lintcheck/src/config.rs b/lintcheck/src/config.rs index a6f93d2a1c0..1742cf677c0 100644 --- a/lintcheck/src/config.rs +++ b/lintcheck/src/config.rs @@ -1,50 +1,40 @@ -use clap::{Arg, ArgMatches, Command}; +use clap::{Arg, ArgAction, ArgMatches, Command}; use std::env; use std::path::PathBuf; fn get_clap_config() -> ArgMatches { Command::new("lintcheck") .about("run clippy on a set of crates and check output") - .arg( + .args([ Arg::new("only") - .takes_value(true) + .action(ArgAction::Set) .value_name("CRATE") .long("only") .help("Only process a single crate of the list"), - ) - .arg( Arg::new("crates-toml") - .takes_value(true) + .action(ArgAction::Set) .value_name("CRATES-SOURCES-TOML-PATH") .long("crates-toml") .help("Set the path for a crates.toml where lintcheck should read the sources from"), - ) - .arg( Arg::new("threads") - .takes_value(true) + .action(ArgAction::Set) .value_name("N") + .value_parser(clap::value_parser!(usize)) .short('j') .long("jobs") .help("Number of threads to use, 0 automatic choice"), - ) - .arg( Arg::new("fix") - .long("--fix") + .long("fix") .help("Runs cargo clippy --fix and checks if all suggestions apply"), - ) - .arg( Arg::new("filter") - .long("--filter") - .takes_value(true) - .multiple_occurrences(true) + .long("filter") + .action(ArgAction::Append) .value_name("clippy_lint_name") .help("Apply a filter to only collect specified lints, this also overrides `allow` attributes"), - ) - .arg( Arg::new("markdown") - .long("--markdown") + .long("markdown") .help("Change the reports table to use markdown links"), - ) + ]) .get_matches() } @@ -75,13 +65,13 @@ impl LintcheckConfig { // if not, use the default "lintcheck/lintcheck_crates.toml" let sources_toml = env::var("LINTCHECK_TOML").unwrap_or_else(|_| { clap_config - .value_of("crates-toml") - .clone() + .get_one::("crates-toml") + .map(|s| &**s) .unwrap_or("lintcheck/lintcheck_crates.toml") - .to_string() + .into() }); - let markdown = clap_config.is_present("markdown"); + let markdown = clap_config.contains_id("markdown"); let sources_toml_path = PathBuf::from(sources_toml); // for the path where we save the lint results, get the filename without extension (so for @@ -96,25 +86,19 @@ impl LintcheckConfig { // look at the --threads arg, if 0 is passed, ask rayon rayon how many threads it would spawn and // use half of that for the physical core count // by default use a single thread - let max_jobs = match clap_config.value_of("threads") { - Some(threads) => { - let threads: usize = threads - .parse() - .unwrap_or_else(|_| panic!("Failed to parse '{}' to a digit", threads)); - if threads == 0 { - // automatic choice - // Rayon seems to return thread count so half that for core count - (rayon::current_num_threads() / 2) as usize - } else { - threads - } + let max_jobs = match clap_config.get_one::("threads") { + Some(&0) => { + // automatic choice + // Rayon seems to return thread count so half that for core count + (rayon::current_num_threads() / 2) as usize }, + Some(&threads) => threads, // no -j passed, use a single thread None => 1, }; let lint_filter: Vec = clap_config - .values_of("filter") + .get_many::("filter") .map(|iter| { iter.map(|lint_name| { let mut filter = lint_name.replace('_', "-"); @@ -131,8 +115,8 @@ impl LintcheckConfig { max_jobs, sources_toml_path, lintcheck_results_path, - only: clap_config.value_of("only").map(String::from), - fix: clap_config.is_present("fix"), + only: clap_config.get_one::("only").map(String::from), + fix: clap_config.contains_id("fix"), lint_filter, markdown, } diff --git a/rust-toolchain b/rust-toolchain index 2386a751f04..6ad56aacf8c 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-06-04" +channel = "nightly-2022-06-16" components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/tests/dogfood.rs b/tests/dogfood.rs index 9da80518ce9..fffc5360342 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -21,7 +21,7 @@ fn dogfood_clippy() { // "" is the root package for package in &["", "clippy_dev", "clippy_lints", "clippy_utils", "rustc_tools_util"] { - run_clippy_for_package(package, &[]); + run_clippy_for_package(package, &["-D", "clippy::all", "-D", "clippy::pedantic"]); } } @@ -77,8 +77,6 @@ fn run_clippy_for_package(project: &str, args: &[&str]) { .arg("--all-features") .arg("--") .args(args) - .args(&["-D", "clippy::all"]) - .args(&["-D", "clippy::pedantic"]) .arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir if cfg!(feature = "internal") { diff --git a/tests/ui-toml/bad_toml/conf_bad_toml.rs b/tests/ui-toml/bad_toml/conf_bad_toml.rs index 3b9458fc284..f328e4d9d04 100644 --- a/tests/ui-toml/bad_toml/conf_bad_toml.rs +++ b/tests/ui-toml/bad_toml/conf_bad_toml.rs @@ -1,3 +1 @@ -// error-pattern: error reading Clippy's configuration file - fn main() {} diff --git a/tests/ui-toml/bad_toml_type/conf_bad_type.rs b/tests/ui-toml/bad_toml_type/conf_bad_type.rs index 8a0062423ad..f328e4d9d04 100644 --- a/tests/ui-toml/bad_toml_type/conf_bad_type.rs +++ b/tests/ui-toml/bad_toml_type/conf_bad_type.rs @@ -1,4 +1 @@ -// error-pattern: error reading Clippy's configuration file: `blacklisted-names` is expected to be a -// `Vec < String >` but is a `integer` - fn main() {} diff --git a/tests/ui-toml/blacklisted_names_append/blacklisted_names.rs b/tests/ui-toml/blacklisted_names_append/blacklisted_names.rs new file mode 100644 index 00000000000..fb2395cf90b --- /dev/null +++ b/tests/ui-toml/blacklisted_names_append/blacklisted_names.rs @@ -0,0 +1,10 @@ +#[warn(clippy::blacklisted_name)] + +fn main() { + // `foo` is part of the default configuration + let foo = "bar"; + // `ducks` was unrightfully blacklisted + let ducks = ["quack", "quack"]; + // `fox` is okay + let fox = ["what", "does", "the", "fox", "say", "?"]; +} diff --git a/tests/ui-toml/blacklisted_names_append/blacklisted_names.stderr b/tests/ui-toml/blacklisted_names_append/blacklisted_names.stderr new file mode 100644 index 00000000000..9169bb0e866 --- /dev/null +++ b/tests/ui-toml/blacklisted_names_append/blacklisted_names.stderr @@ -0,0 +1,16 @@ +error: use of a blacklisted/placeholder name `foo` + --> $DIR/blacklisted_names.rs:5:9 + | +LL | let foo = "bar"; + | ^^^ + | + = note: `-D clippy::blacklisted-name` implied by `-D warnings` + +error: use of a blacklisted/placeholder name `ducks` + --> $DIR/blacklisted_names.rs:7:9 + | +LL | let ducks = ["quack", "quack"]; + | ^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui-toml/blacklisted_names_append/clippy.toml b/tests/ui-toml/blacklisted_names_append/clippy.toml new file mode 100644 index 00000000000..0e052ef50f0 --- /dev/null +++ b/tests/ui-toml/blacklisted_names_append/clippy.toml @@ -0,0 +1 @@ +blacklisted-names = ["ducks", ".."] diff --git a/tests/ui-toml/blacklisted_names_replace/blacklisted_names.rs b/tests/ui-toml/blacklisted_names_replace/blacklisted_names.rs new file mode 100644 index 00000000000..fb2395cf90b --- /dev/null +++ b/tests/ui-toml/blacklisted_names_replace/blacklisted_names.rs @@ -0,0 +1,10 @@ +#[warn(clippy::blacklisted_name)] + +fn main() { + // `foo` is part of the default configuration + let foo = "bar"; + // `ducks` was unrightfully blacklisted + let ducks = ["quack", "quack"]; + // `fox` is okay + let fox = ["what", "does", "the", "fox", "say", "?"]; +} diff --git a/tests/ui-toml/blacklisted_names_replace/blacklisted_names.stderr b/tests/ui-toml/blacklisted_names_replace/blacklisted_names.stderr new file mode 100644 index 00000000000..ec6f7f084f2 --- /dev/null +++ b/tests/ui-toml/blacklisted_names_replace/blacklisted_names.stderr @@ -0,0 +1,10 @@ +error: use of a blacklisted/placeholder name `ducks` + --> $DIR/blacklisted_names.rs:7:9 + | +LL | let ducks = ["quack", "quack"]; + | ^^^^^ + | + = note: `-D clippy::blacklisted-name` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/tests/ui-toml/blacklisted_names_replace/clippy.toml b/tests/ui-toml/blacklisted_names_replace/clippy.toml new file mode 100644 index 00000000000..4582f1c0667 --- /dev/null +++ b/tests/ui-toml/blacklisted_names_replace/clippy.toml @@ -0,0 +1 @@ +blacklisted-names = ["ducks"] diff --git a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs index 2577c1eef92..f328e4d9d04 100644 --- a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs +++ b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs @@ -1,4 +1 @@ -// error-pattern: error reading Clippy's configuration file: found deprecated field -// `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead. - fn main() {} diff --git a/tests/ui-toml/doc_valid_idents_append/clippy.toml b/tests/ui-toml/doc_valid_idents_append/clippy.toml new file mode 100644 index 00000000000..daf3276854b --- /dev/null +++ b/tests/ui-toml/doc_valid_idents_append/clippy.toml @@ -0,0 +1 @@ +doc-valid-idents = ["ClipPy", ".."] diff --git a/tests/ui-toml/doc_valid_idents_append/doc_markdown.rs b/tests/ui-toml/doc_valid_idents_append/doc_markdown.rs new file mode 100644 index 00000000000..327a592e9ca --- /dev/null +++ b/tests/ui-toml/doc_valid_idents_append/doc_markdown.rs @@ -0,0 +1,12 @@ +#![warn(clippy::doc_markdown)] + +/// This is a special interface for ClipPy which doesn't require backticks +fn allowed_name() {} + +/// OAuth and LaTeX are inside Clippy's default list. +fn default_name() {} + +/// TestItemThingyOfCoolness might sound cool but is not on the list and should be linted. +fn unknown_name() {} + +fn main() {} diff --git a/tests/ui-toml/doc_valid_idents_append/doc_markdown.stderr b/tests/ui-toml/doc_valid_idents_append/doc_markdown.stderr new file mode 100644 index 00000000000..0f767c9b855 --- /dev/null +++ b/tests/ui-toml/doc_valid_idents_append/doc_markdown.stderr @@ -0,0 +1,14 @@ +error: item in documentation is missing backticks + --> $DIR/doc_markdown.rs:9:5 + | +LL | /// TestItemThingyOfCoolness might sound cool but is not on the list and should be linted. + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::doc-markdown` implied by `-D warnings` +help: try + | +LL | /// `TestItemThingyOfCoolness` might sound cool but is not on the list and should be linted. + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to previous error + diff --git a/tests/ui-toml/doc_valid_idents_replace/clippy.toml b/tests/ui-toml/doc_valid_idents_replace/clippy.toml new file mode 100644 index 00000000000..70bc477b08c --- /dev/null +++ b/tests/ui-toml/doc_valid_idents_replace/clippy.toml @@ -0,0 +1 @@ +doc-valid-idents = ["ClipPy"] diff --git a/tests/ui-toml/doc_valid_idents_replace/doc_markdown.rs b/tests/ui-toml/doc_valid_idents_replace/doc_markdown.rs new file mode 100644 index 00000000000..327a592e9ca --- /dev/null +++ b/tests/ui-toml/doc_valid_idents_replace/doc_markdown.rs @@ -0,0 +1,12 @@ +#![warn(clippy::doc_markdown)] + +/// This is a special interface for ClipPy which doesn't require backticks +fn allowed_name() {} + +/// OAuth and LaTeX are inside Clippy's default list. +fn default_name() {} + +/// TestItemThingyOfCoolness might sound cool but is not on the list and should be linted. +fn unknown_name() {} + +fn main() {} diff --git a/tests/ui-toml/doc_valid_idents_replace/doc_markdown.stderr b/tests/ui-toml/doc_valid_idents_replace/doc_markdown.stderr new file mode 100644 index 00000000000..e0613eb863b --- /dev/null +++ b/tests/ui-toml/doc_valid_idents_replace/doc_markdown.stderr @@ -0,0 +1,36 @@ +error: item in documentation is missing backticks + --> $DIR/doc_markdown.rs:6:5 + | +LL | /// OAuth and LaTeX are inside Clippy's default list. + | ^^^^^ + | + = note: `-D clippy::doc-markdown` implied by `-D warnings` +help: try + | +LL | /// `OAuth` and LaTeX are inside Clippy's default list. + | ~~~~~~~ + +error: item in documentation is missing backticks + --> $DIR/doc_markdown.rs:6:15 + | +LL | /// OAuth and LaTeX are inside Clippy's default list. + | ^^^^^ + | +help: try + | +LL | /// OAuth and `LaTeX` are inside Clippy's default list. + | ~~~~~~~ + +error: item in documentation is missing backticks + --> $DIR/doc_markdown.rs:9:5 + | +LL | /// TestItemThingyOfCoolness might sound cool but is not on the list and should be linted. + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | /// `TestItemThingyOfCoolness` might sound cool but is not on the list and should be linted. + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 3 previous errors + diff --git a/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs b/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs index 270b9c5c43c..f328e4d9d04 100644 --- a/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs +++ b/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs @@ -1,3 +1 @@ -// error-pattern: should give absolutely no error - fn main() {} diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs b/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs index a47569f62a3..f328e4d9d04 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs @@ -1,3 +1 @@ -// error-pattern: error reading Clippy's configuration file: unknown key `foobar` - fn main() {} diff --git a/tests/ui/almost_complete_letter_range.fixed b/tests/ui/almost_complete_letter_range.fixed index 39f8f0c2949..e69b40f35f4 100644 --- a/tests/ui/almost_complete_letter_range.fixed +++ b/tests/ui/almost_complete_letter_range.fixed @@ -6,6 +6,7 @@ #![feature(stmt_expr_attributes)] #![warn(clippy::almost_complete_letter_range)] #![allow(ellipsis_inclusive_range_patterns)] +#![allow(clippy::needless_parens_on_range_literals)] macro_rules! a { () => { diff --git a/tests/ui/almost_complete_letter_range.rs b/tests/ui/almost_complete_letter_range.rs index 3dc02199257..f2240981d45 100644 --- a/tests/ui/almost_complete_letter_range.rs +++ b/tests/ui/almost_complete_letter_range.rs @@ -6,6 +6,7 @@ #![feature(stmt_expr_attributes)] #![warn(clippy::almost_complete_letter_range)] #![allow(ellipsis_inclusive_range_patterns)] +#![allow(clippy::needless_parens_on_range_literals)] macro_rules! a { () => { diff --git a/tests/ui/almost_complete_letter_range.stderr b/tests/ui/almost_complete_letter_range.stderr index 74980ec1a92..5b5dc40ee54 100644 --- a/tests/ui/almost_complete_letter_range.stderr +++ b/tests/ui/almost_complete_letter_range.stderr @@ -1,5 +1,5 @@ error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:19:17 + --> $DIR/almost_complete_letter_range.rs:20:17 | LL | let _ = ('a') ..'z'; | ^^^^^^--^^^ @@ -9,7 +9,7 @@ LL | let _ = ('a') ..'z'; = note: `-D clippy::almost-complete-letter-range` implied by `-D warnings` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:20:17 + --> $DIR/almost_complete_letter_range.rs:21:17 | LL | let _ = 'A' .. ('Z'); | ^^^^--^^^^^^ @@ -17,7 +17,7 @@ LL | let _ = 'A' .. ('Z'); | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:26:13 + --> $DIR/almost_complete_letter_range.rs:27:13 | LL | let _ = (b'a')..(b'z'); | ^^^^^^--^^^^^^ @@ -25,7 +25,7 @@ LL | let _ = (b'a')..(b'z'); | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:27:13 + --> $DIR/almost_complete_letter_range.rs:28:13 | LL | let _ = b'A'..b'Z'; | ^^^^--^^^^ @@ -33,7 +33,7 @@ LL | let _ = b'A'..b'Z'; | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:32:13 + --> $DIR/almost_complete_letter_range.rs:33:13 | LL | let _ = a!()..'z'; | ^^^^--^^^ @@ -41,7 +41,7 @@ LL | let _ = a!()..'z'; | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:35:9 + --> $DIR/almost_complete_letter_range.rs:36:9 | LL | b'a'..b'z' if true => 1, | ^^^^--^^^^ @@ -49,7 +49,7 @@ LL | b'a'..b'z' if true => 1, | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:36:9 + --> $DIR/almost_complete_letter_range.rs:37:9 | LL | b'A'..b'Z' if true => 2, | ^^^^--^^^^ @@ -57,7 +57,7 @@ LL | b'A'..b'Z' if true => 2, | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:43:9 + --> $DIR/almost_complete_letter_range.rs:44:9 | LL | 'a'..'z' if true => 1, | ^^^--^^^ @@ -65,7 +65,7 @@ LL | 'a'..'z' if true => 1, | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:44:9 + --> $DIR/almost_complete_letter_range.rs:45:9 | LL | 'A'..'Z' if true => 2, | ^^^--^^^ @@ -73,7 +73,7 @@ LL | 'A'..'Z' if true => 2, | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:54:9 + --> $DIR/almost_complete_letter_range.rs:55:9 | LL | 'a'..'z' => 1, | ^^^--^^^ @@ -81,7 +81,7 @@ LL | 'a'..'z' => 1, | help: use an inclusive range: `...` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:61:13 + --> $DIR/almost_complete_letter_range.rs:62:13 | LL | let _ = 'a'..'z'; | ^^^--^^^ @@ -89,7 +89,7 @@ LL | let _ = 'a'..'z'; | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:63:9 + --> $DIR/almost_complete_letter_range.rs:64:9 | LL | 'a'..'z' => 1, | ^^^--^^^ diff --git a/tests/ui/async_yields_async.fixed b/tests/ui/async_yields_async.fixed index e20b58269b9..3cf380d2b95 100644 --- a/tests/ui/async_yields_async.fixed +++ b/tests/ui/async_yields_async.fixed @@ -1,5 +1,5 @@ // run-rustfix - +#![feature(lint_reasons)] #![feature(async_closure)] #![warn(clippy::async_yields_async)] @@ -65,3 +65,14 @@ fn main() { let _n = async || custom_future_type_ctor(); let _o = async || f(); } + +#[rustfmt::skip] +#[allow(dead_code)] +fn check_expect_suppression() { + #[expect(clippy::async_yields_async)] + let _j = async || { + async { + 3 + } + }; +} diff --git a/tests/ui/async_yields_async.rs b/tests/ui/async_yields_async.rs index c1dfa398450..dd4131b60ab 100644 --- a/tests/ui/async_yields_async.rs +++ b/tests/ui/async_yields_async.rs @@ -1,5 +1,5 @@ // run-rustfix - +#![feature(lint_reasons)] #![feature(async_closure)] #![warn(clippy::async_yields_async)] @@ -65,3 +65,14 @@ fn main() { let _n = async || custom_future_type_ctor(); let _o = async || f(); } + +#[rustfmt::skip] +#[allow(dead_code)] +fn check_expect_suppression() { + #[expect(clippy::async_yields_async)] + let _j = async || { + async { + 3 + } + }; +} diff --git a/tests/ui/branches_sharing_code/false_positives.rs b/tests/ui/branches_sharing_code/false_positives.rs index 7f42df46341..06448200951 100644 --- a/tests/ui/branches_sharing_code/false_positives.rs +++ b/tests/ui/branches_sharing_code/false_positives.rs @@ -25,4 +25,17 @@ impl FooBar { fn baz(&mut self) {} } -fn main() {} +fn foo(x: u32, y: u32) -> u32 { + x / y +} + +fn main() { + let x = (1, 2); + let _ = if true { + let (x, y) = x; + foo(x, y) + } else { + let (y, x) = x; + foo(x, y) + }; +} diff --git a/tests/ui/branches_sharing_code/shared_at_bottom.stderr b/tests/ui/branches_sharing_code/shared_at_bottom.stderr index e3c1bbee994..5e1a68d216e 100644 --- a/tests/ui/branches_sharing_code/shared_at_bottom.stderr +++ b/tests/ui/branches_sharing_code/shared_at_bottom.stderr @@ -12,8 +12,8 @@ note: the lint level is defined here | LL | #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: The end suggestion probably needs some adjustments to use the expression result correctly -help: consider moving the end statements out like this + = note: the end suggestion probably needs some adjustments to use the expression result correctly +help: consider moving these statements after the if | LL ~ } LL + let result = false; @@ -28,7 +28,7 @@ LL | / println!("Same end of block"); LL | | } | |_____^ | -help: consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + println!("Same end of block"); @@ -44,7 +44,7 @@ LL | | ); LL | | } | |_____^ | -help: consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + println!( @@ -60,7 +60,7 @@ LL | / println!("Hello World"); LL | | } | |_________^ | -help: consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + println!("Hello World"); @@ -75,8 +75,8 @@ LL | | // I'm expecting a note about this LL | | } | |_____^ | - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the end statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements after the if | LL ~ } LL + let later_used_value = "A string value"; @@ -91,8 +91,8 @@ LL | | println!("This is the new simple_example: {}", simple_examples); LL | | } | |_____^ | - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the end statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements after the if | LL ~ } LL + let simple_examples = "I now identify as a &str :)"; @@ -106,8 +106,8 @@ LL | / x << 2 LL | | }; | |_____^ | - = note: The end suggestion probably needs some adjustments to use the expression result correctly -help: consider moving the end statements out like this + = note: the end suggestion probably needs some adjustments to use the expression result correctly +help: consider moving these statements after the if | LL ~ } LL ~ x << 2; @@ -120,8 +120,8 @@ LL | / x * 4 LL | | } | |_____^ | - = note: The end suggestion probably needs some adjustments to use the expression result correctly -help: consider moving the end statements out like this + = note: the end suggestion probably needs some adjustments to use the expression result correctly +help: consider moving these statements after the if | LL ~ } LL + x * 4 @@ -133,7 +133,7 @@ error: all if blocks contain the same code at the end LL | if x == 17 { b = 1; a = 0x99; } else { a = 0x99; } | ^^^^^^^^^^^ | -help: consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ if x == 17 { b = 1; a = 0x99; } else { } LL + a = 0x99; diff --git a/tests/ui/branches_sharing_code/shared_at_top.stderr b/tests/ui/branches_sharing_code/shared_at_top.stderr index 8d78fa5de7e..d890b12ecbb 100644 --- a/tests/ui/branches_sharing_code/shared_at_top.stderr +++ b/tests/ui/branches_sharing_code/shared_at_top.stderr @@ -10,7 +10,7 @@ note: the lint level is defined here | LL | #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: consider moving the start statements out like this +help: consider moving these statements before the if | LL ~ println!("Hello World!"); LL + if true { @@ -25,8 +25,8 @@ LL | | println!("The value y was set to: `{}`", y); LL | | let _z = y; | |___________________^ | - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the start statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements before the if | LL ~ let y = 9; LL + println!("The value y was set to: `{}`", y); @@ -41,7 +41,7 @@ LL | / let _ = if x == 7 { LL | | let y = 16; | |___________________^ | -help: consider moving the start statements out like this +help: consider moving these statements before the if | LL ~ let y = 16; LL + let _ = if x == 7 { @@ -55,8 +55,8 @@ LL | | let used_value_name = "Different type"; LL | | println!("Str: {}", used_value_name); | |_____________________________________________^ | - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the start statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements before the if | LL ~ let used_value_name = "Different type"; LL + println!("Str: {}", used_value_name); @@ -71,8 +71,8 @@ LL | | let can_be_overridden = "Move me"; LL | | println!("I'm also moveable"); | |______________________________________^ | - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the start statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements before the if | LL ~ let can_be_overridden = "Move me"; LL + println!("I'm also moveable"); @@ -87,7 +87,7 @@ LL | | println!("This should trigger the `SHARED_CODE_IN_IF_BLOCKS` lint LL | | println!("Because `IF_SAME_THEN_ELSE` is allowed here"); | |________________________________________________________________^ | -help: consider moving the start statements out like this +help: consider moving these statements before the if | LL ~ println!("This should trigger the `SHARED_CODE_IN_IF_BLOCKS` lint."); LL + println!("Because `IF_SAME_THEN_ELSE` is allowed here"); diff --git a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr index 1db2343d3fe..11843cc03d8 100644 --- a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr +++ b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr @@ -1,4 +1,4 @@ -error: all if blocks contain the same code at the start and the end. Here at the start +error: all if blocks contain the same code at both the start and the end --> $DIR/shared_at_top_and_bottom.rs:16:5 | LL | / if x == 7 { @@ -12,26 +12,26 @@ note: the lint level is defined here | LL | #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -note: and here at the end +note: this code is shared at the end --> $DIR/shared_at_top_and_bottom.rs:28:5 | LL | / let _u = 9; LL | | } | |_____^ -help: consider moving the start statements out like this +help: consider moving these statements before the if | LL ~ let t = 7; LL + let _overlap_start = t * 2; LL + let _overlap_end = 2 * t; LL + if x == 7 { | -help: and consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + let _u = 9; | -error: all if blocks contain the same code at the start and the end. Here at the start +error: all if blocks contain the same code at both the start and the end --> $DIR/shared_at_top_and_bottom.rs:32:5 | LL | / if x == 99 { @@ -40,29 +40,29 @@ LL | | let _overlap_start = r; LL | | let _overlap_middle = r * r; | |____________________________________^ | -note: and here at the end +note: this code is shared at the end --> $DIR/shared_at_top_and_bottom.rs:43:5 | LL | / let _overlap_end = r * r * r; LL | | let z = "end"; LL | | } | |_____^ - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the start statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements before the if | LL ~ let r = 7; LL + let _overlap_start = r; LL + let _overlap_middle = r * r; LL + if x == 99 { | -help: and consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + let _overlap_end = r * r * r; LL + let z = "end"; | -error: all if blocks contain the same code at the start and the end. Here at the start +error: all if blocks contain the same code at both the start and the end --> $DIR/shared_at_top_and_bottom.rs:61:5 | LL | / if (x > 7 && y < 13) || (x + y) % 2 == 1 { @@ -71,7 +71,7 @@ LL | | let b = 0xffff00ff; LL | | let e_id = gen_id(a, b); | |________________________________^ | -note: and here at the end +note: this code is shared at the end --> $DIR/shared_at_top_and_bottom.rs:81:5 | LL | / let pack = DataPack { @@ -82,15 +82,15 @@ LL | | }; LL | | process_data(pack); LL | | } | |_____^ - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the start statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements before the if | LL ~ let a = 0xcafe; LL + let b = 0xffff00ff; LL + let e_id = gen_id(a, b); LL + if (x > 7 && y < 13) || (x + y) % 2 == 1 { | -help: and consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + let pack = DataPack { @@ -100,51 +100,51 @@ LL + some_data: vec![0x12, 0x34, 0x56, 0x78, 0x90], LL + }; ... -error: all if blocks contain the same code at the start and the end. Here at the start +error: all if blocks contain the same code at both the start and the end --> $DIR/shared_at_top_and_bottom.rs:94:5 | LL | / let _ = if x == 7 { LL | | let _ = 19; | |___________________^ | -note: and here at the end +note: this code is shared at the end --> $DIR/shared_at_top_and_bottom.rs:103:5 | LL | / x << 2 LL | | }; | |_____^ - = note: The end suggestion probably needs some adjustments to use the expression result correctly -help: consider moving the start statements out like this + = note: the end suggestion probably needs some adjustments to use the expression result correctly +help: consider moving these statements before the if | LL ~ let _ = 19; LL + let _ = if x == 7 { | -help: and consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL ~ x << 2; | -error: all if blocks contain the same code at the start and the end. Here at the start +error: all if blocks contain the same code at both the start and the end --> $DIR/shared_at_top_and_bottom.rs:106:5 | LL | / if x == 9 { LL | | let _ = 17; | |___________________^ | -note: and here at the end +note: this code is shared at the end --> $DIR/shared_at_top_and_bottom.rs:115:5 | LL | / x * 4 LL | | } | |_____^ - = note: The end suggestion probably needs some adjustments to use the expression result correctly -help: consider moving the start statements out like this + = note: the end suggestion probably needs some adjustments to use the expression result correctly +help: consider moving these statements before the if | LL ~ let _ = 17; LL + if x == 9 { | -help: and consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + x * 4 diff --git a/tests/ui/checked_conversions.fixed b/tests/ui/checked_conversions.fixed index 0983d393b56..cb7100bc9ef 100644 --- a/tests/ui/checked_conversions.fixed +++ b/tests/ui/checked_conversions.fixed @@ -71,4 +71,9 @@ pub fn i8_to_u8(value: i8) { let _ = value >= 0; } +// Do not lint +pub const fn issue_8898(i: u32) -> bool { + i <= i32::MAX as u32 +} + fn main() {} diff --git a/tests/ui/checked_conversions.rs b/tests/ui/checked_conversions.rs index 7d26ace47fd..ed4e0692388 100644 --- a/tests/ui/checked_conversions.rs +++ b/tests/ui/checked_conversions.rs @@ -71,4 +71,9 @@ pub fn i8_to_u8(value: i8) { let _ = value >= 0; } +// Do not lint +pub const fn issue_8898(i: u32) -> bool { + i <= i32::MAX as u32 +} + fn main() {} diff --git a/tests/ui/default_numeric_fallback_i32.fixed b/tests/ui/default_numeric_fallback_i32.fixed index fa85d278c8f..55451cf2f7d 100644 --- a/tests/ui/default_numeric_fallback_i32.fixed +++ b/tests/ui/default_numeric_fallback_i32.fixed @@ -1,6 +1,7 @@ // run-rustfix // aux-build:macro_rules.rs +#![feature(lint_reasons)] #![warn(clippy::default_numeric_fallback)] #![allow( unused, @@ -173,4 +174,9 @@ mod in_macro { } } +fn check_expect_suppression() { + #[expect(clippy::default_numeric_fallback)] + let x = 21; +} + fn main() {} diff --git a/tests/ui/default_numeric_fallback_i32.rs b/tests/ui/default_numeric_fallback_i32.rs index 71acccd702b..62d72f2feba 100644 --- a/tests/ui/default_numeric_fallback_i32.rs +++ b/tests/ui/default_numeric_fallback_i32.rs @@ -1,6 +1,7 @@ // run-rustfix // aux-build:macro_rules.rs +#![feature(lint_reasons)] #![warn(clippy::default_numeric_fallback)] #![allow( unused, @@ -173,4 +174,9 @@ mod in_macro { } } +fn check_expect_suppression() { + #[expect(clippy::default_numeric_fallback)] + let x = 21; +} + fn main() {} diff --git a/tests/ui/default_numeric_fallback_i32.stderr b/tests/ui/default_numeric_fallback_i32.stderr index 3cc84ff1132..f7c5e724c40 100644 --- a/tests/ui/default_numeric_fallback_i32.stderr +++ b/tests/ui/default_numeric_fallback_i32.stderr @@ -1,5 +1,5 @@ error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:20:17 + --> $DIR/default_numeric_fallback_i32.rs:21:17 | LL | let x = 22; | ^^ help: consider adding suffix: `22_i32` @@ -7,145 +7,145 @@ LL | let x = 22; = note: `-D clippy::default-numeric-fallback` implied by `-D warnings` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:21:18 + --> $DIR/default_numeric_fallback_i32.rs:22:18 | LL | let x = [1, 2, 3]; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:21:21 + --> $DIR/default_numeric_fallback_i32.rs:22:21 | LL | let x = [1, 2, 3]; | ^ help: consider adding suffix: `2_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:21:24 + --> $DIR/default_numeric_fallback_i32.rs:22:24 | LL | let x = [1, 2, 3]; | ^ help: consider adding suffix: `3_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:22:28 + --> $DIR/default_numeric_fallback_i32.rs:23:28 | LL | let x = if true { (1, 2) } else { (3, 4) }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:22:31 + --> $DIR/default_numeric_fallback_i32.rs:23:31 | LL | let x = if true { (1, 2) } else { (3, 4) }; | ^ help: consider adding suffix: `2_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:22:44 + --> $DIR/default_numeric_fallback_i32.rs:23:44 | LL | let x = if true { (1, 2) } else { (3, 4) }; | ^ help: consider adding suffix: `3_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:22:47 + --> $DIR/default_numeric_fallback_i32.rs:23:47 | LL | let x = if true { (1, 2) } else { (3, 4) }; | ^ help: consider adding suffix: `4_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:23:23 + --> $DIR/default_numeric_fallback_i32.rs:24:23 | LL | let x = match 1 { | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:24:13 + --> $DIR/default_numeric_fallback_i32.rs:25:13 | LL | 1 => 1, | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:24:18 + --> $DIR/default_numeric_fallback_i32.rs:25:18 | LL | 1 => 1, | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:25:18 + --> $DIR/default_numeric_fallback_i32.rs:26:18 | LL | _ => 2, | ^ help: consider adding suffix: `2_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:42:21 + --> $DIR/default_numeric_fallback_i32.rs:43:21 | LL | let y = 1; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:50:21 + --> $DIR/default_numeric_fallback_i32.rs:51:21 | LL | let y = 1; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:56:21 + --> $DIR/default_numeric_fallback_i32.rs:57:21 | LL | let y = 1; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:68:9 + --> $DIR/default_numeric_fallback_i32.rs:69:9 | LL | 1 | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:74:27 + --> $DIR/default_numeric_fallback_i32.rs:75:27 | LL | let f = || -> _ { 1 }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:78:29 + --> $DIR/default_numeric_fallback_i32.rs:79:29 | LL | let f = || -> i32 { 1 }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:92:21 + --> $DIR/default_numeric_fallback_i32.rs:93:21 | LL | generic_arg(1); | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:95:32 + --> $DIR/default_numeric_fallback_i32.rs:96:32 | LL | let x: _ = generic_arg(1); | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:113:28 + --> $DIR/default_numeric_fallback_i32.rs:114:28 | LL | GenericStruct { x: 1 }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:116:36 + --> $DIR/default_numeric_fallback_i32.rs:117:36 | LL | let _ = GenericStruct { x: 1 }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:134:24 + --> $DIR/default_numeric_fallback_i32.rs:135:24 | LL | GenericEnum::X(1); | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:154:23 + --> $DIR/default_numeric_fallback_i32.rs:155:23 | LL | s.generic_arg(1); | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:161:21 + --> $DIR/default_numeric_fallback_i32.rs:162:21 | LL | let x = 22; | ^^ help: consider adding suffix: `22_i32` diff --git a/tests/ui/derive_partial_eq_without_eq.fixed b/tests/ui/derive_partial_eq_without_eq.fixed index 012780258fc..bbbe467590f 100644 --- a/tests/ui/derive_partial_eq_without_eq.fixed +++ b/tests/ui/derive_partial_eq_without_eq.fixed @@ -4,28 +4,28 @@ #![warn(clippy::derive_partial_eq_without_eq)] // Don't warn on structs that aren't PartialEq -struct NotPartialEq { +pub struct NotPartialEq { foo: u32, bar: String, } // Eq can be derived but is missing #[derive(Debug, PartialEq, Eq)] -struct MissingEq { +pub struct MissingEq { foo: u32, bar: String, } // Eq is derived #[derive(PartialEq, Eq)] -struct NotMissingEq { +pub struct NotMissingEq { foo: u32, bar: String, } // Eq is manually implemented #[derive(PartialEq)] -struct ManualEqImpl { +pub struct ManualEqImpl { foo: u32, bar: String, } @@ -34,13 +34,13 @@ impl Eq for ManualEqImpl {} // Cannot be Eq because f32 isn't Eq #[derive(PartialEq)] -struct CannotBeEq { +pub struct CannotBeEq { foo: u32, bar: f32, } // Don't warn if PartialEq is manually implemented -struct ManualPartialEqImpl { +pub struct ManualPartialEqImpl { foo: u32, bar: String, } @@ -52,53 +52,75 @@ impl PartialEq for ManualPartialEqImpl { } // Generic fields should be properly checked for Eq-ness -#[derive(PartialEq)] -struct GenericNotEq { +#[derive(PartialEq, Eq)] +pub struct GenericNotEq { foo: T, bar: U, } #[derive(PartialEq, Eq)] -struct GenericEq { +pub struct GenericEq { foo: T, bar: U, } #[derive(PartialEq, Eq)] -struct TupleStruct(u32); +pub struct TupleStruct(u32); #[derive(PartialEq, Eq)] -struct GenericTupleStruct(T); +pub struct GenericTupleStruct(T); #[derive(PartialEq)] -struct TupleStructNotEq(f32); +pub struct TupleStructNotEq(f32); #[derive(PartialEq, Eq)] -enum Enum { +pub enum Enum { Foo(u32), Bar { a: String, b: () }, } #[derive(PartialEq, Eq)] -enum GenericEnum { +pub enum GenericEnum { Foo(T), Bar { a: U, b: V }, } #[derive(PartialEq)] -enum EnumNotEq { +pub enum EnumNotEq { Foo(u32), Bar { a: String, b: f32 }, } // Ensure that rustfix works properly when `PartialEq` has other derives on either side #[derive(Debug, PartialEq, Eq, Clone)] -struct RustFixWithOtherDerives; - -#[derive(PartialEq)] -struct Generic(T); +pub struct RustFixWithOtherDerives; #[derive(PartialEq, Eq)] -struct GenericPhantom(core::marker::PhantomData); +pub struct Generic(T); + +#[derive(PartialEq, Eq)] +pub struct GenericPhantom(core::marker::PhantomData); + +mod _hidden { + #[derive(PartialEq, Eq)] + pub struct Reexported; + + #[derive(PartialEq, Eq)] + pub struct InPubFn; + + #[derive(PartialEq)] + pub(crate) struct PubCrate; + + #[derive(PartialEq)] + pub(super) struct PubSuper; +} + +pub use _hidden::Reexported; +pub fn _from_mod() -> _hidden::InPubFn { + _hidden::InPubFn +} + +#[derive(PartialEq)] +struct InternalTy; fn main() {} diff --git a/tests/ui/derive_partial_eq_without_eq.rs b/tests/ui/derive_partial_eq_without_eq.rs index fc8285b0c6b..88d6fbd1af7 100644 --- a/tests/ui/derive_partial_eq_without_eq.rs +++ b/tests/ui/derive_partial_eq_without_eq.rs @@ -4,28 +4,28 @@ #![warn(clippy::derive_partial_eq_without_eq)] // Don't warn on structs that aren't PartialEq -struct NotPartialEq { +pub struct NotPartialEq { foo: u32, bar: String, } // Eq can be derived but is missing #[derive(Debug, PartialEq)] -struct MissingEq { +pub struct MissingEq { foo: u32, bar: String, } // Eq is derived #[derive(PartialEq, Eq)] -struct NotMissingEq { +pub struct NotMissingEq { foo: u32, bar: String, } // Eq is manually implemented #[derive(PartialEq)] -struct ManualEqImpl { +pub struct ManualEqImpl { foo: u32, bar: String, } @@ -34,13 +34,13 @@ impl Eq for ManualEqImpl {} // Cannot be Eq because f32 isn't Eq #[derive(PartialEq)] -struct CannotBeEq { +pub struct CannotBeEq { foo: u32, bar: f32, } // Don't warn if PartialEq is manually implemented -struct ManualPartialEqImpl { +pub struct ManualPartialEqImpl { foo: u32, bar: String, } @@ -53,52 +53,74 @@ impl PartialEq for ManualPartialEqImpl { // Generic fields should be properly checked for Eq-ness #[derive(PartialEq)] -struct GenericNotEq { +pub struct GenericNotEq { foo: T, bar: U, } #[derive(PartialEq)] -struct GenericEq { +pub struct GenericEq { foo: T, bar: U, } #[derive(PartialEq)] -struct TupleStruct(u32); +pub struct TupleStruct(u32); #[derive(PartialEq)] -struct GenericTupleStruct(T); +pub struct GenericTupleStruct(T); #[derive(PartialEq)] -struct TupleStructNotEq(f32); +pub struct TupleStructNotEq(f32); #[derive(PartialEq)] -enum Enum { +pub enum Enum { Foo(u32), Bar { a: String, b: () }, } #[derive(PartialEq)] -enum GenericEnum { +pub enum GenericEnum { Foo(T), Bar { a: U, b: V }, } #[derive(PartialEq)] -enum EnumNotEq { +pub enum EnumNotEq { Foo(u32), Bar { a: String, b: f32 }, } // Ensure that rustfix works properly when `PartialEq` has other derives on either side #[derive(Debug, PartialEq, Clone)] -struct RustFixWithOtherDerives; +pub struct RustFixWithOtherDerives; #[derive(PartialEq)] -struct Generic(T); +pub struct Generic(T); #[derive(PartialEq, Eq)] -struct GenericPhantom(core::marker::PhantomData); +pub struct GenericPhantom(core::marker::PhantomData); + +mod _hidden { + #[derive(PartialEq)] + pub struct Reexported; + + #[derive(PartialEq)] + pub struct InPubFn; + + #[derive(PartialEq)] + pub(crate) struct PubCrate; + + #[derive(PartialEq)] + pub(super) struct PubSuper; +} + +pub use _hidden::Reexported; +pub fn _from_mod() -> _hidden::InPubFn { + _hidden::InPubFn +} + +#[derive(PartialEq)] +struct InternalTy; fn main() {} diff --git a/tests/ui/derive_partial_eq_without_eq.stderr b/tests/ui/derive_partial_eq_without_eq.stderr index bf55165890a..794c5dab844 100644 --- a/tests/ui/derive_partial_eq_without_eq.stderr +++ b/tests/ui/derive_partial_eq_without_eq.stderr @@ -6,6 +6,12 @@ LL | #[derive(Debug, PartialEq)] | = note: `-D clippy::derive-partial-eq-without-eq` implied by `-D warnings` +error: you are deriving `PartialEq` and can implement `Eq` + --> $DIR/derive_partial_eq_without_eq.rs:55:10 + | +LL | #[derive(PartialEq)] + | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` + error: you are deriving `PartialEq` and can implement `Eq` --> $DIR/derive_partial_eq_without_eq.rs:61:10 | @@ -42,5 +48,23 @@ error: you are deriving `PartialEq` and can implement `Eq` LL | #[derive(Debug, PartialEq, Clone)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` -error: aborting due to 7 previous errors +error: you are deriving `PartialEq` and can implement `Eq` + --> $DIR/derive_partial_eq_without_eq.rs:98:10 + | +LL | #[derive(PartialEq)] + | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` + +error: you are deriving `PartialEq` and can implement `Eq` + --> $DIR/derive_partial_eq_without_eq.rs:105:14 + | +LL | #[derive(PartialEq)] + | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` + +error: you are deriving `PartialEq` and can implement `Eq` + --> $DIR/derive_partial_eq_without_eq.rs:108:14 + | +LL | #[derive(PartialEq)] + | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` + +error: aborting due to 11 previous errors diff --git a/tests/ui/eta.fixed b/tests/ui/eta.fixed index 6c2272f4dff..f8d559bf226 100644 --- a/tests/ui/eta.fixed +++ b/tests/ui/eta.fixed @@ -291,3 +291,15 @@ fn coerced_closure() { fn slice_fn(_: impl FnOnce() -> &'static [u8]) {} slice_fn(|| arr()); } + +// https://github.com/rust-lang/rust-clippy/issues/7861 +fn box_dyn() { + fn f(_: impl Fn(usize) -> Box) {} + f(|x| Box::new(x)); +} + +// https://github.com/rust-lang/rust-clippy/issues/5939 +fn not_general_enough() { + fn f(_: impl FnMut(&Path) -> std::io::Result<()>) {} + f(|path| std::fs::remove_file(path)); +} diff --git a/tests/ui/eta.rs b/tests/ui/eta.rs index a1a9c0dfbf3..f0fb55a1e5f 100644 --- a/tests/ui/eta.rs +++ b/tests/ui/eta.rs @@ -291,3 +291,15 @@ fn coerced_closure() { fn slice_fn(_: impl FnOnce() -> &'static [u8]) {} slice_fn(|| arr()); } + +// https://github.com/rust-lang/rust-clippy/issues/7861 +fn box_dyn() { + fn f(_: impl Fn(usize) -> Box) {} + f(|x| Box::new(x)); +} + +// https://github.com/rust-lang/rust-clippy/issues/5939 +fn not_general_enough() { + fn f(_: impl FnMut(&Path) -> std::io::Result<()>) {} + f(|path| std::fs::remove_file(path)); +} diff --git a/tests/ui/for_loops_over_fallibles.rs b/tests/ui/for_loops_over_fallibles.rs index 1b9dde87cd5..3390111d0a8 100644 --- a/tests/ui/for_loops_over_fallibles.rs +++ b/tests/ui/for_loops_over_fallibles.rs @@ -2,7 +2,7 @@ fn for_loops_over_fallibles() { let option = Some(1); - let result = option.ok_or("x not found"); + let mut result = option.ok_or("x not found"); let v = vec![0, 1, 2]; // check over an `Option` @@ -10,11 +10,26 @@ fn for_loops_over_fallibles() { println!("{}", x); } + // check over an `Option` + for x in option.iter() { + println!("{}", x); + } + // check over a `Result` for x in result { println!("{}", x); } + // check over a `Result` + for x in result.iter_mut() { + println!("{}", x); + } + + // check over a `Result` + for x in result.into_iter() { + println!("{}", x); + } + for x in option.ok_or("x not found") { println!("{}", x); } diff --git a/tests/ui/for_loops_over_fallibles.stderr b/tests/ui/for_loops_over_fallibles.stderr index 52b94875aec..8c8c022243a 100644 --- a/tests/ui/for_loops_over_fallibles.stderr +++ b/tests/ui/for_loops_over_fallibles.stderr @@ -7,16 +7,40 @@ LL | for x in option { = note: `-D clippy::for-loops-over-fallibles` implied by `-D warnings` = help: consider replacing `for x in option` with `if let Some(x) = option` -error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement +error: for loop over `option`, which is an `Option`. This is more readably written as an `if let` statement --> $DIR/for_loops_over_fallibles.rs:14:14 | +LL | for x in option.iter() { + | ^^^^^^ + | + = help: consider replacing `for x in option.iter()` with `if let Some(x) = option` + +error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement + --> $DIR/for_loops_over_fallibles.rs:19:14 + | LL | for x in result { | ^^^^^^ | = help: consider replacing `for x in result` with `if let Ok(x) = result` +error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement + --> $DIR/for_loops_over_fallibles.rs:24:14 + | +LL | for x in result.iter_mut() { + | ^^^^^^ + | + = help: consider replacing `for x in result.iter_mut()` with `if let Ok(x) = result` + +error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement + --> $DIR/for_loops_over_fallibles.rs:29:14 + | +LL | for x in result.into_iter() { + | ^^^^^^ + | + = help: consider replacing `for x in result.into_iter()` with `if let Ok(x) = result` + error: for loop over `option.ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement - --> $DIR/for_loops_over_fallibles.rs:18:14 + --> $DIR/for_loops_over_fallibles.rs:33:14 | LL | for x in option.ok_or("x not found") { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -24,7 +48,7 @@ LL | for x in option.ok_or("x not found") { = help: consider replacing `for x in option.ok_or("x not found")` with `if let Ok(x) = option.ok_or("x not found")` error: you are iterating over `Iterator::next()` which is an Option; this will compile but is probably not what you want - --> $DIR/for_loops_over_fallibles.rs:24:14 + --> $DIR/for_loops_over_fallibles.rs:39:14 | LL | for x in v.iter().next() { | ^^^^^^^^^^^^^^^ @@ -32,7 +56,7 @@ LL | for x in v.iter().next() { = note: `#[deny(clippy::iter_next_loop)]` on by default error: for loop over `v.iter().next().and(Some(0))`, which is an `Option`. This is more readably written as an `if let` statement - --> $DIR/for_loops_over_fallibles.rs:29:14 + --> $DIR/for_loops_over_fallibles.rs:44:14 | LL | for x in v.iter().next().and(Some(0)) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,7 +64,7 @@ LL | for x in v.iter().next().and(Some(0)) { = help: consider replacing `for x in v.iter().next().and(Some(0))` with `if let Some(x) = v.iter().next().and(Some(0))` error: for loop over `v.iter().next().ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement - --> $DIR/for_loops_over_fallibles.rs:33:14 + --> $DIR/for_loops_over_fallibles.rs:48:14 | LL | for x in v.iter().next().ok_or("x not found") { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -48,7 +72,7 @@ LL | for x in v.iter().next().ok_or("x not found") { = help: consider replacing `for x in v.iter().next().ok_or("x not found")` with `if let Ok(x) = v.iter().next().ok_or("x not found")` error: this loop never actually loops - --> $DIR/for_loops_over_fallibles.rs:45:5 + --> $DIR/for_loops_over_fallibles.rs:60:5 | LL | / while let Some(x) = option { LL | | println!("{}", x); @@ -59,7 +83,7 @@ LL | | } = note: `#[deny(clippy::never_loop)]` on by default error: this loop never actually loops - --> $DIR/for_loops_over_fallibles.rs:51:5 + --> $DIR/for_loops_over_fallibles.rs:66:5 | LL | / while let Ok(x) = result { LL | | println!("{}", x); @@ -67,5 +91,5 @@ LL | | break; LL | | } | |_____^ -error: aborting due to 8 previous errors +error: aborting due to 11 previous errors diff --git a/tests/ui/iter_overeager_cloned.fixed b/tests/ui/iter_overeager_cloned.fixed index 7c2b05d837b..c100705d017 100644 --- a/tests/ui/iter_overeager_cloned.fixed +++ b/tests/ui/iter_overeager_cloned.fixed @@ -18,7 +18,8 @@ fn main() { let _ = vec.iter().filter(|x| x == &"2").nth(2).cloned(); let _ = [Some(Some("str".to_string())), Some(Some("str".to_string()))] - .iter().flatten().cloned(); + .iter() + .flatten().cloned(); // Not implemented yet let _ = vec.iter().cloned().filter(|x| x.starts_with('2')); @@ -43,6 +44,9 @@ fn main() { // Should probably stay as it is. let _ = [0, 1, 2, 3, 4].iter().cloned().take(10); + + // `&Range<_>` doesn't implement `IntoIterator` + let _ = [0..1, 2..5].iter().cloned().flatten(); } // #8527 diff --git a/tests/ui/iter_overeager_cloned.rs b/tests/ui/iter_overeager_cloned.rs index f2d0b155d2c..2caa8802066 100644 --- a/tests/ui/iter_overeager_cloned.rs +++ b/tests/ui/iter_overeager_cloned.rs @@ -45,6 +45,9 @@ fn main() { // Should probably stay as it is. let _ = [0, 1, 2, 3, 4].iter().cloned().take(10); + + // `&Range<_>` doesn't implement `IntoIterator` + let _ = [0..1, 2..5].iter().cloned().flatten(); } // #8527 diff --git a/tests/ui/iter_overeager_cloned.stderr b/tests/ui/iter_overeager_cloned.stderr index 0582700fd16..dcae7cecd33 100644 --- a/tests/ui/iter_overeager_cloned.stderr +++ b/tests/ui/iter_overeager_cloned.stderr @@ -1,44 +1,56 @@ -error: called `cloned().last()` on an `Iterator`. It may be more efficient to call `last().cloned()` instead +error: unnecessarily eager cloning of iterator items --> $DIR/iter_overeager_cloned.rs:8:29 | LL | let _: Option = vec.iter().cloned().last(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `vec.iter().last().cloned()` + | ^^^^^^^^^^---------------- + | | + | help: try this: `.last().cloned()` | = note: `-D clippy::iter-overeager-cloned` implied by `-D warnings` -error: called `cloned().next()` on an `Iterator`. It may be more efficient to call `next().cloned()` instead +error: unnecessarily eager cloning of iterator items --> $DIR/iter_overeager_cloned.rs:10:29 | LL | let _: Option = vec.iter().chain(vec.iter()).cloned().next(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `vec.iter().chain(vec.iter()).next().cloned()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------------- + | | + | help: try this: `.next().cloned()` -error: called `cloned().count()` on an `Iterator`. It may be more efficient to call `count()` instead +error: unneeded cloning of iterator items --> $DIR/iter_overeager_cloned.rs:12:20 | LL | let _: usize = vec.iter().filter(|x| x == &"2").cloned().count(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `vec.iter().filter(|x| x == &"2").count()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------- + | | + | help: try this: `.count()` | = note: `-D clippy::redundant-clone` implied by `-D warnings` -error: called `cloned().take(...)` on an `Iterator`. It may be more efficient to call `take(...).cloned()` instead +error: unnecessarily eager cloning of iterator items --> $DIR/iter_overeager_cloned.rs:14:21 | LL | let _: Vec<_> = vec.iter().cloned().take(2).collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `vec.iter().take(2).cloned()` + | ^^^^^^^^^^----------------- + | | + | help: try this: `.take(2).cloned()` -error: called `cloned().skip(...)` on an `Iterator`. It may be more efficient to call `skip(...).cloned()` instead +error: unnecessarily eager cloning of iterator items --> $DIR/iter_overeager_cloned.rs:16:21 | LL | let _: Vec<_> = vec.iter().cloned().skip(2).collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `vec.iter().skip(2).cloned()` + | ^^^^^^^^^^----------------- + | | + | help: try this: `.skip(2).cloned()` -error: called `cloned().nth(...)` on an `Iterator`. It may be more efficient to call `nth(...).cloned()` instead +error: unnecessarily eager cloning of iterator items --> $DIR/iter_overeager_cloned.rs:18:13 | LL | let _ = vec.iter().filter(|x| x == &"2").cloned().nth(2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `vec.iter().filter(|x| x == &"2").nth(2).cloned()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------------- + | | + | help: try this: `.nth(2).cloned()` -error: called `cloned().flatten()` on an `Iterator`. It may be more efficient to call `flatten().cloned()` instead +error: unnecessarily eager cloning of iterator items --> $DIR/iter_overeager_cloned.rs:20:13 | LL | let _ = [Some(Some("str".to_string())), Some(Some("str".to_string()))] @@ -50,8 +62,8 @@ LL | | .flatten(); | help: try this | -LL ~ let _ = [Some(Some("str".to_string())), Some(Some("str".to_string()))] -LL ~ .iter().flatten().cloned(); +LL ~ .iter() +LL ~ .flatten().cloned(); | error: aborting due to 7 previous errors diff --git a/tests/ui/manual_filter_map.fixed b/tests/ui/manual_filter_map.fixed index fc8f58f8ea5..de0d8614889 100644 --- a/tests/ui/manual_filter_map.fixed +++ b/tests/ui/manual_filter_map.fixed @@ -35,3 +35,53 @@ fn to_opt(_: T) -> Option { fn to_res(_: T) -> Result { unimplemented!() } + +struct Issue8920<'a> { + option_field: Option, + result_field: Result, + ref_field: Option<&'a usize>, +} + +fn issue_8920() { + let mut vec = vec![Issue8920 { + option_field: Some(String::from("str")), + result_field: Ok(String::from("str")), + ref_field: Some(&1), + }]; + + let _ = vec + .iter() + .filter_map(|f| f.option_field.clone()); + + let _ = vec + .iter() + .filter_map(|f| f.ref_field.cloned()); + + let _ = vec + .iter() + .filter_map(|f| f.ref_field.copied()); + + let _ = vec + .iter() + .filter_map(|f| f.result_field.clone().ok()); + + let _ = vec + .iter() + .filter_map(|f| f.result_field.as_ref().ok()); + + let _ = vec + .iter() + .filter_map(|f| f.result_field.as_deref().ok()); + + let _ = vec + .iter_mut() + .filter_map(|f| f.result_field.as_mut().ok()); + + let _ = vec + .iter_mut() + .filter_map(|f| f.result_field.as_deref_mut().ok()); + + let _ = vec + .iter() + .filter_map(|f| f.result_field.to_owned().ok()); +} diff --git a/tests/ui/manual_filter_map.rs b/tests/ui/manual_filter_map.rs index 3af4bbee3bf..bd6516f038b 100644 --- a/tests/ui/manual_filter_map.rs +++ b/tests/ui/manual_filter_map.rs @@ -35,3 +35,62 @@ fn to_opt(_: T) -> Option { fn to_res(_: T) -> Result { unimplemented!() } + +struct Issue8920<'a> { + option_field: Option, + result_field: Result, + ref_field: Option<&'a usize>, +} + +fn issue_8920() { + let mut vec = vec![Issue8920 { + option_field: Some(String::from("str")), + result_field: Ok(String::from("str")), + ref_field: Some(&1), + }]; + + let _ = vec + .iter() + .filter(|f| f.option_field.is_some()) + .map(|f| f.option_field.clone().unwrap()); + + let _ = vec + .iter() + .filter(|f| f.ref_field.is_some()) + .map(|f| f.ref_field.cloned().unwrap()); + + let _ = vec + .iter() + .filter(|f| f.ref_field.is_some()) + .map(|f| f.ref_field.copied().unwrap()); + + let _ = vec + .iter() + .filter(|f| f.result_field.is_ok()) + .map(|f| f.result_field.clone().unwrap()); + + let _ = vec + .iter() + .filter(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_ref().unwrap()); + + let _ = vec + .iter() + .filter(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_deref().unwrap()); + + let _ = vec + .iter_mut() + .filter(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_mut().unwrap()); + + let _ = vec + .iter_mut() + .filter(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_deref_mut().unwrap()); + + let _ = vec + .iter() + .filter(|f| f.result_field.is_ok()) + .map(|f| f.result_field.to_owned().unwrap()); +} diff --git a/tests/ui/manual_filter_map.stderr b/tests/ui/manual_filter_map.stderr index 4d4e2d5c12f..465f1b19110 100644 --- a/tests/ui/manual_filter_map.stderr +++ b/tests/ui/manual_filter_map.stderr @@ -18,5 +18,77 @@ error: `filter(..).map(..)` can be simplified as `filter_map(..)` LL | let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_res(a).ok())` -error: aborting due to 3 previous errors +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:54:10 + | +LL | .filter(|f| f.option_field.is_some()) + | __________^ +LL | | .map(|f| f.option_field.clone().unwrap()); + | |_________________________________________________^ help: try: `filter_map(|f| f.option_field.clone())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:59:10 + | +LL | .filter(|f| f.ref_field.is_some()) + | __________^ +LL | | .map(|f| f.ref_field.cloned().unwrap()); + | |_______________________________________________^ help: try: `filter_map(|f| f.ref_field.cloned())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:64:10 + | +LL | .filter(|f| f.ref_field.is_some()) + | __________^ +LL | | .map(|f| f.ref_field.copied().unwrap()); + | |_______________________________________________^ help: try: `filter_map(|f| f.ref_field.copied())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:69:10 + | +LL | .filter(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.clone().unwrap()); + | |_________________________________________________^ help: try: `filter_map(|f| f.result_field.clone().ok())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:74:10 + | +LL | .filter(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_ref().unwrap()); + | |__________________________________________________^ help: try: `filter_map(|f| f.result_field.as_ref().ok())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:79:10 + | +LL | .filter(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_deref().unwrap()); + | |____________________________________________________^ help: try: `filter_map(|f| f.result_field.as_deref().ok())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:84:10 + | +LL | .filter(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_mut().unwrap()); + | |__________________________________________________^ help: try: `filter_map(|f| f.result_field.as_mut().ok())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:89:10 + | +LL | .filter(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_deref_mut().unwrap()); + | |________________________________________________________^ help: try: `filter_map(|f| f.result_field.as_deref_mut().ok())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:94:10 + | +LL | .filter(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.to_owned().unwrap()); + | |____________________________________________________^ help: try: `filter_map(|f| f.result_field.to_owned().ok())` + +error: aborting due to 12 previous errors diff --git a/tests/ui/manual_find_map.fixed b/tests/ui/manual_find_map.fixed index 95e97c4fd1f..d69b6c1dcf3 100644 --- a/tests/ui/manual_find_map.fixed +++ b/tests/ui/manual_find_map.fixed @@ -35,3 +35,53 @@ fn to_opt(_: T) -> Option { fn to_res(_: T) -> Result { unimplemented!() } + +struct Issue8920<'a> { + option_field: Option, + result_field: Result, + ref_field: Option<&'a usize>, +} + +fn issue_8920() { + let mut vec = vec![Issue8920 { + option_field: Some(String::from("str")), + result_field: Ok(String::from("str")), + ref_field: Some(&1), + }]; + + let _ = vec + .iter() + .find_map(|f| f.option_field.clone()); + + let _ = vec + .iter() + .find_map(|f| f.ref_field.cloned()); + + let _ = vec + .iter() + .find_map(|f| f.ref_field.copied()); + + let _ = vec + .iter() + .find_map(|f| f.result_field.clone().ok()); + + let _ = vec + .iter() + .find_map(|f| f.result_field.as_ref().ok()); + + let _ = vec + .iter() + .find_map(|f| f.result_field.as_deref().ok()); + + let _ = vec + .iter_mut() + .find_map(|f| f.result_field.as_mut().ok()); + + let _ = vec + .iter_mut() + .find_map(|f| f.result_field.as_deref_mut().ok()); + + let _ = vec + .iter() + .find_map(|f| f.result_field.to_owned().ok()); +} diff --git a/tests/ui/manual_find_map.rs b/tests/ui/manual_find_map.rs index cd3c82e3b25..1c4e18e31c8 100644 --- a/tests/ui/manual_find_map.rs +++ b/tests/ui/manual_find_map.rs @@ -35,3 +35,62 @@ fn to_opt(_: T) -> Option { fn to_res(_: T) -> Result { unimplemented!() } + +struct Issue8920<'a> { + option_field: Option, + result_field: Result, + ref_field: Option<&'a usize>, +} + +fn issue_8920() { + let mut vec = vec![Issue8920 { + option_field: Some(String::from("str")), + result_field: Ok(String::from("str")), + ref_field: Some(&1), + }]; + + let _ = vec + .iter() + .find(|f| f.option_field.is_some()) + .map(|f| f.option_field.clone().unwrap()); + + let _ = vec + .iter() + .find(|f| f.ref_field.is_some()) + .map(|f| f.ref_field.cloned().unwrap()); + + let _ = vec + .iter() + .find(|f| f.ref_field.is_some()) + .map(|f| f.ref_field.copied().unwrap()); + + let _ = vec + .iter() + .find(|f| f.result_field.is_ok()) + .map(|f| f.result_field.clone().unwrap()); + + let _ = vec + .iter() + .find(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_ref().unwrap()); + + let _ = vec + .iter() + .find(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_deref().unwrap()); + + let _ = vec + .iter_mut() + .find(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_mut().unwrap()); + + let _ = vec + .iter_mut() + .find(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_deref_mut().unwrap()); + + let _ = vec + .iter() + .find(|f| f.result_field.is_ok()) + .map(|f| f.result_field.to_owned().unwrap()); +} diff --git a/tests/ui/manual_find_map.stderr b/tests/ui/manual_find_map.stderr index 9e7f798df45..9dea42b7686 100644 --- a/tests/ui/manual_find_map.stderr +++ b/tests/ui/manual_find_map.stderr @@ -18,5 +18,77 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` LL | let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_res(a).ok())` -error: aborting due to 3 previous errors +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:54:10 + | +LL | .find(|f| f.option_field.is_some()) + | __________^ +LL | | .map(|f| f.option_field.clone().unwrap()); + | |_________________________________________________^ help: try: `find_map(|f| f.option_field.clone())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:59:10 + | +LL | .find(|f| f.ref_field.is_some()) + | __________^ +LL | | .map(|f| f.ref_field.cloned().unwrap()); + | |_______________________________________________^ help: try: `find_map(|f| f.ref_field.cloned())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:64:10 + | +LL | .find(|f| f.ref_field.is_some()) + | __________^ +LL | | .map(|f| f.ref_field.copied().unwrap()); + | |_______________________________________________^ help: try: `find_map(|f| f.ref_field.copied())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:69:10 + | +LL | .find(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.clone().unwrap()); + | |_________________________________________________^ help: try: `find_map(|f| f.result_field.clone().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:74:10 + | +LL | .find(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_ref().unwrap()); + | |__________________________________________________^ help: try: `find_map(|f| f.result_field.as_ref().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:79:10 + | +LL | .find(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_deref().unwrap()); + | |____________________________________________________^ help: try: `find_map(|f| f.result_field.as_deref().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:84:10 + | +LL | .find(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_mut().unwrap()); + | |__________________________________________________^ help: try: `find_map(|f| f.result_field.as_mut().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:89:10 + | +LL | .find(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_deref_mut().unwrap()); + | |________________________________________________________^ help: try: `find_map(|f| f.result_field.as_deref_mut().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:94:10 + | +LL | .find(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.to_owned().unwrap()); + | |____________________________________________________^ help: try: `find_map(|f| f.result_field.to_owned().ok())` + +error: aborting due to 12 previous errors diff --git a/tests/ui/manual_map_option.fixed b/tests/ui/manual_map_option.fixed index fc6a7abca0e..a59da4ae10b 100644 --- a/tests/ui/manual_map_option.fixed +++ b/tests/ui/manual_map_option.fixed @@ -7,6 +7,7 @@ clippy::unit_arg, clippy::match_ref_pats, clippy::redundant_pattern_matching, + clippy::for_loops_over_fallibles, dead_code )] diff --git a/tests/ui/manual_map_option.rs b/tests/ui/manual_map_option.rs index 16508270f64..0bdbefa51e8 100644 --- a/tests/ui/manual_map_option.rs +++ b/tests/ui/manual_map_option.rs @@ -7,6 +7,7 @@ clippy::unit_arg, clippy::match_ref_pats, clippy::redundant_pattern_matching, + clippy::for_loops_over_fallibles, dead_code )] diff --git a/tests/ui/manual_map_option.stderr b/tests/ui/manual_map_option.stderr index 0036b8151de..cdc2c0e62a9 100644 --- a/tests/ui/manual_map_option.stderr +++ b/tests/ui/manual_map_option.stderr @@ -1,5 +1,5 @@ error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:14:5 + --> $DIR/manual_map_option.rs:15:5 | LL | / match Some(0) { LL | | Some(_) => Some(2), @@ -10,7 +10,7 @@ LL | | }; = note: `-D clippy::manual-map` implied by `-D warnings` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:19:5 + --> $DIR/manual_map_option.rs:20:5 | LL | / match Some(0) { LL | | Some(x) => Some(x + 1), @@ -19,7 +19,7 @@ LL | | }; | |_____^ help: try this: `Some(0).map(|x| x + 1)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:24:5 + --> $DIR/manual_map_option.rs:25:5 | LL | / match Some("") { LL | | Some(x) => Some(x.is_empty()), @@ -28,7 +28,7 @@ LL | | }; | |_____^ help: try this: `Some("").map(|x| x.is_empty())` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:29:5 + --> $DIR/manual_map_option.rs:30:5 | LL | / if let Some(x) = Some(0) { LL | | Some(!x) @@ -38,7 +38,7 @@ LL | | }; | |_____^ help: try this: `Some(0).map(|x| !x)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:36:5 + --> $DIR/manual_map_option.rs:37:5 | LL | / match Some(0) { LL | | Some(x) => { Some(std::convert::identity(x)) } @@ -47,7 +47,7 @@ LL | | }; | |_____^ help: try this: `Some(0).map(std::convert::identity)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:41:5 + --> $DIR/manual_map_option.rs:42:5 | LL | / match Some(&String::new()) { LL | | Some(x) => Some(str::len(x)), @@ -56,7 +56,7 @@ LL | | }; | |_____^ help: try this: `Some(&String::new()).map(|x| str::len(x))` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:51:5 + --> $DIR/manual_map_option.rs:52:5 | LL | / match &Some([0, 1]) { LL | | Some(x) => Some(x[0]), @@ -65,7 +65,7 @@ LL | | }; | |_____^ help: try this: `Some([0, 1]).as_ref().map(|x| x[0])` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:56:5 + --> $DIR/manual_map_option.rs:57:5 | LL | / match &Some(0) { LL | | &Some(x) => Some(x * 2), @@ -74,7 +74,7 @@ LL | | }; | |_____^ help: try this: `Some(0).map(|x| x * 2)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:61:5 + --> $DIR/manual_map_option.rs:62:5 | LL | / match Some(String::new()) { LL | | Some(ref x) => Some(x.is_empty()), @@ -83,7 +83,7 @@ LL | | }; | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.is_empty())` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:66:5 + --> $DIR/manual_map_option.rs:67:5 | LL | / match &&Some(String::new()) { LL | | Some(x) => Some(x.len()), @@ -92,7 +92,7 @@ LL | | }; | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.len())` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:71:5 + --> $DIR/manual_map_option.rs:72:5 | LL | / match &&Some(0) { LL | | &&Some(x) => Some(x + x), @@ -101,7 +101,7 @@ LL | | }; | |_____^ help: try this: `Some(0).map(|x| x + x)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:84:9 + --> $DIR/manual_map_option.rs:85:9 | LL | / match &mut Some(String::new()) { LL | | Some(x) => Some(x.push_str("")), @@ -110,7 +110,7 @@ LL | | }; | |_________^ help: try this: `Some(String::new()).as_mut().map(|x| x.push_str(""))` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:90:5 + --> $DIR/manual_map_option.rs:91:5 | LL | / match &mut Some(String::new()) { LL | | Some(ref x) => Some(x.len()), @@ -119,7 +119,7 @@ LL | | }; | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.len())` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:95:5 + --> $DIR/manual_map_option.rs:96:5 | LL | / match &mut &Some(String::new()) { LL | | Some(x) => Some(x.is_empty()), @@ -128,7 +128,7 @@ LL | | }; | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.is_empty())` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:100:5 + --> $DIR/manual_map_option.rs:101:5 | LL | / match Some((0, 1, 2)) { LL | | Some((x, y, z)) => Some(x + y + z), @@ -137,7 +137,7 @@ LL | | }; | |_____^ help: try this: `Some((0, 1, 2)).map(|(x, y, z)| x + y + z)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:105:5 + --> $DIR/manual_map_option.rs:106:5 | LL | / match Some([1, 2, 3]) { LL | | Some([first, ..]) => Some(first), @@ -146,7 +146,7 @@ LL | | }; | |_____^ help: try this: `Some([1, 2, 3]).map(|[first, ..]| first)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:110:5 + --> $DIR/manual_map_option.rs:111:5 | LL | / match &Some((String::new(), "test")) { LL | | Some((x, y)) => Some((y, x)), @@ -155,7 +155,7 @@ LL | | }; | |_____^ help: try this: `Some((String::new(), "test")).as_ref().map(|(x, y)| (y, x))` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:168:5 + --> $DIR/manual_map_option.rs:169:5 | LL | / match Some(0) { LL | | Some(x) => Some(vec![x]), @@ -164,7 +164,7 @@ LL | | }; | |_____^ help: try this: `Some(0).map(|x| vec![x])` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:173:5 + --> $DIR/manual_map_option.rs:174:5 | LL | / match option_env!("") { LL | | Some(x) => Some(String::from(x)), @@ -173,7 +173,7 @@ LL | | }; | |_____^ help: try this: `option_env!("").map(String::from)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:193:12 + --> $DIR/manual_map_option.rs:194:12 | LL | } else if let Some(x) = Some(0) { | ____________^ @@ -184,7 +184,7 @@ LL | | }; | |_____^ help: try this: `{ Some(0).map(|x| x + 1) }` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:201:12 + --> $DIR/manual_map_option.rs:202:12 | LL | } else if let Some(x) = Some(0) { | ____________^ diff --git a/tests/ui/needless_borrow.fixed b/tests/ui/needless_borrow.fixed index efeb5cf5b2b..e7a483c0582 100644 --- a/tests/ui/needless_borrow.fixed +++ b/tests/ui/needless_borrow.fixed @@ -1,5 +1,7 @@ // run-rustfix +#![feature(lint_reasons)] + #[warn(clippy::all, clippy::needless_borrow)] #[allow(unused_variables, clippy::unnecessary_mut_passed)] fn main() { @@ -96,3 +98,10 @@ trait Trait {} impl<'a> Trait for &'a str {} fn h(_: &dyn Trait) {} + +#[allow(dead_code)] +fn check_expect_suppression() { + let a = 5; + #[expect(clippy::needless_borrow)] + let _ = x(&&a); +} diff --git a/tests/ui/needless_borrow.rs b/tests/ui/needless_borrow.rs index 3e416a0eb84..1d6bf46405a 100644 --- a/tests/ui/needless_borrow.rs +++ b/tests/ui/needless_borrow.rs @@ -1,5 +1,7 @@ // run-rustfix +#![feature(lint_reasons)] + #[warn(clippy::all, clippy::needless_borrow)] #[allow(unused_variables, clippy::unnecessary_mut_passed)] fn main() { @@ -96,3 +98,10 @@ trait Trait {} impl<'a> Trait for &'a str {} fn h(_: &dyn Trait) {} + +#[allow(dead_code)] +fn check_expect_suppression() { + let a = 5; + #[expect(clippy::needless_borrow)] + let _ = x(&&a); +} diff --git a/tests/ui/needless_borrow.stderr b/tests/ui/needless_borrow.stderr index 05591ce4117..be59d8f546d 100644 --- a/tests/ui/needless_borrow.stderr +++ b/tests/ui/needless_borrow.stderr @@ -1,5 +1,5 @@ error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:9:15 + --> $DIR/needless_borrow.rs:11:15 | LL | let _ = x(&&a); // warn | ^^^ help: change this to: `&a` @@ -7,91 +7,91 @@ LL | let _ = x(&&a); // warn = note: `-D clippy::needless-borrow` implied by `-D warnings` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:13:13 + --> $DIR/needless_borrow.rs:15:13 | LL | mut_ref(&mut &mut b); // warn | ^^^^^^^^^^^ help: change this to: `&mut b` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:25:13 + --> $DIR/needless_borrow.rs:27:13 | LL | &&a | ^^^ help: change this to: `&a` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:27:15 + --> $DIR/needless_borrow.rs:29:15 | LL | 46 => &&a, | ^^^ help: change this to: `&a` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:33:27 + --> $DIR/needless_borrow.rs:35:27 | LL | break &ref_a; | ^^^^^^ help: change this to: `ref_a` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:40:15 + --> $DIR/needless_borrow.rs:42:15 | LL | let _ = x(&&&a); | ^^^^ help: change this to: `&a` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:41:15 + --> $DIR/needless_borrow.rs:43:15 | LL | let _ = x(&mut &&a); | ^^^^^^^^ help: change this to: `&a` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:42:15 + --> $DIR/needless_borrow.rs:44:15 | LL | let _ = x(&&&mut b); | ^^^^^^^^ help: change this to: `&mut b` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:43:15 + --> $DIR/needless_borrow.rs:45:15 | LL | let _ = x(&&ref_a); | ^^^^^^^ help: change this to: `ref_a` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:46:11 + --> $DIR/needless_borrow.rs:48:11 | LL | x(&b); | ^^ help: change this to: `b` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:53:13 + --> $DIR/needless_borrow.rs:55:13 | LL | mut_ref(&mut x); | ^^^^^^ help: change this to: `x` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:54:13 + --> $DIR/needless_borrow.rs:56:13 | LL | mut_ref(&mut &mut x); | ^^^^^^^^^^^ help: change this to: `x` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:55:23 + --> $DIR/needless_borrow.rs:57:23 | LL | let y: &mut i32 = &mut x; | ^^^^^^ help: change this to: `x` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:56:23 + --> $DIR/needless_borrow.rs:58:23 | LL | let y: &mut i32 = &mut &mut x; | ^^^^^^^^^^^ help: change this to: `x` error: this expression borrows a value the compiler would automatically borrow - --> $DIR/needless_borrow.rs:72:13 + --> $DIR/needless_borrow.rs:74:13 | LL | let _ = (&x).0; | ^^^^ help: change this to: `x` error: this expression borrows a value the compiler would automatically borrow - --> $DIR/needless_borrow.rs:74:22 + --> $DIR/needless_borrow.rs:76:22 | LL | let _ = unsafe { (&*x).0 }; | ^^^^^ help: change this to: `(*x)` diff --git a/tests/ui/needless_parens_on_range_literals.fixed b/tests/ui/needless_parens_on_range_literals.fixed new file mode 100644 index 00000000000..1bd75c806bc --- /dev/null +++ b/tests/ui/needless_parens_on_range_literals.fixed @@ -0,0 +1,14 @@ +// run-rustfix +// edition:2018 + +#![warn(clippy::needless_parens_on_range_literals)] +#![allow(clippy::almost_complete_letter_range)] + +fn main() { + let _ = 'a'..='z'; + let _ = 'a'..'z'; + let _ = (1.)..2.; + let _ = (1.)..2.; + let _ = 'a'..; + let _ = ..'z'; +} diff --git a/tests/ui/needless_parens_on_range_literals.rs b/tests/ui/needless_parens_on_range_literals.rs new file mode 100644 index 00000000000..7abb8a1adc1 --- /dev/null +++ b/tests/ui/needless_parens_on_range_literals.rs @@ -0,0 +1,14 @@ +// run-rustfix +// edition:2018 + +#![warn(clippy::needless_parens_on_range_literals)] +#![allow(clippy::almost_complete_letter_range)] + +fn main() { + let _ = ('a')..=('z'); + let _ = 'a'..('z'); + let _ = (1.)..2.; + let _ = (1.)..(2.); + let _ = ('a')..; + let _ = ..('z'); +} diff --git a/tests/ui/needless_parens_on_range_literals.stderr b/tests/ui/needless_parens_on_range_literals.stderr new file mode 100644 index 00000000000..505f7ac916d --- /dev/null +++ b/tests/ui/needless_parens_on_range_literals.stderr @@ -0,0 +1,40 @@ +error: needless parenthesis on range literals can be removed + --> $DIR/needless_parens_on_range_literals.rs:8:13 + | +LL | let _ = ('a')..=('z'); + | ^^^^^ help: try: `'a'` + | + = note: `-D clippy::needless-parens-on-range-literals` implied by `-D warnings` + +error: needless parenthesis on range literals can be removed + --> $DIR/needless_parens_on_range_literals.rs:8:21 + | +LL | let _ = ('a')..=('z'); + | ^^^^^ help: try: `'z'` + +error: needless parenthesis on range literals can be removed + --> $DIR/needless_parens_on_range_literals.rs:9:18 + | +LL | let _ = 'a'..('z'); + | ^^^^^ help: try: `'z'` + +error: needless parenthesis on range literals can be removed + --> $DIR/needless_parens_on_range_literals.rs:11:19 + | +LL | let _ = (1.)..(2.); + | ^^^^ help: try: `2.` + +error: needless parenthesis on range literals can be removed + --> $DIR/needless_parens_on_range_literals.rs:12:13 + | +LL | let _ = ('a')..; + | ^^^^^ help: try: `'a'` + +error: needless parenthesis on range literals can be removed + --> $DIR/needless_parens_on_range_literals.rs:13:15 + | +LL | let _ = ..('z'); + | ^^^^^ help: try: `'z'` + +error: aborting due to 6 previous errors + diff --git a/tests/ui/never_loop.rs b/tests/ui/never_loop.rs index 2770eb2b2ab..0a21589dd0d 100644 --- a/tests/ui/never_loop.rs +++ b/tests/ui/never_loop.rs @@ -186,6 +186,23 @@ pub fn test16() { } } +// Issue #9001: `continue` in struct expression fields +pub fn test17() { + struct Foo { + f: (), + } + + let mut n = 0; + let _ = loop { + break Foo { + f: if n < 5 { + n += 1; + continue; + }, + }; + }; +} + fn main() { test1(); test2(); diff --git a/tests/ui/read_zero_byte_vec.rs b/tests/ui/read_zero_byte_vec.rs new file mode 100644 index 00000000000..30807e0f8b9 --- /dev/null +++ b/tests/ui/read_zero_byte_vec.rs @@ -0,0 +1,87 @@ +#![warn(clippy::read_zero_byte_vec)] +#![allow(clippy::unused_io_amount)] +use std::fs::File; +use std::io; +use std::io::prelude::*; + +extern crate futures; +use futures::io::{AsyncRead, AsyncReadExt}; +use tokio::io::{AsyncRead as TokioAsyncRead, AsyncReadExt as _, AsyncWrite as TokioAsyncWrite, AsyncWriteExt as _}; + +fn test() -> io::Result<()> { + let cap = 1000; + let mut f = File::open("foo.txt").unwrap(); + + // should lint + let mut data = Vec::with_capacity(20); + f.read_exact(&mut data).unwrap(); + + // should lint + let mut data2 = Vec::with_capacity(cap); + f.read_exact(&mut data2)?; + + // should lint + let mut data3 = Vec::new(); + f.read_exact(&mut data3)?; + + // should lint + let mut data4 = vec![]; + let _ = f.read(&mut data4)?; + + // should lint + let _ = { + let mut data5 = Vec::new(); + f.read(&mut data5) + }; + + // should lint + let _ = { + let mut data6: Vec = Default::default(); + f.read(&mut data6) + }; + + // should not lint + let mut buf = [0u8; 100]; + f.read(&mut buf)?; + + // should not lint + let mut empty = vec![]; + let mut data7 = vec![]; + f.read(&mut empty); + + // should not lint + f.read(&mut data7); + + // should not lint + let mut data8 = Vec::new(); + data8.resize(100, 0); + f.read_exact(&mut data8)?; + + // should not lint + let mut data9 = vec![1, 2, 3]; + f.read_exact(&mut data9)?; + + Ok(()) +} + +async fn test_futures(r: &mut R) { + // should lint + let mut data = Vec::new(); + r.read(&mut data).await.unwrap(); + + // should lint + let mut data2 = Vec::new(); + r.read_exact(&mut data2).await.unwrap(); +} + +async fn test_tokio(r: &mut R) { + // should lint + let mut data = Vec::new(); + r.read(&mut data).await.unwrap(); + + // should lint + let mut data2 = Vec::new(); + r.read_exact(&mut data2).await.unwrap(); +} + +fn main() {} diff --git a/tests/ui/read_zero_byte_vec.stderr b/tests/ui/read_zero_byte_vec.stderr new file mode 100644 index 00000000000..08ba9753d7c --- /dev/null +++ b/tests/ui/read_zero_byte_vec.stderr @@ -0,0 +1,64 @@ +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:17:5 + | +LL | f.read_exact(&mut data).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data.resize(20, 0); f.read_exact(&mut data).unwrap();` + | + = note: `-D clippy::read-zero-byte-vec` implied by `-D warnings` + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:21:5 + | +LL | f.read_exact(&mut data2)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data2.resize(cap, 0); f.read_exact(&mut data2)?;` + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:25:5 + | +LL | f.read_exact(&mut data3)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:29:5 + | +LL | let _ = f.read(&mut data4)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:34:9 + | +LL | f.read(&mut data5) + | ^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:40:9 + | +LL | f.read(&mut data6) + | ^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:70:5 + | +LL | r.read(&mut data).await.unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:74:5 + | +LL | r.read_exact(&mut data2).await.unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:80:5 + | +LL | r.read(&mut data).await.unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:84:5 + | +LL | r.read_exact(&mut data2).await.unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 10 previous errors + diff --git a/tests/ui/ref_binding_to_reference.rs b/tests/ui/ref_binding_to_reference.rs index fe742a4c2f4..570ef406e4a 100644 --- a/tests/ui/ref_binding_to_reference.rs +++ b/tests/ui/ref_binding_to_reference.rs @@ -1,5 +1,6 @@ // FIXME: run-rustfix waiting on multi-span suggestions +#![feature(lint_reasons)] #![warn(clippy::ref_binding_to_reference)] #![allow(clippy::needless_borrowed_reference)] @@ -73,3 +74,12 @@ impl T1 for S { let _: &&String = x; } } + +fn check_expect_suppression() { + let x = String::new(); + #[expect(clippy::ref_binding_to_reference)] + let _: &&String = match Some(&x) { + Some(ref x) => x, + None => return, + }; +} diff --git a/tests/ui/ref_binding_to_reference.stderr b/tests/ui/ref_binding_to_reference.stderr index c5856e15fa9..eb36cd516a2 100644 --- a/tests/ui/ref_binding_to_reference.stderr +++ b/tests/ui/ref_binding_to_reference.stderr @@ -1,5 +1,5 @@ error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:30:14 + --> $DIR/ref_binding_to_reference.rs:31:14 | LL | Some(ref x) => x, | ^^^^^ @@ -11,7 +11,7 @@ LL | Some(x) => &x, | ~ ~~ error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:36:14 + --> $DIR/ref_binding_to_reference.rs:37:14 | LL | Some(ref x) => { | ^^^^^ @@ -25,7 +25,7 @@ LL ~ &x | error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:46:14 + --> $DIR/ref_binding_to_reference.rs:47:14 | LL | Some(ref x) => m2!(x), | ^^^^^ @@ -36,7 +36,7 @@ LL | Some(x) => m2!(&x), | ~ ~~ error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:51:15 + --> $DIR/ref_binding_to_reference.rs:52:15 | LL | let _ = |&ref x: &&String| { | ^^^^^ @@ -48,7 +48,7 @@ LL ~ let _: &&String = &x; | error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:57:12 + --> $DIR/ref_binding_to_reference.rs:58:12 | LL | fn f2<'a>(&ref x: &&'a String) -> &'a String { | ^^^^^ @@ -61,7 +61,7 @@ LL ~ x | error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:64:11 + --> $DIR/ref_binding_to_reference.rs:65:11 | LL | fn f(&ref x: &&String) { | ^^^^^ @@ -73,7 +73,7 @@ LL ~ let _: &&String = &x; | error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:72:11 + --> $DIR/ref_binding_to_reference.rs:73:11 | LL | fn f(&ref x: &&String) { | ^^^^^ diff --git a/tests/ui/same_name_method.rs b/tests/ui/same_name_method.rs index 12e10ba6c49..9562b47f0c4 100644 --- a/tests/ui/same_name_method.rs +++ b/tests/ui/same_name_method.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![warn(clippy::same_name_method)] #![allow(dead_code, non_camel_case_types)] @@ -108,4 +109,19 @@ mod should_not_lint { } } +mod check_expect_suppression { + use crate::T1; + + struct S; + + impl S { + #[expect(clippy::same_name_method)] + fn foo() {} + } + + impl T1 for S { + fn foo() {} + } +} + fn main() {} diff --git a/tests/ui/same_name_method.stderr b/tests/ui/same_name_method.stderr index cf06eb32e0c..f55ec9f3cc6 100644 --- a/tests/ui/same_name_method.stderr +++ b/tests/ui/same_name_method.stderr @@ -1,61 +1,61 @@ error: method's name is the same as an existing method in a trait - --> $DIR/same_name_method.rs:20:13 + --> $DIR/same_name_method.rs:21:13 | LL | fn foo() {} | ^^^^^^^^^^^ | = note: `-D clippy::same-name-method` implied by `-D warnings` note: existing `foo` defined here - --> $DIR/same_name_method.rs:24:13 + --> $DIR/same_name_method.rs:25:13 | LL | fn foo() {} | ^^^^^^^^^^^ error: method's name is the same as an existing method in a trait - --> $DIR/same_name_method.rs:34:13 + --> $DIR/same_name_method.rs:35:13 | LL | fn clone() {} | ^^^^^^^^^^^^^ | note: existing `clone` defined here - --> $DIR/same_name_method.rs:30:18 + --> $DIR/same_name_method.rs:31:18 | LL | #[derive(Clone)] | ^^^^^ = note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info) error: method's name is the same as an existing method in a trait - --> $DIR/same_name_method.rs:44:13 + --> $DIR/same_name_method.rs:45:13 | LL | fn foo() {} | ^^^^^^^^^^^ | note: existing `foo` defined here - --> $DIR/same_name_method.rs:48:13 + --> $DIR/same_name_method.rs:49:13 | LL | fn foo() {} | ^^^^^^^^^^^ error: method's name is the same as an existing method in a trait - --> $DIR/same_name_method.rs:58:13 + --> $DIR/same_name_method.rs:59:13 | LL | fn foo() {} | ^^^^^^^^^^^ | note: existing `foo` defined here - --> $DIR/same_name_method.rs:61:9 + --> $DIR/same_name_method.rs:62:9 | LL | impl T1 for S {} | ^^^^^^^^^^^^^^^^ error: method's name is the same as an existing method in a trait - --> $DIR/same_name_method.rs:70:13 + --> $DIR/same_name_method.rs:71:13 | LL | fn foo() {} | ^^^^^^^^^^^ | note: existing `foo` defined here - --> $DIR/same_name_method.rs:73:9 + --> $DIR/same_name_method.rs:74:9 | LL | impl T1 for S {} | ^^^^^^^^^^^^^^^^ diff --git a/util/gh-pages/index.html b/util/gh-pages/index.html index 2076d129978..4999cce7511 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index.html @@ -206,6 +206,26 @@ Otherwise, have a great day =^.^= margin: auto 5px; font-family: monospace; } + + details { + border-radius: 4px; + padding: .5em .5em 0; + } + + code { + white-space: pre !important; + } + + summary { + font-weight: bold; + margin: -.5em -.5em 0; + padding: .5em; + display: revert; + } + + details[open] { + padding: .5em; + }