--- /dev/null
+# Used by CI to be able to push:
+/.github/deploy_key
+out
+
+# Compiled files
+*.o
+*.d
+*.so
+*.rlib
+*.dll
+*.pyc
+*.rmeta
+
+# Executables
+*.exe
+
+# Generated by Cargo
+*Cargo.lock
+/target
+/clippy_lints/target
+/clippy_utils/target
+/clippy_workspace_tests/target
+/clippy_dev/target
+/lintcheck/target
+/rustc_tools_util/target
+
+# Generated by dogfood
+/target_recur/
+
+# gh pages docs
+util/gh-pages/lints.json
++**/metadata_collection.json
+
+# rustfmt backups
+*.rs.bk
+
+helper.txt
+*.iml
+.vscode
+.idea
--- /dev/null
- Clippys `Cargo.toml`s and should allow rust-analyzer to understand most of the types that Clippy uses.
+# Contributing to Clippy
+
+Hello fellow Rustacean! Great to see your interest in compiler internals and lints!
+
+**First**: if you're unsure or afraid of _anything_, just ask or submit the issue or pull request anyway. You won't be
+yelled at for giving it your best effort. The worst that can happen is that you'll be politely asked to change
+something. We appreciate any sort of contributions, and don't want a wall of rules to get in the way of that.
+
+Clippy welcomes contributions from everyone. There are many ways to contribute to Clippy and the following document
+explains how you can contribute and how to get started. If you have any questions about contributing or need help with
+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)
+ - [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)
+
+[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy
+[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct
+
+## Getting started
+
+**Note: If this is your first time contributing to Clippy, you should
+first read the [Basics docs](doc/basics.md).**
+
+### 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
+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
+
+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)
+
+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.
+
+There are also some abandoned PRs, marked with [`S-inactive-closed`].
+Pretty often these PRs are nearly completed and just need some extra steps
+(formatting, addressing review comments, ...) to be merged. If you want to
+complete such a PR, please leave a comment in the PR and open a new one based
+on it.
+
+Issues marked [`T-AST`] involve simple matching of the syntax tree structure,
+and are generally easier than [`T-middle`] issues, which involve types
+and resolved paths.
+
+[`T-AST`] issues will generally need you to match against a predefined syntax structure.
+To figure out how this syntax structure is encoded in the AST, it is recommended to run
+`rustc -Z ast-json` on an example of the structure and compare with the [nodes in the AST docs].
+Usually the lint will end up to be a nested series of matches and ifs, [like so][deep-nesting].
+But we can make it nest-less by using [if_chain] macro, [like this][nest-less].
+
+[`E-medium`] issues are generally pretty easy too, though it's recommended you work on an [`good-first-issue`]
+first. Sometimes they are only somewhat involved code wise, but not difficult per-se.
+Note that [`E-medium`] issues may require some knowledge of Clippy internals or some
+debugging to find the actual problem behind the issue.
+
+[`T-middle`] issues can be more involved and require verifying types. The [`ty`] module contains a
+lot of methods that are useful, though one of the most useful would be `expr_ty` (gives the type of
+an AST expression). `match_def_path()` in Clippy's `utils` module can also be useful.
+
+[`good-first-issue`]: https://github.com/rust-lang/rust-clippy/labels/good-first-issue
+[`S-inactive-closed`]: https://github.com/rust-lang/rust-clippy/pulls?q=is%3Aclosed+label%3AS-inactive-closed
+[`T-AST`]: https://github.com/rust-lang/rust-clippy/labels/T-AST
+[`T-middle`]: https://github.com/rust-lang/rust-clippy/labels/T-middle
+[`E-medium`]: https://github.com/rust-lang/rust-clippy/labels/E-medium
+[`ty`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty
+[nodes in the AST docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/
+[deep-nesting]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/mem_forget.rs#L29-L43
+[if_chain]: https://docs.rs/if_chain/*/if_chain
+[nest-less]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/bit_mask.rs#L124-L150
+
+## 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
+Unfortunately, [`IntelliJ Rust`][IntelliJ_rust_homepage] does not (yet?) understand how Clippy uses compiler-internals
+using `extern crate` and it also needs to be able to read the source files of the rustc-compiler which are not
+available via a `rustup` component at the time of writing.
+To work around this, you need to have a copy of the [rustc-repo][rustc_repo] available which can be obtained via
+`git clone https://github.com/rust-lang/rust/`.
+Then you can run a `cargo dev` command to automatically make Clippy use the rustc-repo via path-dependencies
+which `IntelliJ Rust` will be able to understand.
+Run `cargo dev ide_setup --repo-path <repo-path>` where `<repo-path>` is a path to the rustc repo
+you just cloned.
+The command will add path-dependencies pointing towards rustc-crates inside the rustc repo to
++Clippys `Cargo.toml`s and should allow `IntelliJ Rust` to understand most of the types that Clippy uses.
+Just make sure to remove the dependencies again before finally making a pull request!
+
+[rustc_repo]: https://github.com/rust-lang/rust/
+[IntelliJ_rust_homepage]: https://intellij-rust.github.io/
+
+### Rust Analyzer
+As of [#6869][6869], [`rust-analyzer`][ra_homepage] can understand that Clippy uses compiler-internals
+using `extern crate` when `package.metadata.rust-analyzer.rustc_private` is set to `true` in Clippys `Cargo.toml.`
+You will required a `nightly` toolchain with the `rustc-dev` component installed.
+Make sure that in the `rust-analyzer` configuration, you set
+```
+{ "rust-analyzer.rustcSource": "discover" }
+```
+and
+```
+{ "rust-analyzer.updates.channel": "nightly" }
+```
+You should be able to see information on things like `Expr` or `EarlyContext` now if you hover them, also
+a lot more type hints.
+This will work with `rust-analyzer 2021-03-15` shipped in nightly `1.52.0-nightly (107896c32 2021-03-15)` or later.
+
+[ra_homepage]: https://rust-analyzer.github.io/
+[6869]: https://github.com/rust-lang/rust-clippy/pull/6869
+
+## How Clippy works
+
+[`clippy_lints/src/lib.rs`][lint_crate_entry] imports all the different lint modules and registers in the [`LintStore`].
+For example, the [`else_if_without_else`][else_if_without_else] lint is registered like this:
+
+```rust
+// ./clippy_lints/src/lib.rs
+
+// ...
+pub mod else_if_without_else;
+// ...
+
+pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
+ // ...
+ store.register_early_pass(|| box else_if_without_else::ElseIfWithoutElse);
+ // ...
+
+ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
+ // ...
+ LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE),
+ // ...
+ ]);
+}
+```
+
+The [`rustc_lint::LintStore`][`LintStore`] provides two methods to register lints:
+[register_early_pass][reg_early_pass] and [register_late_pass][reg_late_pass]. Both take an object
+that implements an [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass] respectively. This is done in
+every single lint. It's worth noting that the majority of `clippy_lints/src/lib.rs` is autogenerated by `cargo dev
+update_lints`. When you are writing your own lint, you can use that script to save you some time.
+
+```rust
+// ./clippy_lints/src/else_if_without_else.rs
+
+use rustc_lint::{EarlyLintPass, EarlyContext};
+
+// ...
+
+pub struct ElseIfWithoutElse;
+
+// ...
+
+impl EarlyLintPass for ElseIfWithoutElse {
+ // ... the functions needed, to make the lint work
+}
+```
+
+The difference between `EarlyLintPass` and `LateLintPass` is that the methods of the `EarlyLintPass` trait only provide
+AST information. The methods of the `LateLintPass` trait are executed after type checking and contain type information
+via the `LateContext` parameter.
+
+That's why the `else_if_without_else` example uses the `register_early_pass` function. Because the
+[actual lint logic][else_if_without_else] does not depend on any type information.
+
+[lint_crate_entry]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/lib.rs
+[else_if_without_else]: https://github.com/rust-lang/rust-clippy/blob/4253aa7137cb7378acc96133c787e49a345c2b3c/clippy_lints/src/else_if_without_else.rs
+[`LintStore`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html
+[reg_early_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html#method.register_early_pass
+[reg_late_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html#method.register_late_pass
+[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
+ 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
+requests.
+
+However, we are a smaller project with all contributors being volunteers
+currently. Between writing new lints, fixing issues, reviewing pull requests and
+responding to issues there may not always be enough time to stay on top of it
+all.
+
+Our highest priority is fixing [crashes][l-crash] and [bugs][l-bug], for example
+an ICE in a popular crate that many other crates depend on. We don't
+want Clippy to crash on your code and we want it to be as reliable as the
+suggestions from Rust compiler errors.
+
+We have prioritization labels and a sync-blocker label, which are described below.
+- [P-low][p-low]: Requires attention (fix/response/evaluation) by a team member but isn't urgent.
+- [P-medium][p-medium]: Should be addressed by a team member until the next sync.
+- [P-high][p-high]: Should be immediately addressed and will require an out-of-cycle sync or a backport.
+- [L-sync-blocker][l-sync-blocker]: An issue that "blocks" a sync.
+Or rather: before the sync this should be addressed,
+e.g. by removing a lint again, so it doesn't hit beta/stable.
+
+## Bors and Homu
+
+We use a bot powered by [Homu][homu] to help automate testing and landing of pull
+requests in Clippy. The bot's username is @bors.
+
+You can find the Clippy bors queue [here][homu_queue].
+
+If you have @bors permissions, you can find an overview of the available
+commands [here][homu_instructions].
+
+[triage]: https://forge.rust-lang.org/release/triage-procedure.html
+[l-crash]: https://github.com/rust-lang/rust-clippy/labels/L-crash
+[l-bug]: https://github.com/rust-lang/rust-clippy/labels/L-bug
+[p-low]: https://github.com/rust-lang/rust-clippy/labels/P-low
+[p-medium]: https://github.com/rust-lang/rust-clippy/labels/P-medium
+[p-high]: https://github.com/rust-lang/rust-clippy/labels/P-high
+[l-sync-blocker]: https://github.com/rust-lang/rust-clippy/labels/L-sync-blocker
+[homu]: https://github.com/rust-lang/homu
+[homu_instructions]: https://bors.rust-lang.org/
+[homu_queue]: https://bors.rust-lang.org/queue/clippy
+
+## Contributions
+
+Contributions to Clippy should be made in the form of GitHub pull requests. Each pull request will
+be reviewed by a core contributor (someone with permission to land patches) and either landed in the
+main tree or given feedback for changes that would be required.
+
+All code in this repository is under the [Apache-2.0] or the [MIT] license.
+
+<!-- adapted from https://github.com/servo/servo/blob/master/CONTRIBUTING.md -->
+
+[Apache-2.0]: https://www.apache.org/licenses/LICENSE-2.0
+[MIT]: https://opensource.org/licenses/MIT
--- /dev/null
- version = "0.1.53"
+[package]
+name = "clippy"
++version = "0.1.54"
+authors = ["The Rust Clippy Developers"]
+description = "A bunch of helpful lints to avoid common pitfalls in Rust"
+repository = "https://github.com/rust-lang/rust-clippy"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+keywords = ["clippy", "lint", "plugin"]
+categories = ["development-tools", "development-tools::cargo-plugins"]
+build = "build.rs"
+edition = "2018"
+publish = false
+
+[[bin]]
+name = "cargo-clippy"
+test = false
+path = "src/main.rs"
+
+[[bin]]
+name = "clippy-driver"
+path = "src/driver.rs"
+
+[dependencies]
+# begin automatic update
+clippy_lints = { version = "0.1.50", path = "clippy_lints" }
+# end automatic update
+semver = "0.11"
+rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" }
+tempfile = { version = "3.1.0", optional = true }
+
+[dev-dependencies]
+cargo_metadata = "0.12"
+compiletest_rs = { version = "0.6.0", features = ["tmp"] }
+tester = "0.9"
+clippy-mini-macro-test = { version = "0.2", path = "mini-macro" }
+serde = { version = "1.0", features = ["derive"] }
+derive-new = "0.5"
+regex = "1.4"
+quote = "1"
+syn = { version = "1", features = ["full"] }
+
+# A noop dependency that changes in the Rust repository, it's a bit of a hack.
+# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
+# for more information.
+rustc-workspace-hack = "1.0.0"
+
+[build-dependencies]
+rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" }
+
+[features]
+deny-warnings = []
+integration = ["tempfile"]
+internal-lints = ["clippy_lints/internal-lints"]
++metadata-collector-lint = ["internal-lints", "clippy_lints/metadata-collector-lint"]
+
+[package.metadata.rust-analyzer]
+# This package uses #[feature(rustc_private)]
+rustc_private = true
--- /dev/null
- version = "0.1.53"
+[package]
+name = "clippy_lints"
+# begin automatic update
++version = "0.1.54"
+# end automatic update
+authors = ["The Rust Clippy Developers"]
+description = "A bunch of helpful lints to avoid common pitfalls in Rust"
+repository = "https://github.com/rust-lang/rust-clippy"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+keywords = ["clippy", "lint", "plugin"]
+edition = "2018"
+
+[dependencies]
+cargo_metadata = "0.12"
+clippy_utils = { path = "../clippy_utils" }
+if_chain = "1.0.0"
+itertools = "0.9"
+pulldown-cmark = { version = "0.8", default-features = false }
+quine-mc_cluskey = "0.2.2"
+regex-syntax = "0.6"
+serde = { version = "1.0", features = ["derive"] }
++serde_json = { version = "1.0", optional = true }
+toml = "0.5.3"
+unicode-normalization = "0.1"
+semver = "0.11"
+rustc-semver = "1.1.0"
+# NOTE: cargo requires serde feat in its url dep
+# see <https://github.com/rust-lang/rust/pull/63587#issuecomment-522343864>
+url = { version = "2.1.0", features = ["serde"] }
+
+[features]
+deny-warnings = []
+# build clippy with internal lints enabled, off by default
+internal-lints = ["clippy_utils/internal-lints"]
++metadata-collector-lint = ["serde_json", "clippy_utils/metadata-collector-lint"]
+
+[package.metadata.rust-analyzer]
+# This crate uses #[feature(rustc_private)]
+rustc_private = true
--- /dev/null
- use clippy_utils::{get_trait_def_id, if_sequence, is_else_clause, paths, SpanlessEq};
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::implements_trait;
++use clippy_utils::{get_trait_def_id, if_sequence, in_constant, is_else_clause, paths, SpanlessEq};
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks comparison chains written with `if` that can be
+ /// rewritten with `match` and `cmp`.
+ ///
+ /// **Why is this bad?** `if` is not guaranteed to be exhaustive and conditionals can get
+ /// repetitive
+ ///
+ /// **Known problems:** The match statement may be slower due to the compiler
+ /// not inlining the call to cmp. See issue [#5354](https://github.com/rust-lang/rust-clippy/issues/5354)
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// # fn a() {}
+ /// # fn b() {}
+ /// # fn c() {}
+ /// fn f(x: u8, y: u8) {
+ /// if x > y {
+ /// a()
+ /// } else if x < y {
+ /// b()
+ /// } else {
+ /// c()
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust,ignore
+ /// use std::cmp::Ordering;
+ /// # fn a() {}
+ /// # fn b() {}
+ /// # fn c() {}
+ /// fn f(x: u8, y: u8) {
+ /// match x.cmp(&y) {
+ /// Ordering::Greater => a(),
+ /// Ordering::Less => b(),
+ /// Ordering::Equal => c()
+ /// }
+ /// }
+ /// ```
+ pub COMPARISON_CHAIN,
+ style,
+ "`if`s that can be rewritten with `match` and `cmp`"
+}
+
+declare_lint_pass!(ComparisonChain => [COMPARISON_CHAIN]);
+
+impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ // We only care about the top-most `if` in the chain
+ if is_else_clause(cx.tcx, expr) {
+ return;
+ }
+
++ if in_constant(cx, expr.hir_id) {
++ return;
++ }
++
+ // Check that there exists at least one explicit else condition
+ let (conds, _) = if_sequence(expr);
+ if conds.len() < 2 {
+ return;
+ }
+
+ for cond in conds.windows(2) {
+ if let (&ExprKind::Binary(ref kind1, lhs1, rhs1), &ExprKind::Binary(ref kind2, lhs2, rhs2)) =
+ (&cond[0].kind, &cond[1].kind)
+ {
+ if !kind_is_cmp(kind1.node) || !kind_is_cmp(kind2.node) {
+ return;
+ }
+
+ // Check that both sets of operands are equal
+ let mut spanless_eq = SpanlessEq::new(cx);
+ let same_fixed_operands = spanless_eq.eq_expr(lhs1, lhs2) && spanless_eq.eq_expr(rhs1, rhs2);
+ let same_transposed_operands = spanless_eq.eq_expr(lhs1, rhs2) && spanless_eq.eq_expr(rhs1, lhs2);
+
+ if !same_fixed_operands && !same_transposed_operands {
+ return;
+ }
+
+ // Check that if the operation is the same, either it's not `==` or the operands are transposed
+ if kind1.node == kind2.node {
+ if kind1.node == BinOpKind::Eq {
+ return;
+ }
+ if !same_transposed_operands {
+ return;
+ }
+ }
+
+ // Check that the type being compared implements `core::cmp::Ord`
+ let ty = cx.typeck_results().expr_ty(lhs1);
+ let is_ord = get_trait_def_id(cx, &paths::ORD).map_or(false, |id| implements_trait(cx, ty, id, &[]));
+
+ if !is_ord {
+ return;
+ }
+ } else {
+ // We only care about comparison chains
+ return;
+ }
+ }
+ span_lint_and_help(
+ cx,
+ COMPARISON_CHAIN,
+ expr.span,
+ "`if` chain can be rewritten with `match`",
+ None,
+ "consider rewriting the `if` chain to use `cmp` and `match`",
+ )
+ }
+}
+
+fn kind_is_cmp(kind: BinOpKind) -> bool {
+ matches!(kind, BinOpKind::Lt | BinOpKind::Gt | BinOpKind::Eq)
+}
--- /dev/null
- use clippy_utils::{any_parent_is_automatically_derived, contains_name, match_def_path, paths};
+use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
+use clippy_utils::source::snippet_with_macro_callsite;
++use clippy_utils::{any_parent_is_automatically_derived, contains_name, in_macro, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir::def::Res;
+use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// **What it does:** 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`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// // Bad
+ /// let s: String = Default::default();
+ ///
+ /// // Good
+ /// let s = String::default();
+ /// ```
+ pub DEFAULT_TRAIT_ACCESS,
+ pedantic,
+ "checks for literal calls to `Default::default()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for immediate reassignment of fields initialized
+ /// with Default::default().
+ ///
+ /// **Why is this bad?**It's more idiomatic to use the [functional update syntax](https://doc.rust-lang.org/reference/expressions/struct-expr.html#functional-update-syntax).
+ ///
+ /// **Known problems:** 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)]
+ /// # struct A { i: i32 }
+ /// let a = A {
+ /// i: 42,
+ /// .. Default::default()
+ /// };
+ /// ```
+ pub FIELD_REASSIGN_WITH_DEFAULT,
+ style,
+ "binding initialized with Default should have its fields set in the initializer"
+}
+
+#[derive(Default)]
+pub struct Default {
+ // Spans linted by `field_reassign_with_default`.
+ reassigned_linted: FxHashSet<Span>,
+}
+
+impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]);
+
+impl LateLintPass<'_> for Default {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
++ if !in_macro(expr.span);
+ // Avoid cases already linted by `field_reassign_with_default`
+ if !self.reassigned_linted.contains(&expr.span);
+ if let ExprKind::Call(path, ..) = expr.kind;
+ if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
+ if let ExprKind::Path(ref qpath) = path.kind;
+ if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
+ if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD);
+ // Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
+ if let QPath::Resolved(None, _path) = qpath;
+ let expr_ty = cx.typeck_results().expr_ty(expr);
+ if let ty::Adt(def, ..) = expr_ty.kind();
+ then {
+ // TODO: Work out a way to put "whatever the imported way of referencing
+ // this type in this file" rather than a fully-qualified type.
+ let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did));
+ span_lint_and_sugg(
+ cx,
+ DEFAULT_TRAIT_ACCESS,
+ expr.span,
+ &format!("calling `{}` is more clear than this expression", replacement),
+ "try",
+ replacement,
+ Applicability::Unspecified, // First resolve the TODO above
+ );
+ }
+ }
+ }
+
+ #[allow(clippy::too_many_lines)]
+ fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
+ // start from the `let mut _ = _::default();` and look at all the following
+ // statements, see if they re-assign the fields of the binding
+ let stmts_head = match block.stmts {
+ // Skip the last statement since there cannot possibly be any following statements that re-assign fields.
+ [head @ .., _] if !head.is_empty() => head,
+ _ => return,
+ };
+ for (stmt_idx, stmt) in stmts_head.iter().enumerate() {
+ // find all binding statements like `let mut _ = T::default()` where `T::default()` is the
+ // `default` method of the `Default` trait, and store statement index in current block being
+ // checked and the name of the bound variable
+ let (local, variant, binding_name, binding_type, span) = if_chain! {
+ // only take `let ...` statements
+ if let StmtKind::Local(local) = stmt.kind;
+ if let Some(expr) = local.init;
+ if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
+ if !in_external_macro(cx.tcx.sess, expr.span);
+ // only take bindings to identifiers
+ if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind;
+ // only when assigning `... = Default::default()`
+ if is_expr_default(expr, cx);
+ let binding_type = cx.typeck_results().node_type(binding_id);
+ if let Some(adt) = binding_type.ty_adt_def();
+ if adt.is_struct();
+ let variant = adt.non_enum_variant();
+ if adt.did.is_local() || !variant.is_field_list_non_exhaustive();
+ let module_did = cx.tcx.parent_module(stmt.hir_id).to_def_id();
+ if variant
+ .fields
+ .iter()
+ .all(|field| field.vis.is_accessible_from(module_did, cx.tcx));
+ then {
+ (local, variant, ident.name, binding_type, expr.span)
+ } else {
+ continue;
+ }
+ };
+
+ // find all "later statement"'s where the fields of the binding set as
+ // Default::default() get reassigned, unless the reassignment refers to the original binding
+ let mut first_assign = None;
+ let mut assigned_fields = Vec::new();
+ let mut cancel_lint = false;
+ for consecutive_statement in &block.stmts[stmt_idx + 1..] {
+ // find out if and which field was set by this `consecutive_statement`
+ if let Some((field_ident, assign_rhs)) = field_reassigned_by_stmt(consecutive_statement, binding_name) {
+ // interrupt and cancel lint if assign_rhs references the original binding
+ if contains_name(binding_name, assign_rhs) {
+ cancel_lint = true;
+ break;
+ }
+
+ // if the field was previously assigned, replace the assignment, otherwise insert the assignment
+ if let Some(prev) = assigned_fields
+ .iter_mut()
+ .find(|(field_name, _)| field_name == &field_ident.name)
+ {
+ *prev = (field_ident.name, assign_rhs);
+ } else {
+ assigned_fields.push((field_ident.name, assign_rhs));
+ }
+
+ // also set first instance of error for help message
+ if first_assign.is_none() {
+ first_assign = Some(consecutive_statement);
+ }
+ }
+ // interrupt if no field was assigned, since we only want to look at consecutive statements
+ else {
+ break;
+ }
+ }
+
+ // if there are incorrectly assigned fields, do a span_lint_and_note to suggest
+ // construction using `Ty { fields, ..Default::default() }`
+ if !assigned_fields.is_empty() && !cancel_lint {
+ // if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
+ let ext_with_default = !variant
+ .fields
+ .iter()
+ .all(|field| assigned_fields.iter().any(|(a, _)| a == &field.ident.name));
+
+ let field_list = assigned_fields
+ .into_iter()
+ .map(|(field, rhs)| {
+ // extract and store the assigned value for help message
+ let value_snippet = snippet_with_macro_callsite(cx, rhs.span, "..");
+ format!("{}: {}", field, value_snippet)
+ })
+ .collect::<Vec<String>>()
+ .join(", ");
+
+ // give correct suggestion if generics are involved (see #6944)
+ let binding_type = if_chain! {
+ if let ty::Adt(adt_def, substs) = binding_type.kind();
+ if !substs.is_empty();
+ then {
+ let adt_def_ty_name = cx.tcx.item_name(adt_def.did);
+ let generic_args = substs.iter().collect::<Vec<_>>();
+ let tys_str = generic_args
+ .iter()
+ .map(ToString::to_string)
+ .collect::<Vec<_>>()
+ .join(", ");
+ format!("{}::<{}>", adt_def_ty_name, &tys_str)
+ } else {
+ binding_type.to_string()
+ }
+ };
+
+ let sugg = if ext_with_default {
+ if field_list.is_empty() {
+ format!("{}::default()", binding_type)
+ } else {
+ format!("{} {{ {}, ..Default::default() }}", binding_type, field_list)
+ }
+ } else {
+ format!("{} {{ {} }}", binding_type, field_list)
+ };
+
+ // span lint once per statement that binds default
+ span_lint_and_note(
+ cx,
+ FIELD_REASSIGN_WITH_DEFAULT,
+ first_assign.unwrap().span,
+ "field assignment outside of initializer for an instance created with Default::default()",
+ Some(local.span),
+ &format!(
+ "consider initializing the variable with `{}` and removing relevant reassignments",
+ sugg
+ ),
+ );
+ self.reassigned_linted.insert(span);
+ }
+ }
+ }
+}
+
+/// Checks if the given expression is the `default` method belonging to the `Default` trait.
+fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
+ if_chain! {
+ if let ExprKind::Call(fn_expr, _) = &expr.kind;
+ if let ExprKind::Path(qpath) = &fn_expr.kind;
+ if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
+ then {
+ // right hand side of assignment is `Default::default`
+ match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD)
+ } else {
+ false
+ }
+ }
+}
+
+/// Returns the reassigned field and the assigning expression (right-hand side of assign).
+fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
+ if_chain! {
+ // only take assignments
+ if let StmtKind::Semi(later_expr) = this.kind;
+ if let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind;
+ // only take assignments to fields where the left-hand side field is a field of
+ // the same binding as the previous statement
+ if let ExprKind::Field(binding, field_ident) = assign_lhs.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
+ if let Some(second_binding_name) = path.segments.last();
+ if second_binding_name.ident.name == binding_name;
+ then {
+ Some((field_ident, assign_rhs))
+ } else {
+ None
+ }
+ }
+}
--- /dev/null
- match expr.kind {
- ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) => {
- if let Some(var) = path_to_local(lhs) {
- let mut visitor = ReadVisitor {
- cx,
- var,
- write_expr: expr,
- last_expr: expr,
- };
- check_for_unsequenced_reads(&mut visitor);
- }
- },
- _ => {},
- }
+use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
+use clippy_utils::{get_parent_expr, path_to_local, path_to_local_id};
++use if_chain::if_chain;
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Guard, HirId, Local, Node, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for a read and a write to the same variable where
+ /// whether the read occurs before or after the write depends on the evaluation
+ /// order of sub-expressions.
+ ///
+ /// **Why is this bad?** It is often confusing to read. In addition, the
+ /// sub-expression evaluation order for Rust is not well documented.
+ ///
+ /// **Known problems:** Code which intentionally depends on the evaluation
+ /// order, or which is correct for any evaluation order.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let mut x = 0;
+ ///
+ /// // Bad
+ /// let a = {
+ /// x = 1;
+ /// 1
+ /// } + x;
+ /// // Unclear whether a is 1 or 2.
+ ///
+ /// // Good
+ /// let tmp = {
+ /// x = 1;
+ /// 1
+ /// };
+ /// let a = tmp + x;
+ /// ```
+ pub EVAL_ORDER_DEPENDENCE,
+ complexity,
+ "whether a variable read occurs before a write depends on sub-expression evaluation order"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for diverging calls that are not match arms or
+ /// statements.
+ ///
+ /// **Why is this bad?** It is often confusing to read. In addition, the
+ /// sub-expression evaluation order for Rust is not well documented.
+ ///
+ /// **Known problems:** Someone might want to use `some_bool || panic!()` as a
+ /// shorthand.
+ ///
+ /// **Example:**
+ /// ```rust,no_run
+ /// # fn b() -> bool { true }
+ /// # fn c() -> bool { true }
+ /// let a = b() || panic!() || c();
+ /// // `c()` is dead, `panic!()` is only called if `b()` returns `false`
+ /// let x = (a, b, c, panic!());
+ /// // can simply be replaced by `panic!()`
+ /// ```
+ pub DIVERGING_SUB_EXPRESSION,
+ complexity,
+ "whether an expression contains a diverging sub expression"
+}
+
+declare_lint_pass!(EvalOrderDependence => [EVAL_ORDER_DEPENDENCE, DIVERGING_SUB_EXPRESSION]);
+
+impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // Find a write to a local variable.
- "unsequenced read of a variable",
++ let var = if_chain! {
++ if let ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) = expr.kind;
++ if let Some(var) = path_to_local(lhs);
++ if expr.span.desugaring_kind().is_none();
++ then { var } else { return; }
++ };
++ let mut visitor = ReadVisitor {
++ cx,
++ var,
++ write_expr: expr,
++ last_expr: expr,
++ };
++ check_for_unsequenced_reads(&mut visitor);
+ }
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ match stmt.kind {
+ StmtKind::Local(local) => {
+ if let Local { init: Some(e), .. } = local {
+ DivergenceVisitor { cx }.visit_expr(e);
+ }
+ },
+ StmtKind::Expr(e) | StmtKind::Semi(e) => DivergenceVisitor { cx }.maybe_walk_expr(e),
+ StmtKind::Item(..) => {},
+ }
+ }
+}
+
+struct DivergenceVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> {
+ fn maybe_walk_expr(&mut self, e: &'tcx Expr<'_>) {
+ match e.kind {
+ ExprKind::Closure(..) => {},
+ ExprKind::Match(e, arms, _) => {
+ self.visit_expr(e);
+ for arm in arms {
+ if let Some(Guard::If(if_expr)) = arm.guard {
+ self.visit_expr(if_expr)
+ }
+ // make sure top level arm expressions aren't linted
+ self.maybe_walk_expr(&*arm.body);
+ }
+ },
+ _ => walk_expr(self, e),
+ }
+ }
+ fn report_diverging_sub_expr(&mut self, e: &Expr<'_>) {
+ span_lint(self.cx, DIVERGING_SUB_EXPRESSION, e.span, "sub-expression diverges");
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for DivergenceVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ match e.kind {
+ ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => self.report_diverging_sub_expr(e),
+ ExprKind::Call(func, _) => {
+ let typ = self.cx.typeck_results().expr_ty(func);
+ match typ.kind() {
+ ty::FnDef(..) | ty::FnPtr(_) => {
+ let sig = typ.fn_sig(self.cx.tcx);
+ if let ty::Never = self.cx.tcx.erase_late_bound_regions(sig).output().kind() {
+ self.report_diverging_sub_expr(e);
+ }
+ },
+ _ => {},
+ }
+ },
+ ExprKind::MethodCall(..) => {
+ let borrowed_table = self.cx.typeck_results();
+ if borrowed_table.expr_ty(e).is_never() {
+ self.report_diverging_sub_expr(e);
+ }
+ },
+ _ => {
+ // do not lint expressions referencing objects of type `!`, as that required a
+ // diverging expression
+ // to begin with
+ },
+ }
+ self.maybe_walk_expr(e);
+ }
+ fn visit_block(&mut self, _: &'tcx Block<'_>) {
+ // don't continue over blocks, LateLintPass already does that
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// Walks up the AST from the given write expression (`vis.write_expr`) looking
+/// for reads to the same variable that are unsequenced relative to the write.
+///
+/// This means reads for which there is a common ancestor between the read and
+/// the write such that
+///
+/// * evaluating the ancestor necessarily evaluates both the read and the write (for example, `&x`
+/// and `|| x = 1` don't necessarily evaluate `x`), and
+///
+/// * which one is evaluated first depends on the order of sub-expression evaluation. Blocks, `if`s,
+/// loops, `match`es, and the short-circuiting logical operators are considered to have a defined
+/// evaluation order.
+///
+/// When such a read is found, the lint is triggered.
+fn check_for_unsequenced_reads(vis: &mut ReadVisitor<'_, '_>) {
+ let map = &vis.cx.tcx.hir();
+ let mut cur_id = vis.write_expr.hir_id;
+ loop {
+ let parent_id = map.get_parent_node(cur_id);
+ if parent_id == cur_id {
+ break;
+ }
+ let parent_node = match map.find(parent_id) {
+ Some(parent) => parent,
+ None => break,
+ };
+
+ let stop_early = match parent_node {
+ Node::Expr(expr) => check_expr(vis, expr),
+ Node::Stmt(stmt) => check_stmt(vis, stmt),
+ Node::Item(_) => {
+ // We reached the top of the function, stop.
+ break;
+ },
+ _ => StopEarly::KeepGoing,
+ };
+ match stop_early {
+ StopEarly::Stop => break,
+ StopEarly::KeepGoing => {},
+ }
+
+ cur_id = parent_id;
+ }
+}
+
+/// Whether to stop early for the loop in `check_for_unsequenced_reads`. (If
+/// `check_expr` weren't an independent function, this would be unnecessary and
+/// we could just use `break`).
+enum StopEarly {
+ KeepGoing,
+ Stop,
+}
+
+fn check_expr<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, expr: &'tcx Expr<'_>) -> StopEarly {
+ if expr.hir_id == vis.last_expr.hir_id {
+ return StopEarly::KeepGoing;
+ }
+
+ match expr.kind {
+ ExprKind::Array(_)
+ | ExprKind::Tup(_)
+ | ExprKind::MethodCall(..)
+ | ExprKind::Call(_, _)
+ | ExprKind::Assign(..)
+ | ExprKind::Index(_, _)
+ | ExprKind::Repeat(_, _)
+ | ExprKind::Struct(_, _, _) => {
+ walk_expr(vis, expr);
+ },
+ ExprKind::Binary(op, _, _) | ExprKind::AssignOp(op, _, _) => {
+ if op.node == BinOpKind::And || op.node == BinOpKind::Or {
+ // x && y and x || y always evaluate x first, so these are
+ // strictly sequenced.
+ } else {
+ walk_expr(vis, expr);
+ }
+ },
+ ExprKind::Closure(_, _, _, _, _) => {
+ // Either
+ //
+ // * `var` is defined in the closure body, in which case we've reached the top of the enclosing
+ // function and can stop, or
+ //
+ // * `var` is captured by the closure, in which case, because evaluating a closure does not evaluate
+ // its body, we don't necessarily have a write, so we need to stop to avoid generating false
+ // positives.
+ //
+ // This is also the only place we need to stop early (grrr).
+ return StopEarly::Stop;
+ },
+ // All other expressions either have only one child or strictly
+ // sequence the evaluation order of their sub-expressions.
+ _ => {},
+ }
+
+ vis.last_expr = expr;
+
+ StopEarly::KeepGoing
+}
+
+fn check_stmt<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, stmt: &'tcx Stmt<'_>) -> StopEarly {
+ match stmt.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => check_expr(vis, expr),
+ // If the declaration is of a local variable, check its initializer
+ // expression if it has one. Otherwise, keep going.
+ StmtKind::Local(local) => local
+ .init
+ .as_ref()
+ .map_or(StopEarly::KeepGoing, |expr| check_expr(vis, expr)),
+ StmtKind::Item(..) => StopEarly::KeepGoing,
+ }
+}
+
+/// A visitor that looks for reads from a variable.
+struct ReadVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ /// The ID of the variable we're looking for.
+ var: HirId,
+ /// The expressions where the write to the variable occurred (for reporting
+ /// in the lint).
+ write_expr: &'tcx Expr<'tcx>,
+ /// The last (highest in the AST) expression we've checked, so we know not
+ /// to recheck it.
+ last_expr: &'tcx Expr<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for ReadVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if expr.hir_id == self.last_expr.hir_id {
+ return;
+ }
+
+ if path_to_local_id(expr, self.var) {
+ // Check that this is a read, not a write.
+ if !is_in_assignment_position(self.cx, expr) {
+ span_lint_and_note(
+ self.cx,
+ EVAL_ORDER_DEPENDENCE,
+ expr.span,
++ &format!("unsequenced read of `{}`", self.cx.tcx.hir().name(self.var)),
+ Some(self.write_expr.span),
+ "whether read occurs before this write depends on evaluation order",
+ );
+ }
+ }
+ match expr.kind {
+ // We're about to descend a closure. Since we don't know when (or
+ // if) the closure will be evaluated, any reads in it might not
+ // occur here (or ever). Like above, bail to avoid false positives.
+ ExprKind::Closure(_, _, _, _, _) |
+
+ // We want to avoid a false positive when a variable name occurs
+ // only to have its address taken, so we stop here. Technically,
+ // this misses some weird cases, eg.
+ //
+ // ```rust
+ // let mut x = 0;
+ // let a = foo(&{x = 1; x}, x);
+ // ```
+ //
+ // TODO: fix this
+ ExprKind::AddrOf(_, _, _) => {
+ return;
+ }
+ _ => {}
+ }
+
+ walk_expr(self, expr);
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// Returns `true` if `expr` is the LHS of an assignment, like `expr = ...`.
+fn is_in_assignment_position(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ if let Some(parent) = get_parent_expr(cx, expr) {
+ if let ExprKind::Assign(lhs, ..) = parent.kind {
+ return lhs.hir_id == expr.hir_id;
+ }
+ }
+ false
+}
--- /dev/null
- use clippy_utils::diagnostics::span_lint_and_then;
- use clippy_utils::match_panic_def_id;
- use clippy_utils::source::snippet_opt;
- use if_chain::if_chain;
++use clippy_utils::{
++ diagnostics::span_lint_and_sugg,
++ get_async_fn_body, is_async_fn,
++ source::{snippet_with_applicability, snippet_with_context, walk_span_to_context},
++ visitors::visit_break_exprs,
++};
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::FnKind;
- use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, MatchSource, StmtKind};
- use rustc_lint::{LateContext, LateLintPass};
++use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId};
++use rustc_lint::{LateContext, LateLintPass, LintContext};
++use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
- use rustc_span::source_map::Span;
++use rustc_span::{Span, SyntaxContext};
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for missing return statements at the end of a block.
+ ///
+ /// **Why is this bad?** Actually omitting the return keyword is idiomatic Rust code. Programmers
+ /// coming from other languages might prefer the expressiveness of `return`. It's possible to miss
+ /// the last returning statement because the only difference is a missing `;`. Especially in bigger
+ /// code with multiple return paths having a `return` keyword makes it easier to find the
+ /// corresponding statements.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// x
+ /// }
+ /// ```
+ /// add return
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// return x;
+ /// }
+ /// ```
+ pub IMPLICIT_RETURN,
+ restriction,
+ "use a return statement like `return expr` instead of an expression"
+}
+
+declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]);
+
- static LINT_BREAK: &str = "change `break` to `return` as shown";
- static LINT_RETURN: &str = "add `return` as shown";
-
- fn lint(cx: &LateContext<'_>, outer_span: Span, inner_span: Span, msg: &str) {
- let outer_span = outer_span.source_callsite();
- let inner_span = inner_span.source_callsite();
-
- span_lint_and_then(cx, IMPLICIT_RETURN, outer_span, "missing `return` statement", |diag| {
- if let Some(snippet) = snippet_opt(cx, inner_span) {
- diag.span_suggestion(
- outer_span,
- msg,
- format!("return {}", snippet),
- Applicability::MachineApplicable,
- );
- }
- });
++fn lint_return(cx: &LateContext<'_>, span: Span) {
++ let mut app = Applicability::MachineApplicable;
++ let snip = snippet_with_applicability(cx, span, "..", &mut app);
++ span_lint_and_sugg(
++ cx,
++ IMPLICIT_RETURN,
++ span,
++ "missing `return` statement",
++ "add `return` as shown",
++ format!("return {}", snip),
++ app,
++ );
++}
++
++fn lint_break(cx: &LateContext<'_>, break_span: Span, expr_span: Span) {
++ let mut app = Applicability::MachineApplicable;
++ let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0;
++ span_lint_and_sugg(
++ cx,
++ IMPLICIT_RETURN,
++ break_span,
++ "missing `return` statement",
++ "change `break` to `return` as shown",
++ format!("return {}", snip),
++ app,
++ )
++}
++
++#[derive(Clone, Copy, PartialEq, Eq)]
++enum LintLocation {
++ /// The lint was applied to a parent expression.
++ Parent,
++ /// The lint was applied to this expression, a child, or not applied.
++ Inner,
++}
++impl LintLocation {
++ fn still_parent(self, b: bool) -> Self {
++ if b { self } else { Self::Inner }
++ }
++
++ fn is_parent(self) -> bool {
++ self == Self::Parent
++ }
++}
++
++// Gets the call site if the span is in a child context. Otherwise returns `None`.
++fn get_call_site(span: Span, ctxt: SyntaxContext) -> Option<Span> {
++ (span.ctxt() != ctxt).then(|| walk_span_to_context(span, ctxt).unwrap_or(span))
+}
+
- fn expr_match(cx: &LateContext<'_>, expr: &Expr<'_>) {
++fn lint_implicit_returns(
++ cx: &LateContext<'tcx>,
++ expr: &'tcx Expr<'_>,
++ // The context of the function body.
++ ctxt: SyntaxContext,
++ // Whether the expression is from a macro expansion.
++ call_site_span: Option<Span>,
++) -> LintLocation {
+ match expr.kind {
- // loops could be using `break` instead of `return`
- ExprKind::Block(block, ..) | ExprKind::Loop(block, ..) => {
- if let Some(expr) = &block.expr {
- expr_match(cx, expr);
- }
- // only needed in the case of `break` with `;` at the end
- else if let Some(stmt) = block.stmts.last() {
- if_chain! {
- if let StmtKind::Semi(expr, ..) = &stmt.kind;
- // make sure it's a break, otherwise we want to skip
- if let ExprKind::Break(.., Some(break_expr)) = &expr.kind;
- then {
- lint(cx, expr.span, break_expr.span, LINT_BREAK);
- }
- }
- }
- },
- // use `return` instead of `break`
- ExprKind::Break(.., break_expr) => {
- if let Some(break_expr) = break_expr {
- lint(cx, expr.span, break_expr.span, LINT_BREAK);
++ ExprKind::Block(
++ Block {
++ expr: Some(block_expr), ..
++ },
++ _,
++ ) => lint_implicit_returns(
++ cx,
++ block_expr,
++ ctxt,
++ call_site_span.or_else(|| get_call_site(block_expr.span, ctxt)),
++ )
++ .still_parent(call_site_span.is_some()),
++
++ ExprKind::If(_, then_expr, Some(else_expr)) => {
++ // Both `then_expr` or `else_expr` are required to be blocks in the same context as the `if`. Don't
++ // bother checking.
++ let res = lint_implicit_returns(cx, then_expr, ctxt, call_site_span).still_parent(call_site_span.is_some());
++ if res.is_parent() {
++ // The return was added as a parent of this if expression.
++ return res;
+ }
++ lint_implicit_returns(cx, else_expr, ctxt, call_site_span).still_parent(call_site_span.is_some())
+ },
- ExprKind::If(.., if_expr, else_expr) => {
- expr_match(cx, if_expr);
+
- if let Some(else_expr) = else_expr {
- expr_match(cx, else_expr);
++ ExprKind::Match(_, arms, _) => {
++ for arm in arms {
++ let res = lint_implicit_returns(
++ cx,
++ arm.body,
++ ctxt,
++ call_site_span.or_else(|| get_call_site(arm.body.span, ctxt)),
++ )
++ .still_parent(call_site_span.is_some());
++ if res.is_parent() {
++ // The return was added as a parent of this match expression.
++ return res;
++ }
+ }
++ LintLocation::Inner
+ },
- ExprKind::Match(.., arms, source) => {
- let check_all_arms = match source {
- MatchSource::IfLetDesugar {
- contains_else_clause: has_else,
- } => has_else,
- _ => true,
- };
-
- if check_all_arms {
- for arm in arms {
- expr_match(cx, arm.body);
++
++ ExprKind::Loop(block, ..) => {
++ let mut add_return = false;
++ visit_break_exprs(block, |break_expr, dest, sub_expr| {
++ if dest.target_id.ok() == Some(expr.hir_id) {
++ if call_site_span.is_none() && break_expr.span.ctxt() == ctxt {
++ lint_break(cx, break_expr.span, sub_expr.unwrap().span);
++ } else {
++ // the break expression is from a macro call, add a return to the loop
++ add_return = true;
++ }
++ }
++ });
++ if add_return {
++ #[allow(clippy::option_if_let_else)]
++ if let Some(span) = call_site_span {
++ lint_return(cx, span);
++ LintLocation::Parent
++ } else {
++ lint_return(cx, expr.span);
++ LintLocation::Inner
+ }
+ } else {
- expr_match(cx, arms.first().expect("`if let` doesn't have a single arm").body);
++ LintLocation::Inner
+ }
+ },
- // skip if it already has a return statement
- ExprKind::Ret(..) => (),
- // make sure it's not a call that panics
- ExprKind::Call(expr, ..) => {
- if_chain! {
- if let ExprKind::Path(qpath) = &expr.kind;
- if let Some(path_def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id();
- if match_panic_def_id(cx, path_def_id);
- then { }
- else {
- lint(cx, expr.span, expr.span, LINT_RETURN)
- }
++
++ // If expressions without an else clause, and blocks without a final expression can only be the final expression
++ // if they are divergent, or return the unit type.
++ ExprKind::If(_, _, None) | ExprKind::Block(Block { expr: None, .. }, _) | ExprKind::Ret(_) => {
++ LintLocation::Inner
++ },
++
++ // Any divergent expression doesn't need a return statement.
++ ExprKind::MethodCall(..)
++ | ExprKind::Call(..)
++ | ExprKind::Binary(..)
++ | ExprKind::Unary(..)
++ | ExprKind::Index(..)
++ if cx.typeck_results().expr_ty(expr).is_never() =>
++ {
++ LintLocation::Inner
++ },
++
++ _ =>
++ {
++ #[allow(clippy::option_if_let_else)]
++ if let Some(span) = call_site_span {
++ lint_return(cx, span);
++ LintLocation::Parent
++ } else {
++ lint_return(cx, expr.span);
++ LintLocation::Inner
+ }
+ },
- // everything else is missing `return`
- _ => lint(cx, expr.span, expr.span, LINT_RETURN),
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ImplicitReturn {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
- _: FnKind<'tcx>,
- _: &'tcx FnDecl<'_>,
++ kind: FnKind<'tcx>,
++ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ _: HirId,
+ ) {
- if span.from_expansion() {
++ if (!matches!(kind, FnKind::Closure) && matches!(decl.output, FnRetTy::DefaultReturn(_)))
++ || span.ctxt() != body.value.span.ctxt()
++ || in_external_macro(cx.sess(), span)
++ {
+ return;
+ }
- let body = cx.tcx.hir().body(body.id());
- if cx.typeck_results().expr_ty(&body.value).is_unit() {
++
++ let res_ty = cx.typeck_results().expr_ty(&body.value);
++ if res_ty.is_unit() || res_ty.is_never() {
+ return;
+ }
- expr_match(cx, &body.value);
++
++ let expr = if is_async_fn(kind) {
++ match get_async_fn_body(cx.tcx, body) {
++ Some(e) => e,
++ None => return,
++ }
++ } else {
++ &body.value
++ };
++ lint_implicit_returns(cx, expr, expr.span.ctxt(), None);
+ }
+}
--- /dev/null
- pub fn read_conf(args: &[rustc_ast::NestedMetaItem], sess: &Session) -> Conf {
+// error-pattern:cargo-clippy
+
+#![feature(box_patterns)]
+#![feature(box_syntax)]
+#![feature(drain_filter)]
+#![feature(in_band_lifetimes)]
+#![feature(iter_zip)]
+#![feature(once_cell)]
+#![cfg_attr(bootstrap, feature(or_patterns))]
+#![feature(rustc_private)]
+#![feature(stmt_expr_attributes)]
+#![feature(control_flow_enum)]
+#![recursion_limit = "512"]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![allow(clippy::missing_docs_in_private_items, clippy::must_use_candidate)]
+#![warn(trivial_casts, trivial_numeric_casts)]
+// warn on lints, that are included in `rust-lang/rust`s bootstrap
+#![warn(rust_2018_idioms, unused_lifetimes)]
+// warn on rustc internal lints
+#![warn(rustc::internal)]
+
+// FIXME: switch to something more ergonomic here, once available.
+// (Currently there is no way to opt into sysroot crates without `extern crate`.)
+extern crate rustc_ast;
+extern crate rustc_ast_pretty;
+extern crate rustc_data_structures;
+extern crate rustc_driver;
+extern crate rustc_errors;
+extern crate rustc_hir;
+extern crate rustc_hir_pretty;
+extern crate rustc_index;
+extern crate rustc_infer;
+extern crate rustc_lexer;
+extern crate rustc_lint;
+extern crate rustc_middle;
+extern crate rustc_mir;
+extern crate rustc_parse;
+extern crate rustc_parse_format;
+extern crate rustc_session;
+extern crate rustc_span;
+extern crate rustc_target;
+extern crate rustc_trait_selection;
+extern crate rustc_typeck;
+
+use clippy_utils::parse_msrv;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_lint::LintId;
+use rustc_session::Session;
+
+/// Macro used to declare a Clippy lint.
+///
+/// Every lint declaration consists of 4 parts:
+///
+/// 1. The documentation, which is used for the website
+/// 2. The `LINT_NAME`. See [lint naming][lint_naming] on lint naming conventions.
+/// 3. The `lint_level`, which is a mapping from *one* of our lint groups to `Allow`, `Warn` or
+/// `Deny`. The lint level here has nothing to do with what lint groups the lint is a part of.
+/// 4. The `description` that contains a short explanation on what's wrong with code where the
+/// lint is triggered.
+///
+/// Currently the categories `style`, `correctness`, `complexity` and `perf` are enabled by default.
+/// As said in the README.md of this repository, if the lint level mapping changes, please update
+/// README.md.
+///
+/// # Example
+///
+/// ```
+/// #![feature(rustc_private)]
+/// extern crate rustc_session;
+/// use rustc_session::declare_tool_lint;
+/// use clippy_lints::declare_clippy_lint;
+///
+/// declare_clippy_lint! {
+/// /// **What it does:** Checks for ... (describe what the lint matches).
+/// ///
+/// /// **Why is this bad?** Supply the reason for linting the code.
+/// ///
+/// /// **Known problems:** None. (Or describe where it could go wrong.)
+/// ///
+/// /// **Example:**
+/// ///
+/// /// ```rust
+/// /// // Bad
+/// /// Insert a short example of code that triggers the lint
+/// ///
+/// /// // Good
+/// /// Insert a short example of improved code that doesn't trigger the lint
+/// /// ```
+/// pub LINT_NAME,
+/// pedantic,
+/// "description"
+/// }
+/// ```
+/// [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints
+#[macro_export]
+macro_rules! declare_clippy_lint {
+ { $(#[$attr:meta])* pub $name:tt, style, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, correctness, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Deny, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, complexity, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, perf, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, pedantic, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, restriction, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, cargo, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, nursery, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, internal, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true
+ }
+ };
+ { $(#[$attr:meta])* pub $name:tt, internal_warn, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! sym {
+ ( $($x:tt)* ) => { clippy_utils::sym!($($x)*) }
+}
+
+#[macro_export]
+macro_rules! unwrap_cargo_metadata {
+ ( $($x:tt)* ) => { clippy_utils::unwrap_cargo_metadata!($($x)*) }
+}
+
+macro_rules! extract_msrv_attr {
+ ( $($x:tt)* ) => { clippy_utils::extract_msrv_attr!($($x)*); }
+}
+
+mod consts;
+#[macro_use]
+mod utils;
+
+// begin lints modules, do not remove this comment, it’s used in `update_lints`
+mod absurd_extreme_comparisons;
+mod approx_const;
+mod arithmetic;
+mod as_conversions;
+mod asm_syntax;
+mod assertions_on_constants;
+mod assign_ops;
+mod async_yields_async;
+mod atomic_ordering;
+mod attrs;
+mod await_holding_invalid;
+mod bit_mask;
+mod blacklisted_name;
+mod blocks_in_if_conditions;
+mod bool_assert_comparison;
+mod booleans;
+mod bytecount;
+mod cargo_common_metadata;
+mod case_sensitive_file_extension_comparisons;
+mod casts;
+mod checked_conversions;
+mod cognitive_complexity;
+mod collapsible_if;
+mod collapsible_match;
+mod comparison_chain;
+mod copies;
+mod copy_iterator;
+mod create_dir;
+mod dbg_macro;
+mod default;
+mod default_numeric_fallback;
+mod dereference;
+mod derive;
+mod disallowed_method;
+mod doc;
+mod double_comparison;
+mod double_parens;
+mod drop_forget_ref;
+mod duration_subsec;
+mod else_if_without_else;
+mod empty_enum;
+mod entry;
+mod enum_clike;
+mod enum_variants;
+mod eq_op;
+mod erasing_op;
+mod escape;
+mod eta_reduction;
+mod eval_order_dependence;
+mod excessive_bools;
+mod exhaustive_items;
+mod exit;
+mod explicit_write;
+mod fallible_impl_from;
+mod float_equality_without_abs;
+mod float_literal;
+mod floating_point_arithmetic;
+mod format;
+mod formatting;
+mod from_over_into;
+mod from_str_radix_10;
+mod functions;
+mod future_not_send;
+mod get_last_with_len;
+mod identity_op;
+mod if_let_mutex;
+mod if_let_some_result;
+mod if_not_else;
+mod if_then_some_else_none;
+mod implicit_hasher;
+mod implicit_return;
+mod implicit_saturating_sub;
+mod inconsistent_struct_constructor;
+mod indexing_slicing;
+mod infinite_iter;
+mod inherent_impl;
+mod inherent_to_string;
+mod inline_fn_without_body;
+mod int_plus_one;
+mod integer_division;
+mod invalid_upcast_comparisons;
+mod items_after_statements;
+mod large_const_arrays;
+mod large_enum_variant;
+mod large_stack_arrays;
+mod len_zero;
+mod let_if_seq;
+mod let_underscore;
+mod lifetimes;
+mod literal_representation;
+mod loops;
+mod macro_use;
+mod main_recursion;
+mod manual_async_fn;
+mod manual_map;
+mod manual_non_exhaustive;
+mod manual_ok_or;
+mod manual_strip;
+mod manual_unwrap_or;
+mod map_clone;
+mod map_err_ignore;
+mod map_identity;
+mod map_unit_fn;
+mod match_on_vec_items;
+mod matches;
+mod mem_discriminant;
+mod mem_forget;
+mod mem_replace;
+mod methods;
+mod minmax;
+mod misc;
+mod misc_early;
+mod missing_const_for_fn;
+mod missing_doc;
+mod missing_inline;
+mod modulo_arithmetic;
+mod multiple_crate_versions;
+mod mut_key;
+mod mut_mut;
+mod mut_mutex_lock;
+mod mut_reference;
+mod mutable_debug_assertion;
+mod mutex_atomic;
+mod needless_arbitrary_self_type;
+mod needless_bool;
+mod needless_borrow;
+mod needless_borrowed_ref;
+mod needless_continue;
+mod needless_for_each;
+mod needless_pass_by_value;
+mod needless_question_mark;
+mod needless_update;
+mod neg_cmp_op_on_partial_ord;
+mod neg_multiply;
+mod new_without_default;
+mod no_effect;
+mod non_copy_const;
+mod non_expressive_names;
+mod non_octal_unix_permissions;
+mod open_options;
+mod option_env_unwrap;
+mod option_if_let_else;
+mod overflow_check_conditional;
+mod panic_in_result_fn;
+mod panic_unimplemented;
+mod partialeq_ne_impl;
+mod pass_by_ref_or_value;
+mod path_buf_push_overwrite;
+mod pattern_type_mismatch;
+mod precedence;
+mod ptr;
+mod ptr_eq;
+mod ptr_offset_with_cast;
+mod question_mark;
+mod ranges;
+mod redundant_clone;
+mod redundant_closure_call;
+mod redundant_else;
+mod redundant_field_names;
+mod redundant_pub_crate;
+mod redundant_slicing;
+mod redundant_static_lifetimes;
+mod ref_option_ref;
+mod reference;
+mod regex;
+mod repeat_once;
+mod returns;
+mod self_assignment;
+mod semicolon_if_nothing_returned;
+mod serde_api;
+mod shadow;
+mod single_component_path_imports;
+mod size_of_in_element_count;
+mod slow_vector_initialization;
+mod stable_sort_primitive;
+mod strings;
+mod suspicious_operation_groupings;
+mod suspicious_trait_impl;
+mod swap;
+mod tabs_in_doc_comments;
+mod temporary_assignment;
+mod to_digit_is_some;
+mod to_string_in_display;
+mod trait_bounds;
+mod transmute;
+mod transmuting_null;
+mod try_err;
+mod types;
+mod undropped_manually_drops;
+mod unicode;
+mod unit_return_expecting_ord;
+mod unit_types;
+mod unnamed_address;
+mod unnecessary_self_imports;
+mod unnecessary_sort_by;
+mod unnecessary_wraps;
+mod unnested_or_patterns;
+mod unsafe_removed_from_name;
+mod unused_io_amount;
+mod unused_self;
+mod unused_unit;
+mod unwrap;
+mod unwrap_in_result;
+mod upper_case_acronyms;
+mod use_self;
+mod useless_conversion;
+mod vec;
+mod vec_init_then_push;
+mod vec_resize_to_zero;
+mod verbose_file_reads;
+mod wildcard_dependencies;
+mod wildcard_imports;
+mod write;
+mod zero_div_zero;
+mod zero_sized_map_values;
+// end lints modules, do not remove this comment, it’s used in `update_lints`
+
+pub use crate::utils::conf::Conf;
++use crate::utils::conf::TryConf;
+
+/// Register all pre expansion lints
+///
+/// Pre-expansion lints run before any macro expansion has happened.
+///
+/// Note that due to the architecture of the compiler, currently `cfg_attr` attributes on crate
+/// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass.
+///
+/// Used in `./src/driver.rs`.
+pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) {
+ // NOTE: Do not add any more pre-expansion passes. These should be removed eventually.
+ store.register_pre_expansion_pass(|| box write::Write::default());
+ store.register_pre_expansion_pass(|| box attrs::EarlyAttributes);
+ store.register_pre_expansion_pass(|| box dbg_macro::DbgMacro);
+}
+
+#[doc(hidden)]
- match utils::conf::file_from_args(args) {
- Ok(file_name) => {
- // if the user specified a file, it must exist, otherwise default to `clippy.toml` but
- // do not require the file to exist
- let file_name = match file_name {
- Some(file_name) => file_name,
- None => match utils::conf::lookup_conf_file() {
- Ok(Some(path)) => path,
- Ok(None) => return Conf::default(),
- Err(error) => {
- sess.struct_err(&format!("error finding Clippy's configuration file: {}", error))
- .emit();
- return Conf::default();
- },
- },
- };
-
- let file_name = if file_name.is_relative() {
- sess.local_crate_source_file
- .as_deref()
- .and_then(Path::parent)
- .unwrap_or_else(|| Path::new(""))
- .join(file_name)
- } else {
- file_name
- };
-
- let (conf, errors) = utils::conf::read(&file_name);
-
- // all conf errors are non-fatal, we just use the default conf in case of error
- for error in errors {
- sess.struct_err(&format!(
- "error reading Clippy's configuration file `{}`: {}",
- file_name.display(),
- error
- ))
- .emit();
- }
-
- conf
- },
- Err((err, span)) => {
- sess.struct_span_err(span, err)
- .span_note(span, "Clippy will use default configuration")
++pub fn read_conf(sess: &Session) -> Conf {
+ use std::path::Path;
- Conf::default()
++ let file_name = match utils::conf::lookup_conf_file() {
++ Ok(Some(path)) => path,
++ Ok(None) => return Conf::default(),
++ Err(error) => {
++ sess.struct_err(&format!("error finding Clippy's configuration file: {}", error))
+ .emit();
++ return Conf::default();
+ },
++ };
++
++ let file_name = if file_name.is_relative() {
++ sess.local_crate_source_file
++ .as_deref()
++ .and_then(Path::parent)
++ .unwrap_or_else(|| Path::new(""))
++ .join(file_name)
++ } else {
++ file_name
++ };
++
++ let TryConf { conf, errors } = utils::conf::read(&file_name);
++ // all conf errors are non-fatal, we just use the default conf in case of error
++ for error in errors {
++ sess.struct_err(&format!(
++ "error reading Clippy's configuration file `{}`: {}",
++ file_name.display(),
++ error
++ ))
++ .emit();
+ }
++
++ conf
+}
+
+/// Register all lints and lint groups with the rustc plugin registry
+///
+/// Used in `./src/driver.rs`.
+#[allow(clippy::too_many_lines)]
+#[rustfmt::skip]
+pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
+ register_removed_non_tool_lints(store);
+
+ // begin deprecated lints, do not remove this comment, it’s used in `update_lints`
+ store.register_removed(
+ "clippy::should_assert_eq",
+ "`assert!()` will be more flexible with RFC 2011",
+ );
+ store.register_removed(
+ "clippy::extend_from_slice",
+ "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice",
+ );
+ store.register_removed(
+ "clippy::range_step_by_zero",
+ "`iterator.step_by(0)` panics nowadays",
+ );
+ store.register_removed(
+ "clippy::unstable_as_slice",
+ "`Vec::as_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "clippy::unstable_as_mut_slice",
+ "`Vec::as_mut_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "clippy::misaligned_transmute",
+ "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr",
+ );
+ store.register_removed(
+ "clippy::assign_ops",
+ "using compound assignment operators (e.g., `+=`) is harmless",
+ );
+ store.register_removed(
+ "clippy::if_let_redundant_pattern_matching",
+ "this lint has been changed to redundant_pattern_matching",
+ );
+ store.register_removed(
+ "clippy::unsafe_vector_initialization",
+ "the replacement suggested by this lint had substantially different behavior",
+ );
+ store.register_removed(
+ "clippy::unused_collect",
+ "`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint",
+ );
+ store.register_removed(
+ "clippy::replace_consts",
+ "associated-constants `MIN`/`MAX` of integers are preferred to `{min,max}_value()` and module constants",
+ );
+ store.register_removed(
+ "clippy::regex_macro",
+ "the regex! macro has been removed from the regex crate in 2018",
+ );
+ store.register_removed(
+ "clippy::find_map",
+ "this lint has been replaced by `manual_find_map`, a more specific lint",
+ );
+ store.register_removed(
+ "clippy::filter_map",
+ "this lint has been replaced by `manual_filter_map`, a more specific lint",
+ );
+ // end deprecated lints, do not remove this comment, it’s used in `update_lints`
+
+ // begin register lints, do not remove this comment, it’s used in `update_lints`
+ store.register_lints(&[
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::CLIPPY_LINTS_INTERNAL,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::COMPILER_LINT_FUNCTIONS,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::DEFAULT_LINT,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::IF_CHAIN_STYLE,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::INTERNING_DEFINED_SYMBOL,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::INVALID_PATHS,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::LINT_WITHOUT_LINT_PASS,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::OUTER_EXPN_EXPN_DATA,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::PRODUCE_ICE,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::UNNECESSARY_SYMBOL_STR,
+ absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS,
+ approx_const::APPROX_CONSTANT,
+ arithmetic::FLOAT_ARITHMETIC,
+ arithmetic::INTEGER_ARITHMETIC,
+ as_conversions::AS_CONVERSIONS,
+ asm_syntax::INLINE_ASM_X86_ATT_SYNTAX,
+ asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX,
+ assertions_on_constants::ASSERTIONS_ON_CONSTANTS,
+ assign_ops::ASSIGN_OP_PATTERN,
+ assign_ops::MISREFACTORED_ASSIGN_OP,
+ async_yields_async::ASYNC_YIELDS_ASYNC,
+ atomic_ordering::INVALID_ATOMIC_ORDERING,
+ attrs::BLANKET_CLIPPY_RESTRICTION_LINTS,
+ attrs::DEPRECATED_CFG_ATTR,
+ attrs::DEPRECATED_SEMVER,
+ attrs::EMPTY_LINE_AFTER_OUTER_ATTR,
+ attrs::INLINE_ALWAYS,
+ attrs::MISMATCHED_TARGET_OS,
+ attrs::USELESS_ATTRIBUTE,
+ await_holding_invalid::AWAIT_HOLDING_LOCK,
+ await_holding_invalid::AWAIT_HOLDING_REFCELL_REF,
+ bit_mask::BAD_BIT_MASK,
+ bit_mask::INEFFECTIVE_BIT_MASK,
+ bit_mask::VERBOSE_BIT_MASK,
+ blacklisted_name::BLACKLISTED_NAME,
+ blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS,
+ bool_assert_comparison::BOOL_ASSERT_COMPARISON,
+ booleans::LOGIC_BUG,
+ booleans::NONMINIMAL_BOOL,
+ bytecount::NAIVE_BYTECOUNT,
+ cargo_common_metadata::CARGO_COMMON_METADATA,
+ case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
+ casts::CAST_LOSSLESS,
+ casts::CAST_POSSIBLE_TRUNCATION,
+ casts::CAST_POSSIBLE_WRAP,
+ casts::CAST_PRECISION_LOSS,
+ casts::CAST_PTR_ALIGNMENT,
+ casts::CAST_REF_TO_MUT,
+ casts::CAST_SIGN_LOSS,
+ casts::CHAR_LIT_AS_U8,
+ casts::FN_TO_NUMERIC_CAST,
+ casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+ casts::PTR_AS_PTR,
+ casts::UNNECESSARY_CAST,
+ checked_conversions::CHECKED_CONVERSIONS,
+ cognitive_complexity::COGNITIVE_COMPLEXITY,
+ collapsible_if::COLLAPSIBLE_ELSE_IF,
+ collapsible_if::COLLAPSIBLE_IF,
+ collapsible_match::COLLAPSIBLE_MATCH,
+ comparison_chain::COMPARISON_CHAIN,
+ copies::BRANCHES_SHARING_CODE,
+ copies::IFS_SAME_COND,
+ copies::IF_SAME_THEN_ELSE,
+ copies::SAME_FUNCTIONS_IN_IF_CONDITION,
+ copy_iterator::COPY_ITERATOR,
+ create_dir::CREATE_DIR,
+ dbg_macro::DBG_MACRO,
+ default::DEFAULT_TRAIT_ACCESS,
+ default::FIELD_REASSIGN_WITH_DEFAULT,
+ default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK,
+ dereference::EXPLICIT_DEREF_METHODS,
+ derive::DERIVE_HASH_XOR_EQ,
+ derive::DERIVE_ORD_XOR_PARTIAL_ORD,
+ derive::EXPL_IMPL_CLONE_ON_COPY,
+ derive::UNSAFE_DERIVE_DESERIALIZE,
+ disallowed_method::DISALLOWED_METHOD,
+ doc::DOC_MARKDOWN,
+ doc::MISSING_ERRORS_DOC,
+ doc::MISSING_PANICS_DOC,
+ doc::MISSING_SAFETY_DOC,
+ doc::NEEDLESS_DOCTEST_MAIN,
+ double_comparison::DOUBLE_COMPARISONS,
+ double_parens::DOUBLE_PARENS,
+ drop_forget_ref::DROP_COPY,
+ drop_forget_ref::DROP_REF,
+ drop_forget_ref::FORGET_COPY,
+ drop_forget_ref::FORGET_REF,
+ duration_subsec::DURATION_SUBSEC,
+ else_if_without_else::ELSE_IF_WITHOUT_ELSE,
+ empty_enum::EMPTY_ENUM,
+ entry::MAP_ENTRY,
+ enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT,
+ enum_variants::ENUM_VARIANT_NAMES,
+ enum_variants::MODULE_INCEPTION,
+ enum_variants::MODULE_NAME_REPETITIONS,
+ enum_variants::PUB_ENUM_VARIANT_NAMES,
+ eq_op::EQ_OP,
+ eq_op::OP_REF,
+ erasing_op::ERASING_OP,
+ escape::BOXED_LOCAL,
+ eta_reduction::REDUNDANT_CLOSURE,
+ eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
+ eval_order_dependence::DIVERGING_SUB_EXPRESSION,
+ eval_order_dependence::EVAL_ORDER_DEPENDENCE,
+ excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS,
+ excessive_bools::STRUCT_EXCESSIVE_BOOLS,
+ exhaustive_items::EXHAUSTIVE_ENUMS,
+ exhaustive_items::EXHAUSTIVE_STRUCTS,
+ exit::EXIT,
+ explicit_write::EXPLICIT_WRITE,
+ fallible_impl_from::FALLIBLE_IMPL_FROM,
+ float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS,
+ float_literal::EXCESSIVE_PRECISION,
+ float_literal::LOSSY_FLOAT_LITERAL,
+ floating_point_arithmetic::IMPRECISE_FLOPS,
+ floating_point_arithmetic::SUBOPTIMAL_FLOPS,
+ format::USELESS_FORMAT,
+ formatting::POSSIBLE_MISSING_COMMA,
+ formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
+ formatting::SUSPICIOUS_ELSE_FORMATTING,
+ formatting::SUSPICIOUS_UNARY_OP_FORMATTING,
+ from_over_into::FROM_OVER_INTO,
+ from_str_radix_10::FROM_STR_RADIX_10,
+ functions::DOUBLE_MUST_USE,
+ functions::MUST_USE_CANDIDATE,
+ functions::MUST_USE_UNIT,
+ functions::NOT_UNSAFE_PTR_ARG_DEREF,
+ functions::RESULT_UNIT_ERR,
+ functions::TOO_MANY_ARGUMENTS,
+ functions::TOO_MANY_LINES,
+ future_not_send::FUTURE_NOT_SEND,
+ get_last_with_len::GET_LAST_WITH_LEN,
+ identity_op::IDENTITY_OP,
+ if_let_mutex::IF_LET_MUTEX,
+ if_let_some_result::IF_LET_SOME_RESULT,
+ if_not_else::IF_NOT_ELSE,
+ if_then_some_else_none::IF_THEN_SOME_ELSE_NONE,
+ implicit_hasher::IMPLICIT_HASHER,
+ implicit_return::IMPLICIT_RETURN,
+ implicit_saturating_sub::IMPLICIT_SATURATING_SUB,
+ inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR,
+ indexing_slicing::INDEXING_SLICING,
+ indexing_slicing::OUT_OF_BOUNDS_INDEXING,
+ infinite_iter::INFINITE_ITER,
+ infinite_iter::MAYBE_INFINITE_ITER,
+ inherent_impl::MULTIPLE_INHERENT_IMPL,
+ inherent_to_string::INHERENT_TO_STRING,
+ inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY,
+ inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
+ int_plus_one::INT_PLUS_ONE,
+ integer_division::INTEGER_DIVISION,
+ invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS,
+ items_after_statements::ITEMS_AFTER_STATEMENTS,
+ large_const_arrays::LARGE_CONST_ARRAYS,
+ large_enum_variant::LARGE_ENUM_VARIANT,
+ large_stack_arrays::LARGE_STACK_ARRAYS,
+ len_zero::COMPARISON_TO_EMPTY,
+ len_zero::LEN_WITHOUT_IS_EMPTY,
+ len_zero::LEN_ZERO,
+ let_if_seq::USELESS_LET_IF_SEQ,
+ let_underscore::LET_UNDERSCORE_DROP,
+ let_underscore::LET_UNDERSCORE_LOCK,
+ let_underscore::LET_UNDERSCORE_MUST_USE,
+ lifetimes::EXTRA_UNUSED_LIFETIMES,
+ lifetimes::NEEDLESS_LIFETIMES,
+ literal_representation::DECIMAL_LITERAL_REPRESENTATION,
+ literal_representation::INCONSISTENT_DIGIT_GROUPING,
+ literal_representation::LARGE_DIGIT_GROUPS,
+ literal_representation::MISTYPED_LITERAL_SUFFIXES,
+ literal_representation::UNREADABLE_LITERAL,
+ literal_representation::UNUSUAL_BYTE_GROUPINGS,
+ loops::EMPTY_LOOP,
+ loops::EXPLICIT_COUNTER_LOOP,
+ loops::EXPLICIT_INTO_ITER_LOOP,
+ loops::EXPLICIT_ITER_LOOP,
+ loops::FOR_KV_MAP,
+ loops::FOR_LOOPS_OVER_FALLIBLES,
+ loops::ITER_NEXT_LOOP,
+ loops::MANUAL_FLATTEN,
+ loops::MANUAL_MEMCPY,
+ loops::MUT_RANGE_BOUND,
+ loops::NEEDLESS_COLLECT,
+ loops::NEEDLESS_RANGE_LOOP,
+ loops::NEVER_LOOP,
+ loops::SAME_ITEM_PUSH,
+ loops::SINGLE_ELEMENT_LOOP,
+ loops::WHILE_IMMUTABLE_CONDITION,
+ loops::WHILE_LET_LOOP,
+ loops::WHILE_LET_ON_ITERATOR,
+ macro_use::MACRO_USE_IMPORTS,
+ main_recursion::MAIN_RECURSION,
+ manual_async_fn::MANUAL_ASYNC_FN,
+ manual_map::MANUAL_MAP,
+ manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
+ manual_ok_or::MANUAL_OK_OR,
+ manual_strip::MANUAL_STRIP,
+ manual_unwrap_or::MANUAL_UNWRAP_OR,
+ map_clone::MAP_CLONE,
+ map_err_ignore::MAP_ERR_IGNORE,
+ map_identity::MAP_IDENTITY,
+ map_unit_fn::OPTION_MAP_UNIT_FN,
+ map_unit_fn::RESULT_MAP_UNIT_FN,
+ match_on_vec_items::MATCH_ON_VEC_ITEMS,
+ matches::INFALLIBLE_DESTRUCTURING_MATCH,
+ matches::MATCH_AS_REF,
+ matches::MATCH_BOOL,
+ matches::MATCH_LIKE_MATCHES_MACRO,
+ matches::MATCH_OVERLAPPING_ARM,
+ matches::MATCH_REF_PATS,
+ matches::MATCH_SAME_ARMS,
+ matches::MATCH_SINGLE_BINDING,
+ matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
+ matches::MATCH_WILD_ERR_ARM,
+ matches::REDUNDANT_PATTERN_MATCHING,
+ matches::REST_PAT_IN_FULLY_BOUND_STRUCTS,
+ matches::SINGLE_MATCH,
+ matches::SINGLE_MATCH_ELSE,
+ matches::WILDCARD_ENUM_MATCH_ARM,
+ matches::WILDCARD_IN_OR_PATTERNS,
+ mem_discriminant::MEM_DISCRIMINANT_NON_ENUM,
+ mem_forget::MEM_FORGET,
+ mem_replace::MEM_REPLACE_OPTION_WITH_NONE,
+ mem_replace::MEM_REPLACE_WITH_DEFAULT,
+ mem_replace::MEM_REPLACE_WITH_UNINIT,
+ methods::BIND_INSTEAD_OF_MAP,
+ methods::BYTES_NTH,
+ methods::CHARS_LAST_CMP,
+ methods::CHARS_NEXT_CMP,
+ methods::CLONED_INSTEAD_OF_COPIED,
+ methods::CLONE_DOUBLE_REF,
+ methods::CLONE_ON_COPY,
+ methods::CLONE_ON_REF_PTR,
+ methods::EXPECT_FUN_CALL,
+ methods::EXPECT_USED,
+ methods::FILETYPE_IS_FILE,
+ methods::FILTER_MAP_IDENTITY,
+ methods::FILTER_MAP_NEXT,
+ methods::FILTER_NEXT,
+ methods::FLAT_MAP_IDENTITY,
+ methods::FLAT_MAP_OPTION,
+ methods::FROM_ITER_INSTEAD_OF_COLLECT,
+ methods::GET_UNWRAP,
+ methods::IMPLICIT_CLONE,
+ methods::INEFFICIENT_TO_STRING,
+ methods::INSPECT_FOR_EACH,
+ methods::INTO_ITER_ON_REF,
+ methods::ITERATOR_STEP_BY_ZERO,
+ methods::ITER_CLONED_COLLECT,
+ methods::ITER_COUNT,
+ methods::ITER_NEXT_SLICE,
+ methods::ITER_NTH,
+ methods::ITER_NTH_ZERO,
+ methods::ITER_SKIP_NEXT,
+ methods::MANUAL_FILTER_MAP,
+ methods::MANUAL_FIND_MAP,
+ methods::MANUAL_SATURATING_ARITHMETIC,
+ methods::MAP_COLLECT_RESULT_UNIT,
+ methods::MAP_FLATTEN,
+ methods::MAP_UNWRAP_OR,
+ methods::NEW_RET_NO_SELF,
+ methods::OK_EXPECT,
+ methods::OPTION_AS_REF_DEREF,
+ methods::OPTION_FILTER_MAP,
+ methods::OPTION_MAP_OR_NONE,
+ methods::OR_FUN_CALL,
+ methods::RESULT_MAP_OR_INTO_OPTION,
+ methods::SEARCH_IS_SOME,
+ methods::SHOULD_IMPLEMENT_TRAIT,
+ methods::SINGLE_CHAR_ADD_STR,
+ methods::SINGLE_CHAR_PATTERN,
+ methods::SKIP_WHILE_NEXT,
+ methods::STRING_EXTEND_CHARS,
+ methods::SUSPICIOUS_MAP,
+ methods::UNINIT_ASSUMED_INIT,
+ methods::UNNECESSARY_FILTER_MAP,
+ methods::UNNECESSARY_FOLD,
+ methods::UNNECESSARY_LAZY_EVALUATIONS,
+ methods::UNWRAP_USED,
+ methods::USELESS_ASREF,
+ methods::WRONG_PUB_SELF_CONVENTION,
+ methods::WRONG_SELF_CONVENTION,
+ methods::ZST_OFFSET,
+ minmax::MIN_MAX,
+ misc::CMP_NAN,
+ misc::CMP_OWNED,
+ misc::FLOAT_CMP,
+ misc::FLOAT_CMP_CONST,
+ misc::MODULO_ONE,
+ misc::SHORT_CIRCUIT_STATEMENT,
+ misc::TOPLEVEL_REF_ARG,
+ misc::USED_UNDERSCORE_BINDING,
+ misc::ZERO_PTR,
+ misc_early::BUILTIN_TYPE_SHADOW,
+ misc_early::DOUBLE_NEG,
+ misc_early::DUPLICATE_UNDERSCORE_ARGUMENT,
+ misc_early::MIXED_CASE_HEX_LITERALS,
+ misc_early::REDUNDANT_PATTERN,
+ misc_early::UNNEEDED_FIELD_PATTERN,
+ misc_early::UNNEEDED_WILDCARD_PATTERN,
+ misc_early::UNSEPARATED_LITERAL_SUFFIX,
+ misc_early::ZERO_PREFIXED_LITERAL,
+ missing_const_for_fn::MISSING_CONST_FOR_FN,
+ missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS,
+ missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS,
+ modulo_arithmetic::MODULO_ARITHMETIC,
+ multiple_crate_versions::MULTIPLE_CRATE_VERSIONS,
+ mut_key::MUTABLE_KEY_TYPE,
+ mut_mut::MUT_MUT,
+ mut_mutex_lock::MUT_MUTEX_LOCK,
+ mut_reference::UNNECESSARY_MUT_PASSED,
+ mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL,
+ mutex_atomic::MUTEX_ATOMIC,
+ mutex_atomic::MUTEX_INTEGER,
+ needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE,
+ needless_bool::BOOL_COMPARISON,
+ needless_bool::NEEDLESS_BOOL,
+ needless_borrow::NEEDLESS_BORROW,
+ needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE,
+ needless_continue::NEEDLESS_CONTINUE,
+ needless_for_each::NEEDLESS_FOR_EACH,
+ needless_pass_by_value::NEEDLESS_PASS_BY_VALUE,
+ needless_question_mark::NEEDLESS_QUESTION_MARK,
+ needless_update::NEEDLESS_UPDATE,
+ neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD,
+ neg_multiply::NEG_MULTIPLY,
+ new_without_default::NEW_WITHOUT_DEFAULT,
+ no_effect::NO_EFFECT,
+ no_effect::UNNECESSARY_OPERATION,
+ non_copy_const::BORROW_INTERIOR_MUTABLE_CONST,
+ non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST,
+ non_expressive_names::JUST_UNDERSCORES_AND_DIGITS,
+ non_expressive_names::MANY_SINGLE_CHAR_NAMES,
+ non_expressive_names::SIMILAR_NAMES,
+ non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS,
+ open_options::NONSENSICAL_OPEN_OPTIONS,
+ option_env_unwrap::OPTION_ENV_UNWRAP,
+ option_if_let_else::OPTION_IF_LET_ELSE,
+ overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL,
+ panic_in_result_fn::PANIC_IN_RESULT_FN,
+ panic_unimplemented::PANIC,
+ panic_unimplemented::TODO,
+ panic_unimplemented::UNIMPLEMENTED,
+ panic_unimplemented::UNREACHABLE,
+ partialeq_ne_impl::PARTIALEQ_NE_IMPL,
+ pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE,
+ pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF,
+ path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE,
+ pattern_type_mismatch::PATTERN_TYPE_MISMATCH,
+ precedence::PRECEDENCE,
+ ptr::CMP_NULL,
+ ptr::INVALID_NULL_PTR_USAGE,
+ ptr::MUT_FROM_REF,
+ ptr::PTR_ARG,
+ ptr_eq::PTR_EQ,
+ ptr_offset_with_cast::PTR_OFFSET_WITH_CAST,
+ question_mark::QUESTION_MARK,
+ ranges::MANUAL_RANGE_CONTAINS,
+ ranges::RANGE_MINUS_ONE,
+ ranges::RANGE_PLUS_ONE,
+ ranges::RANGE_ZIP_WITH_LEN,
+ ranges::REVERSED_EMPTY_RANGES,
+ redundant_clone::REDUNDANT_CLONE,
+ redundant_closure_call::REDUNDANT_CLOSURE_CALL,
+ redundant_else::REDUNDANT_ELSE,
+ redundant_field_names::REDUNDANT_FIELD_NAMES,
+ redundant_pub_crate::REDUNDANT_PUB_CRATE,
+ redundant_slicing::REDUNDANT_SLICING,
+ redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES,
+ ref_option_ref::REF_OPTION_REF,
+ reference::DEREF_ADDROF,
+ reference::REF_IN_DEREF,
+ regex::INVALID_REGEX,
+ regex::TRIVIAL_REGEX,
+ repeat_once::REPEAT_ONCE,
+ returns::LET_AND_RETURN,
+ returns::NEEDLESS_RETURN,
+ self_assignment::SELF_ASSIGNMENT,
+ semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED,
+ serde_api::SERDE_API_MISUSE,
+ shadow::SHADOW_REUSE,
+ shadow::SHADOW_SAME,
+ shadow::SHADOW_UNRELATED,
+ single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS,
+ size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT,
+ slow_vector_initialization::SLOW_VECTOR_INITIALIZATION,
+ stable_sort_primitive::STABLE_SORT_PRIMITIVE,
+ strings::STRING_ADD,
+ strings::STRING_ADD_ASSIGN,
+ strings::STRING_FROM_UTF8_AS_BYTES,
+ strings::STRING_LIT_AS_BYTES,
+ strings::STRING_TO_STRING,
+ strings::STR_TO_STRING,
+ suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS,
+ suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL,
+ suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL,
+ swap::ALMOST_SWAPPED,
+ swap::MANUAL_SWAP,
+ tabs_in_doc_comments::TABS_IN_DOC_COMMENTS,
+ temporary_assignment::TEMPORARY_ASSIGNMENT,
+ to_digit_is_some::TO_DIGIT_IS_SOME,
+ to_string_in_display::TO_STRING_IN_DISPLAY,
+ trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS,
+ trait_bounds::TYPE_REPETITION_IN_BOUNDS,
+ transmute::CROSSPOINTER_TRANSMUTE,
+ transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
+ transmute::TRANSMUTE_BYTES_TO_STR,
+ transmute::TRANSMUTE_FLOAT_TO_INT,
+ transmute::TRANSMUTE_INT_TO_BOOL,
+ transmute::TRANSMUTE_INT_TO_CHAR,
+ transmute::TRANSMUTE_INT_TO_FLOAT,
+ transmute::TRANSMUTE_PTR_TO_PTR,
+ transmute::TRANSMUTE_PTR_TO_REF,
+ transmute::UNSOUND_COLLECTION_TRANSMUTE,
+ transmute::USELESS_TRANSMUTE,
+ transmute::WRONG_TRANSMUTE,
+ transmuting_null::TRANSMUTING_NULL,
+ try_err::TRY_ERR,
+ types::BORROWED_BOX,
+ types::BOX_VEC,
+ types::LINKEDLIST,
+ types::OPTION_OPTION,
+ types::RC_BUFFER,
+ types::REDUNDANT_ALLOCATION,
+ types::TYPE_COMPLEXITY,
+ types::VEC_BOX,
+ undropped_manually_drops::UNDROPPED_MANUALLY_DROPS,
+ unicode::INVISIBLE_CHARACTERS,
+ unicode::NON_ASCII_LITERAL,
+ unicode::UNICODE_NOT_NFC,
+ unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD,
+ unit_types::LET_UNIT_VALUE,
+ unit_types::UNIT_ARG,
+ unit_types::UNIT_CMP,
+ unnamed_address::FN_ADDRESS_COMPARISONS,
+ unnamed_address::VTABLE_ADDRESS_COMPARISONS,
+ unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS,
+ unnecessary_sort_by::UNNECESSARY_SORT_BY,
+ unnecessary_wraps::UNNECESSARY_WRAPS,
+ unnested_or_patterns::UNNESTED_OR_PATTERNS,
+ unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
+ unused_io_amount::UNUSED_IO_AMOUNT,
+ unused_self::UNUSED_SELF,
+ unused_unit::UNUSED_UNIT,
+ unwrap::PANICKING_UNWRAP,
+ unwrap::UNNECESSARY_UNWRAP,
+ unwrap_in_result::UNWRAP_IN_RESULT,
+ upper_case_acronyms::UPPER_CASE_ACRONYMS,
+ use_self::USE_SELF,
+ useless_conversion::USELESS_CONVERSION,
+ vec::USELESS_VEC,
+ vec_init_then_push::VEC_INIT_THEN_PUSH,
+ vec_resize_to_zero::VEC_RESIZE_TO_ZERO,
+ verbose_file_reads::VERBOSE_FILE_READS,
+ wildcard_dependencies::WILDCARD_DEPENDENCIES,
+ wildcard_imports::ENUM_GLOB_USE,
+ wildcard_imports::WILDCARD_IMPORTS,
+ write::PRINTLN_EMPTY_STRING,
+ write::PRINT_LITERAL,
+ write::PRINT_STDERR,
+ write::PRINT_STDOUT,
+ write::PRINT_WITH_NEWLINE,
+ write::USE_DEBUG,
+ write::WRITELN_EMPTY_STRING,
+ write::WRITE_LITERAL,
+ write::WRITE_WITH_NEWLINE,
+ zero_div_zero::ZERO_DIVIDED_BY_ZERO,
+ zero_sized_map_values::ZERO_SIZED_MAP_VALUES,
+ ]);
+ // end register lints, do not remove this comment, it’s used in `update_lints`
+
+ // all the internal lints
+ #[cfg(feature = "internal-lints")]
+ {
+ store.register_early_pass(|| box utils::internal_lints::ClippyLintsInternal);
+ store.register_early_pass(|| box utils::internal_lints::ProduceIce);
+ store.register_late_pass(|| box utils::inspector::DeepCodeInspector);
+ store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls);
+ store.register_late_pass(|| box utils::internal_lints::CompilerLintFunctions::new());
+ store.register_late_pass(|| box utils::internal_lints::IfChainStyle);
+ store.register_late_pass(|| box utils::internal_lints::InvalidPaths);
+ store.register_late_pass(|| box utils::internal_lints::InterningDefinedSymbol::default());
+ store.register_late_pass(|| box utils::internal_lints::LintWithoutLintPass::default());
+ store.register_late_pass(|| box utils::internal_lints::MatchTypeOnDiagItem);
+ store.register_late_pass(|| box utils::internal_lints::OuterExpnDataPass);
+ }
++ #[cfg(feature = "metadata-collector-lint")]
++ {
++ if std::env::var("ENABLE_METADATA_COLLECTION").eq(&Ok("1".to_string())) {
++ store.register_late_pass(|| box utils::internal_lints::metadata_collector::MetadataCollector::default());
++ }
++ }
++
+ store.register_late_pass(|| box utils::author::Author);
+ store.register_late_pass(|| box await_holding_invalid::AwaitHolding);
+ store.register_late_pass(|| box serde_api::SerdeApi);
+ let vec_box_size_threshold = conf.vec_box_size_threshold;
+ let type_complexity_threshold = conf.type_complexity_threshold;
+ store.register_late_pass(move || box types::Types::new(vec_box_size_threshold, type_complexity_threshold));
+ store.register_late_pass(|| box booleans::NonminimalBool);
+ store.register_late_pass(|| box eq_op::EqOp);
+ store.register_late_pass(|| box enum_clike::UnportableVariant);
+ store.register_late_pass(|| box float_literal::FloatLiteral);
+ let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
+ store.register_late_pass(move || box bit_mask::BitMask::new(verbose_bit_mask_threshold));
+ store.register_late_pass(|| box ptr::Ptr);
+ store.register_late_pass(|| box ptr_eq::PtrEq);
+ store.register_late_pass(|| box needless_bool::NeedlessBool);
+ store.register_late_pass(|| box needless_bool::BoolComparison);
+ store.register_late_pass(|| box needless_for_each::NeedlessForEach);
+ store.register_late_pass(|| box approx_const::ApproxConstant);
+ store.register_late_pass(|| box misc::MiscLints);
+ store.register_late_pass(|| box eta_reduction::EtaReduction);
+ store.register_late_pass(|| box identity_op::IdentityOp);
+ store.register_late_pass(|| box erasing_op::ErasingOp);
+ store.register_late_pass(|| box mut_mut::MutMut);
+ store.register_late_pass(|| box mut_reference::UnnecessaryMutPassed);
+ store.register_late_pass(|| box len_zero::LenZero);
+ store.register_late_pass(|| box attrs::Attributes);
+ store.register_late_pass(|| box blocks_in_if_conditions::BlocksInIfConditions);
+ store.register_late_pass(|| box collapsible_match::CollapsibleMatch);
+ store.register_late_pass(|| box unicode::Unicode);
+ store.register_late_pass(|| box unit_return_expecting_ord::UnitReturnExpectingOrd);
+ store.register_late_pass(|| box strings::StringAdd);
+ store.register_late_pass(|| box implicit_return::ImplicitReturn);
+ store.register_late_pass(|| box implicit_saturating_sub::ImplicitSaturatingSub);
+ store.register_late_pass(|| box default_numeric_fallback::DefaultNumericFallback);
+ store.register_late_pass(|| box inconsistent_struct_constructor::InconsistentStructConstructor);
+ store.register_late_pass(|| box non_octal_unix_permissions::NonOctalUnixPermissions);
+ store.register_early_pass(|| box unnecessary_self_imports::UnnecessarySelfImports);
+
+ let msrv = conf.msrv.as_ref().and_then(|s| {
+ parse_msrv(s, None, None).or_else(|| {
+ sess.err(&format!("error reading Clippy's configuration file. `{}` is not a valid Rust version", s));
+ None
+ })
+ });
+
+ store.register_late_pass(move || box methods::Methods::new(msrv));
+ store.register_late_pass(move || box matches::Matches::new(msrv));
+ store.register_early_pass(move || box manual_non_exhaustive::ManualNonExhaustive::new(msrv));
+ store.register_late_pass(move || box manual_strip::ManualStrip::new(msrv));
+ store.register_early_pass(move || box redundant_static_lifetimes::RedundantStaticLifetimes::new(msrv));
+ store.register_early_pass(move || box redundant_field_names::RedundantFieldNames::new(msrv));
+ store.register_late_pass(move || box checked_conversions::CheckedConversions::new(msrv));
+ store.register_late_pass(move || box mem_replace::MemReplace::new(msrv));
+ store.register_late_pass(move || box ranges::Ranges::new(msrv));
+ store.register_late_pass(move || box from_over_into::FromOverInto::new(msrv));
+ store.register_late_pass(move || box use_self::UseSelf::new(msrv));
+ store.register_late_pass(move || box missing_const_for_fn::MissingConstForFn::new(msrv));
+ store.register_late_pass(move || box needless_question_mark::NeedlessQuestionMark);
+ store.register_late_pass(move || box casts::Casts::new(msrv));
+ store.register_early_pass(move || box unnested_or_patterns::UnnestedOrPatterns::new(msrv));
+
+ store.register_late_pass(|| box size_of_in_element_count::SizeOfInElementCount);
+ store.register_late_pass(|| box map_clone::MapClone);
+ store.register_late_pass(|| box map_err_ignore::MapErrIgnore);
+ store.register_late_pass(|| box shadow::Shadow);
+ store.register_late_pass(|| box unit_types::UnitTypes);
+ store.register_late_pass(|| box loops::Loops);
+ store.register_late_pass(|| box main_recursion::MainRecursion::default());
+ store.register_late_pass(|| box lifetimes::Lifetimes);
+ store.register_late_pass(|| box entry::HashMapPass);
+ store.register_late_pass(|| box minmax::MinMaxPass);
+ store.register_late_pass(|| box open_options::OpenOptions);
+ store.register_late_pass(|| box zero_div_zero::ZeroDiv);
+ store.register_late_pass(|| box mutex_atomic::Mutex);
+ store.register_late_pass(|| box needless_update::NeedlessUpdate);
+ store.register_late_pass(|| box needless_borrow::NeedlessBorrow::default());
+ store.register_late_pass(|| box needless_borrowed_ref::NeedlessBorrowedRef);
+ store.register_late_pass(|| box no_effect::NoEffect);
+ store.register_late_pass(|| box temporary_assignment::TemporaryAssignment);
+ store.register_late_pass(|| box transmute::Transmute);
+ let cognitive_complexity_threshold = conf.cognitive_complexity_threshold;
+ store.register_late_pass(move || box cognitive_complexity::CognitiveComplexity::new(cognitive_complexity_threshold));
+ let too_large_for_stack = conf.too_large_for_stack;
+ store.register_late_pass(move || box escape::BoxedLocal{too_large_for_stack});
+ store.register_late_pass(move || box vec::UselessVec{too_large_for_stack});
+ store.register_late_pass(|| box panic_unimplemented::PanicUnimplemented);
+ store.register_late_pass(|| box strings::StringLitAsBytes);
+ store.register_late_pass(|| box derive::Derive);
+ store.register_late_pass(|| box get_last_with_len::GetLastWithLen);
+ store.register_late_pass(|| box drop_forget_ref::DropForgetRef);
+ store.register_late_pass(|| box empty_enum::EmptyEnum);
+ store.register_late_pass(|| box absurd_extreme_comparisons::AbsurdExtremeComparisons);
+ store.register_late_pass(|| box invalid_upcast_comparisons::InvalidUpcastComparisons);
+ store.register_late_pass(|| box regex::Regex::default());
+ store.register_late_pass(|| box copies::CopyAndPaste);
+ store.register_late_pass(|| box copy_iterator::CopyIterator);
+ store.register_late_pass(|| box format::UselessFormat);
+ store.register_late_pass(|| box swap::Swap);
+ store.register_late_pass(|| box overflow_check_conditional::OverflowCheckConditional);
+ store.register_late_pass(|| box new_without_default::NewWithoutDefault::default());
+ let blacklisted_names = conf.blacklisted_names.iter().cloned().collect::<FxHashSet<_>>();
+ store.register_late_pass(move || box blacklisted_name::BlacklistedName::new(blacklisted_names.clone()));
+ let too_many_arguments_threshold = conf.too_many_arguments_threshold;
+ let too_many_lines_threshold = conf.too_many_lines_threshold;
+ store.register_late_pass(move || box functions::Functions::new(too_many_arguments_threshold, too_many_lines_threshold));
+ let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>();
+ store.register_late_pass(move || box doc::DocMarkdown::new(doc_valid_idents.clone()));
+ store.register_late_pass(|| box neg_multiply::NegMultiply);
+ store.register_late_pass(|| box mem_discriminant::MemDiscriminant);
+ store.register_late_pass(|| box mem_forget::MemForget);
+ store.register_late_pass(|| box arithmetic::Arithmetic::default());
+ store.register_late_pass(|| box assign_ops::AssignOps);
+ store.register_late_pass(|| box let_if_seq::LetIfSeq);
+ store.register_late_pass(|| box eval_order_dependence::EvalOrderDependence);
+ store.register_late_pass(|| box missing_doc::MissingDoc::new());
+ store.register_late_pass(|| box missing_inline::MissingInline);
+ store.register_late_pass(move || box exhaustive_items::ExhaustiveItems);
+ store.register_late_pass(|| box if_let_some_result::OkIfLet);
+ store.register_late_pass(|| box partialeq_ne_impl::PartialEqNeImpl);
+ store.register_late_pass(|| box unused_io_amount::UnusedIoAmount);
+ let enum_variant_size_threshold = conf.enum_variant_size_threshold;
+ store.register_late_pass(move || box large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold));
+ store.register_late_pass(|| box explicit_write::ExplicitWrite);
+ store.register_late_pass(|| box needless_pass_by_value::NeedlessPassByValue);
+ let pass_by_ref_or_value = pass_by_ref_or_value::PassByRefOrValue::new(
+ conf.trivial_copy_size_limit,
+ conf.pass_by_value_size_limit,
+ &sess.target,
+ );
+ store.register_late_pass(move || box pass_by_ref_or_value);
+ store.register_late_pass(|| box ref_option_ref::RefOptionRef);
+ store.register_late_pass(|| box try_err::TryErr);
+ store.register_late_pass(|| box bytecount::ByteCount);
+ store.register_late_pass(|| box infinite_iter::InfiniteIter);
+ store.register_late_pass(|| box inline_fn_without_body::InlineFnWithoutBody);
+ store.register_late_pass(|| box useless_conversion::UselessConversion::default());
+ store.register_late_pass(|| box implicit_hasher::ImplicitHasher);
+ store.register_late_pass(|| box fallible_impl_from::FallibleImplFrom);
+ store.register_late_pass(|| box double_comparison::DoubleComparisons);
+ store.register_late_pass(|| box question_mark::QuestionMark);
+ store.register_early_pass(|| box suspicious_operation_groupings::SuspiciousOperationGroupings);
+ store.register_late_pass(|| box suspicious_trait_impl::SuspiciousImpl);
+ store.register_late_pass(|| box map_unit_fn::MapUnit);
+ store.register_late_pass(|| box inherent_impl::MultipleInherentImpl::default());
+ store.register_late_pass(|| box neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd);
+ store.register_late_pass(|| box unwrap::Unwrap);
+ store.register_late_pass(|| box duration_subsec::DurationSubsec);
+ store.register_late_pass(|| box indexing_slicing::IndexingSlicing);
+ store.register_late_pass(|| box non_copy_const::NonCopyConst);
+ store.register_late_pass(|| box ptr_offset_with_cast::PtrOffsetWithCast);
+ store.register_late_pass(|| box redundant_clone::RedundantClone);
+ store.register_late_pass(|| box slow_vector_initialization::SlowVectorInit);
+ store.register_late_pass(|| box unnecessary_sort_by::UnnecessarySortBy);
+ store.register_late_pass(|| box unnecessary_wraps::UnnecessaryWraps);
+ store.register_late_pass(|| box assertions_on_constants::AssertionsOnConstants);
+ store.register_late_pass(|| box transmuting_null::TransmutingNull);
+ store.register_late_pass(|| box path_buf_push_overwrite::PathBufPushOverwrite);
+ store.register_late_pass(|| box integer_division::IntegerDivision);
+ store.register_late_pass(|| box inherent_to_string::InherentToString);
+ let max_trait_bounds = conf.max_trait_bounds;
+ store.register_late_pass(move || box trait_bounds::TraitBounds::new(max_trait_bounds));
+ store.register_late_pass(|| box comparison_chain::ComparisonChain);
+ store.register_late_pass(|| box mut_key::MutableKeyType);
+ store.register_late_pass(|| box modulo_arithmetic::ModuloArithmetic);
+ store.register_early_pass(|| box reference::DerefAddrOf);
+ store.register_early_pass(|| box reference::RefInDeref);
+ store.register_early_pass(|| box double_parens::DoubleParens);
+ store.register_late_pass(|| box to_string_in_display::ToStringInDisplay::new());
+ store.register_early_pass(|| box unsafe_removed_from_name::UnsafeNameRemoval);
+ store.register_early_pass(|| box if_not_else::IfNotElse);
+ store.register_early_pass(|| box else_if_without_else::ElseIfWithoutElse);
+ store.register_early_pass(|| box int_plus_one::IntPlusOne);
+ store.register_early_pass(|| box formatting::Formatting);
+ store.register_early_pass(|| box misc_early::MiscEarlyLints);
+ store.register_early_pass(|| box redundant_closure_call::RedundantClosureCall);
+ store.register_late_pass(|| box redundant_closure_call::RedundantClosureCall);
+ store.register_early_pass(|| box unused_unit::UnusedUnit);
+ store.register_late_pass(|| box returns::Return);
+ store.register_early_pass(|| box collapsible_if::CollapsibleIf);
+ store.register_early_pass(|| box items_after_statements::ItemsAfterStatements);
+ store.register_early_pass(|| box precedence::Precedence);
+ store.register_early_pass(|| box needless_continue::NeedlessContinue);
+ store.register_early_pass(|| box redundant_else::RedundantElse);
+ store.register_late_pass(|| box create_dir::CreateDir);
+ store.register_early_pass(|| box needless_arbitrary_self_type::NeedlessArbitrarySelfType);
+ let cargo_ignore_publish = conf.cargo_ignore_publish;
+ store.register_late_pass(move || box cargo_common_metadata::CargoCommonMetadata::new(cargo_ignore_publish));
+ store.register_late_pass(|| box multiple_crate_versions::MultipleCrateVersions);
+ store.register_late_pass(|| box wildcard_dependencies::WildcardDependencies);
+ let literal_representation_lint_fraction_readability = conf.unreadable_literal_lint_fractions;
+ store.register_early_pass(move || box literal_representation::LiteralDigitGrouping::new(literal_representation_lint_fraction_readability));
+ let literal_representation_threshold = conf.literal_representation_threshold;
+ store.register_early_pass(move || box literal_representation::DecimalLiteralRepresentation::new(literal_representation_threshold));
+ let enum_variant_name_threshold = conf.enum_variant_name_threshold;
+ store.register_early_pass(move || box enum_variants::EnumVariantNames::new(enum_variant_name_threshold));
+ store.register_early_pass(|| box tabs_in_doc_comments::TabsInDocComments);
+ let upper_case_acronyms_aggressive = conf.upper_case_acronyms_aggressive;
+ store.register_early_pass(move || box upper_case_acronyms::UpperCaseAcronyms::new(upper_case_acronyms_aggressive));
+ store.register_late_pass(|| box default::Default::default());
+ store.register_late_pass(|| box unused_self::UnusedSelf);
+ store.register_late_pass(|| box mutable_debug_assertion::DebugAssertWithMutCall);
+ store.register_late_pass(|| box exit::Exit);
+ store.register_late_pass(|| box to_digit_is_some::ToDigitIsSome);
+ let array_size_threshold = conf.array_size_threshold;
+ store.register_late_pass(move || box large_stack_arrays::LargeStackArrays::new(array_size_threshold));
+ store.register_late_pass(move || box large_const_arrays::LargeConstArrays::new(array_size_threshold));
+ store.register_late_pass(|| box floating_point_arithmetic::FloatingPointArithmetic);
+ store.register_early_pass(|| box as_conversions::AsConversions);
+ store.register_late_pass(|| box let_underscore::LetUnderscore);
+ store.register_late_pass(|| box atomic_ordering::AtomicOrdering);
+ store.register_early_pass(|| box single_component_path_imports::SingleComponentPathImports);
+ let max_fn_params_bools = conf.max_fn_params_bools;
+ let max_struct_bools = conf.max_struct_bools;
+ store.register_early_pass(move || box excessive_bools::ExcessiveBools::new(max_struct_bools, max_fn_params_bools));
+ store.register_early_pass(|| box option_env_unwrap::OptionEnvUnwrap);
+ let warn_on_all_wildcard_imports = conf.warn_on_all_wildcard_imports;
+ store.register_late_pass(move || box wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports));
+ store.register_late_pass(|| box verbose_file_reads::VerboseFileReads);
+ store.register_late_pass(|| box redundant_pub_crate::RedundantPubCrate::default());
+ store.register_late_pass(|| box unnamed_address::UnnamedAddress);
+ store.register_late_pass(|| box dereference::Dereferencing::default());
+ store.register_late_pass(|| box option_if_let_else::OptionIfLetElse);
+ store.register_late_pass(|| box future_not_send::FutureNotSend);
+ store.register_late_pass(|| box if_let_mutex::IfLetMutex);
+ store.register_late_pass(|| box mut_mutex_lock::MutMutexLock);
+ store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems);
+ store.register_late_pass(|| box manual_async_fn::ManualAsyncFn);
+ store.register_late_pass(|| box vec_resize_to_zero::VecResizeToZero);
+ store.register_late_pass(|| box panic_in_result_fn::PanicInResultFn);
+ let single_char_binding_names_threshold = conf.single_char_binding_names_threshold;
+ store.register_early_pass(move || box non_expressive_names::NonExpressiveNames {
+ single_char_binding_names_threshold,
+ });
+ store.register_late_pass(|| box macro_use::MacroUseImports::default());
+ store.register_late_pass(|| box map_identity::MapIdentity);
+ store.register_late_pass(|| box pattern_type_mismatch::PatternTypeMismatch);
+ store.register_late_pass(|| box stable_sort_primitive::StableSortPrimitive);
+ store.register_late_pass(|| box repeat_once::RepeatOnce);
+ store.register_late_pass(|| box unwrap_in_result::UnwrapInResult);
+ store.register_late_pass(|| box self_assignment::SelfAssignment);
+ store.register_late_pass(|| box manual_unwrap_or::ManualUnwrapOr);
+ store.register_late_pass(|| box manual_ok_or::ManualOkOr);
+ store.register_late_pass(|| box float_equality_without_abs::FloatEqualityWithoutAbs);
+ store.register_late_pass(|| box semicolon_if_nothing_returned::SemicolonIfNothingReturned);
+ store.register_late_pass(|| box async_yields_async::AsyncYieldsAsync);
+ let disallowed_methods = conf.disallowed_methods.iter().cloned().collect::<FxHashSet<_>>();
+ store.register_late_pass(move || box disallowed_method::DisallowedMethod::new(&disallowed_methods));
+ store.register_early_pass(|| box asm_syntax::InlineAsmX86AttSyntax);
+ store.register_early_pass(|| box asm_syntax::InlineAsmX86IntelSyntax);
+ store.register_late_pass(|| box undropped_manually_drops::UndroppedManuallyDrops);
+ store.register_late_pass(|| box strings::StrToString);
+ store.register_late_pass(|| box strings::StringToString);
+ store.register_late_pass(|| box zero_sized_map_values::ZeroSizedMapValues);
+ store.register_late_pass(|| box vec_init_then_push::VecInitThenPush::default());
+ store.register_late_pass(|| box case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons);
+ store.register_late_pass(|| box redundant_slicing::RedundantSlicing);
+ store.register_late_pass(|| box from_str_radix_10::FromStrRadix10);
+ store.register_late_pass(|| box manual_map::ManualMap);
+ store.register_late_pass(move || box if_then_some_else_none::IfThenSomeElseNone::new(msrv));
+ store.register_early_pass(|| box bool_assert_comparison::BoolAssertComparison);
+
+ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
+ LintId::of(arithmetic::FLOAT_ARITHMETIC),
+ LintId::of(arithmetic::INTEGER_ARITHMETIC),
+ LintId::of(as_conversions::AS_CONVERSIONS),
+ LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX),
+ LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
+ LintId::of(create_dir::CREATE_DIR),
+ LintId::of(dbg_macro::DBG_MACRO),
+ LintId::of(default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK),
+ LintId::of(else_if_without_else::ELSE_IF_WITHOUT_ELSE),
+ LintId::of(exhaustive_items::EXHAUSTIVE_ENUMS),
+ LintId::of(exhaustive_items::EXHAUSTIVE_STRUCTS),
+ LintId::of(exit::EXIT),
+ LintId::of(float_literal::LOSSY_FLOAT_LITERAL),
+ LintId::of(if_then_some_else_none::IF_THEN_SOME_ELSE_NONE),
+ LintId::of(implicit_return::IMPLICIT_RETURN),
+ LintId::of(indexing_slicing::INDEXING_SLICING),
+ LintId::of(inherent_impl::MULTIPLE_INHERENT_IMPL),
+ LintId::of(integer_division::INTEGER_DIVISION),
+ LintId::of(let_underscore::LET_UNDERSCORE_MUST_USE),
+ LintId::of(literal_representation::DECIMAL_LITERAL_REPRESENTATION),
+ LintId::of(map_err_ignore::MAP_ERR_IGNORE),
+ LintId::of(matches::REST_PAT_IN_FULLY_BOUND_STRUCTS),
+ LintId::of(matches::WILDCARD_ENUM_MATCH_ARM),
+ LintId::of(mem_forget::MEM_FORGET),
+ LintId::of(methods::CLONE_ON_REF_PTR),
+ LintId::of(methods::EXPECT_USED),
+ LintId::of(methods::FILETYPE_IS_FILE),
+ LintId::of(methods::GET_UNWRAP),
+ LintId::of(methods::UNWRAP_USED),
+ LintId::of(methods::WRONG_PUB_SELF_CONVENTION),
+ LintId::of(misc::FLOAT_CMP_CONST),
+ LintId::of(misc_early::UNNEEDED_FIELD_PATTERN),
+ LintId::of(missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS),
+ LintId::of(missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS),
+ LintId::of(modulo_arithmetic::MODULO_ARITHMETIC),
+ LintId::of(panic_in_result_fn::PANIC_IN_RESULT_FN),
+ LintId::of(panic_unimplemented::PANIC),
+ LintId::of(panic_unimplemented::TODO),
+ LintId::of(panic_unimplemented::UNIMPLEMENTED),
+ LintId::of(panic_unimplemented::UNREACHABLE),
+ LintId::of(pattern_type_mismatch::PATTERN_TYPE_MISMATCH),
+ LintId::of(semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED),
+ LintId::of(shadow::SHADOW_REUSE),
+ LintId::of(shadow::SHADOW_SAME),
+ LintId::of(strings::STRING_ADD),
+ LintId::of(strings::STRING_TO_STRING),
+ LintId::of(strings::STR_TO_STRING),
+ LintId::of(types::RC_BUFFER),
+ LintId::of(unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS),
+ LintId::of(unwrap_in_result::UNWRAP_IN_RESULT),
+ LintId::of(verbose_file_reads::VERBOSE_FILE_READS),
+ LintId::of(write::PRINT_STDERR),
+ LintId::of(write::PRINT_STDOUT),
+ LintId::of(write::USE_DEBUG),
+ ]);
+
+ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
+ LintId::of(attrs::INLINE_ALWAYS),
+ LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
+ LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
+ LintId::of(bit_mask::VERBOSE_BIT_MASK),
+ LintId::of(bytecount::NAIVE_BYTECOUNT),
+ LintId::of(case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS),
+ LintId::of(casts::CAST_LOSSLESS),
+ LintId::of(casts::CAST_POSSIBLE_TRUNCATION),
+ LintId::of(casts::CAST_POSSIBLE_WRAP),
+ LintId::of(casts::CAST_PRECISION_LOSS),
+ LintId::of(casts::CAST_PTR_ALIGNMENT),
+ LintId::of(casts::CAST_SIGN_LOSS),
+ LintId::of(casts::PTR_AS_PTR),
+ LintId::of(checked_conversions::CHECKED_CONVERSIONS),
+ LintId::of(copies::SAME_FUNCTIONS_IN_IF_CONDITION),
+ LintId::of(copy_iterator::COPY_ITERATOR),
+ LintId::of(default::DEFAULT_TRAIT_ACCESS),
+ LintId::of(dereference::EXPLICIT_DEREF_METHODS),
+ LintId::of(derive::EXPL_IMPL_CLONE_ON_COPY),
+ LintId::of(derive::UNSAFE_DERIVE_DESERIALIZE),
+ LintId::of(doc::DOC_MARKDOWN),
+ LintId::of(doc::MISSING_ERRORS_DOC),
+ LintId::of(doc::MISSING_PANICS_DOC),
+ LintId::of(empty_enum::EMPTY_ENUM),
+ LintId::of(enum_variants::MODULE_NAME_REPETITIONS),
+ LintId::of(enum_variants::PUB_ENUM_VARIANT_NAMES),
+ LintId::of(eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS),
+ LintId::of(excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS),
+ LintId::of(excessive_bools::STRUCT_EXCESSIVE_BOOLS),
+ LintId::of(functions::MUST_USE_CANDIDATE),
+ LintId::of(functions::TOO_MANY_LINES),
+ LintId::of(if_not_else::IF_NOT_ELSE),
+ LintId::of(implicit_hasher::IMPLICIT_HASHER),
+ LintId::of(implicit_saturating_sub::IMPLICIT_SATURATING_SUB),
+ LintId::of(infinite_iter::MAYBE_INFINITE_ITER),
+ LintId::of(invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS),
+ LintId::of(items_after_statements::ITEMS_AFTER_STATEMENTS),
+ LintId::of(large_stack_arrays::LARGE_STACK_ARRAYS),
+ LintId::of(let_underscore::LET_UNDERSCORE_DROP),
+ LintId::of(literal_representation::LARGE_DIGIT_GROUPS),
+ LintId::of(literal_representation::UNREADABLE_LITERAL),
+ LintId::of(loops::EXPLICIT_INTO_ITER_LOOP),
+ LintId::of(loops::EXPLICIT_ITER_LOOP),
+ LintId::of(macro_use::MACRO_USE_IMPORTS),
+ LintId::of(manual_ok_or::MANUAL_OK_OR),
+ LintId::of(match_on_vec_items::MATCH_ON_VEC_ITEMS),
+ LintId::of(matches::MATCH_BOOL),
+ LintId::of(matches::MATCH_SAME_ARMS),
+ LintId::of(matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS),
+ LintId::of(matches::MATCH_WILD_ERR_ARM),
+ LintId::of(matches::SINGLE_MATCH_ELSE),
+ LintId::of(methods::CLONED_INSTEAD_OF_COPIED),
+ LintId::of(methods::FILTER_MAP_NEXT),
+ LintId::of(methods::FLAT_MAP_OPTION),
+ LintId::of(methods::IMPLICIT_CLONE),
+ LintId::of(methods::INEFFICIENT_TO_STRING),
+ LintId::of(methods::MAP_FLATTEN),
+ LintId::of(methods::MAP_UNWRAP_OR),
+ LintId::of(misc::USED_UNDERSCORE_BINDING),
+ LintId::of(misc_early::UNSEPARATED_LITERAL_SUFFIX),
+ LintId::of(mut_mut::MUT_MUT),
+ LintId::of(needless_continue::NEEDLESS_CONTINUE),
+ LintId::of(needless_for_each::NEEDLESS_FOR_EACH),
+ LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE),
+ LintId::of(non_expressive_names::SIMILAR_NAMES),
+ LintId::of(option_if_let_else::OPTION_IF_LET_ELSE),
+ LintId::of(pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE),
+ LintId::of(pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF),
+ LintId::of(ranges::RANGE_MINUS_ONE),
+ LintId::of(ranges::RANGE_PLUS_ONE),
+ LintId::of(redundant_else::REDUNDANT_ELSE),
+ LintId::of(ref_option_ref::REF_OPTION_REF),
+ LintId::of(shadow::SHADOW_UNRELATED),
+ LintId::of(strings::STRING_ADD_ASSIGN),
+ LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS),
+ LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS),
+ LintId::of(transmute::TRANSMUTE_PTR_TO_PTR),
+ LintId::of(types::LINKEDLIST),
+ LintId::of(types::OPTION_OPTION),
+ LintId::of(unicode::NON_ASCII_LITERAL),
+ LintId::of(unicode::UNICODE_NOT_NFC),
+ LintId::of(unit_types::LET_UNIT_VALUE),
+ LintId::of(unnecessary_wraps::UNNECESSARY_WRAPS),
+ LintId::of(unnested_or_patterns::UNNESTED_OR_PATTERNS),
+ LintId::of(unused_self::UNUSED_SELF),
+ LintId::of(wildcard_imports::ENUM_GLOB_USE),
+ LintId::of(wildcard_imports::WILDCARD_IMPORTS),
+ LintId::of(zero_sized_map_values::ZERO_SIZED_MAP_VALUES),
+ ]);
+
+ #[cfg(feature = "internal-lints")]
+ store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
+ LintId::of(utils::internal_lints::CLIPPY_LINTS_INTERNAL),
+ LintId::of(utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
+ LintId::of(utils::internal_lints::COMPILER_LINT_FUNCTIONS),
+ LintId::of(utils::internal_lints::DEFAULT_LINT),
+ LintId::of(utils::internal_lints::IF_CHAIN_STYLE),
+ LintId::of(utils::internal_lints::INTERNING_DEFINED_SYMBOL),
+ LintId::of(utils::internal_lints::INVALID_PATHS),
+ LintId::of(utils::internal_lints::LINT_WITHOUT_LINT_PASS),
+ LintId::of(utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM),
+ LintId::of(utils::internal_lints::OUTER_EXPN_EXPN_DATA),
+ LintId::of(utils::internal_lints::PRODUCE_ICE),
+ LintId::of(utils::internal_lints::UNNECESSARY_SYMBOL_STR),
+ ]);
+
+ store.register_group(true, "clippy::all", Some("clippy"), vec![
+ LintId::of(absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS),
+ LintId::of(approx_const::APPROX_CONSTANT),
+ LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
+ LintId::of(assign_ops::ASSIGN_OP_PATTERN),
+ LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP),
+ LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC),
+ LintId::of(atomic_ordering::INVALID_ATOMIC_ORDERING),
+ LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
+ LintId::of(attrs::DEPRECATED_CFG_ATTR),
+ LintId::of(attrs::DEPRECATED_SEMVER),
+ LintId::of(attrs::MISMATCHED_TARGET_OS),
+ LintId::of(attrs::USELESS_ATTRIBUTE),
+ LintId::of(bit_mask::BAD_BIT_MASK),
+ LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
+ LintId::of(blacklisted_name::BLACKLISTED_NAME),
+ LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
+ LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
+ LintId::of(booleans::LOGIC_BUG),
+ LintId::of(booleans::NONMINIMAL_BOOL),
+ LintId::of(casts::CAST_REF_TO_MUT),
+ LintId::of(casts::CHAR_LIT_AS_U8),
+ LintId::of(casts::FN_TO_NUMERIC_CAST),
+ LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
+ LintId::of(casts::UNNECESSARY_CAST),
+ LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
+ LintId::of(collapsible_if::COLLAPSIBLE_IF),
+ LintId::of(collapsible_match::COLLAPSIBLE_MATCH),
+ LintId::of(comparison_chain::COMPARISON_CHAIN),
+ LintId::of(copies::BRANCHES_SHARING_CODE),
+ LintId::of(copies::IFS_SAME_COND),
+ LintId::of(copies::IF_SAME_THEN_ELSE),
+ LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
+ LintId::of(derive::DERIVE_HASH_XOR_EQ),
+ LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD),
+ LintId::of(doc::MISSING_SAFETY_DOC),
+ LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
+ LintId::of(double_comparison::DOUBLE_COMPARISONS),
+ LintId::of(double_parens::DOUBLE_PARENS),
+ LintId::of(drop_forget_ref::DROP_COPY),
+ LintId::of(drop_forget_ref::DROP_REF),
+ LintId::of(drop_forget_ref::FORGET_COPY),
+ LintId::of(drop_forget_ref::FORGET_REF),
+ LintId::of(duration_subsec::DURATION_SUBSEC),
+ LintId::of(entry::MAP_ENTRY),
+ LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
+ LintId::of(enum_variants::ENUM_VARIANT_NAMES),
+ LintId::of(enum_variants::MODULE_INCEPTION),
+ LintId::of(eq_op::EQ_OP),
+ LintId::of(eq_op::OP_REF),
+ LintId::of(erasing_op::ERASING_OP),
+ LintId::of(escape::BOXED_LOCAL),
+ LintId::of(eta_reduction::REDUNDANT_CLOSURE),
+ LintId::of(eval_order_dependence::DIVERGING_SUB_EXPRESSION),
+ LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
+ LintId::of(explicit_write::EXPLICIT_WRITE),
+ LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
+ LintId::of(float_literal::EXCESSIVE_PRECISION),
+ LintId::of(format::USELESS_FORMAT),
+ LintId::of(formatting::POSSIBLE_MISSING_COMMA),
+ LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
+ LintId::of(from_over_into::FROM_OVER_INTO),
+ LintId::of(from_str_radix_10::FROM_STR_RADIX_10),
+ LintId::of(functions::DOUBLE_MUST_USE),
+ LintId::of(functions::MUST_USE_UNIT),
+ LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF),
+ LintId::of(functions::RESULT_UNIT_ERR),
+ LintId::of(functions::TOO_MANY_ARGUMENTS),
+ LintId::of(get_last_with_len::GET_LAST_WITH_LEN),
+ LintId::of(identity_op::IDENTITY_OP),
+ LintId::of(if_let_mutex::IF_LET_MUTEX),
+ LintId::of(if_let_some_result::IF_LET_SOME_RESULT),
+ LintId::of(inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR),
+ LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING),
+ LintId::of(infinite_iter::INFINITE_ITER),
+ LintId::of(inherent_to_string::INHERENT_TO_STRING),
+ LintId::of(inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY),
+ LintId::of(inline_fn_without_body::INLINE_FN_WITHOUT_BODY),
+ LintId::of(int_plus_one::INT_PLUS_ONE),
+ LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
+ LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
+ LintId::of(len_zero::COMPARISON_TO_EMPTY),
+ LintId::of(len_zero::LEN_WITHOUT_IS_EMPTY),
+ LintId::of(len_zero::LEN_ZERO),
+ LintId::of(let_underscore::LET_UNDERSCORE_LOCK),
+ LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
+ LintId::of(lifetimes::NEEDLESS_LIFETIMES),
+ LintId::of(literal_representation::INCONSISTENT_DIGIT_GROUPING),
+ LintId::of(literal_representation::MISTYPED_LITERAL_SUFFIXES),
+ LintId::of(literal_representation::UNUSUAL_BYTE_GROUPINGS),
+ LintId::of(loops::EMPTY_LOOP),
+ LintId::of(loops::EXPLICIT_COUNTER_LOOP),
+ LintId::of(loops::FOR_KV_MAP),
+ LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
+ LintId::of(loops::ITER_NEXT_LOOP),
+ LintId::of(loops::MANUAL_FLATTEN),
+ LintId::of(loops::MANUAL_MEMCPY),
+ LintId::of(loops::MUT_RANGE_BOUND),
+ LintId::of(loops::NEEDLESS_COLLECT),
+ LintId::of(loops::NEEDLESS_RANGE_LOOP),
+ LintId::of(loops::NEVER_LOOP),
+ LintId::of(loops::SAME_ITEM_PUSH),
+ LintId::of(loops::SINGLE_ELEMENT_LOOP),
+ LintId::of(loops::WHILE_IMMUTABLE_CONDITION),
+ LintId::of(loops::WHILE_LET_LOOP),
+ LintId::of(loops::WHILE_LET_ON_ITERATOR),
+ LintId::of(main_recursion::MAIN_RECURSION),
+ LintId::of(manual_async_fn::MANUAL_ASYNC_FN),
+ LintId::of(manual_map::MANUAL_MAP),
+ LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
+ LintId::of(manual_strip::MANUAL_STRIP),
+ LintId::of(manual_unwrap_or::MANUAL_UNWRAP_OR),
+ LintId::of(map_clone::MAP_CLONE),
+ LintId::of(map_identity::MAP_IDENTITY),
+ LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
+ LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
+ LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
+ LintId::of(matches::MATCH_AS_REF),
+ LintId::of(matches::MATCH_LIKE_MATCHES_MACRO),
+ LintId::of(matches::MATCH_OVERLAPPING_ARM),
+ LintId::of(matches::MATCH_REF_PATS),
+ LintId::of(matches::MATCH_SINGLE_BINDING),
+ LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
+ LintId::of(matches::SINGLE_MATCH),
+ LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
+ LintId::of(mem_discriminant::MEM_DISCRIMINANT_NON_ENUM),
+ LintId::of(mem_replace::MEM_REPLACE_OPTION_WITH_NONE),
+ LintId::of(mem_replace::MEM_REPLACE_WITH_DEFAULT),
+ LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT),
+ LintId::of(methods::BIND_INSTEAD_OF_MAP),
+ LintId::of(methods::BYTES_NTH),
+ LintId::of(methods::CHARS_LAST_CMP),
+ LintId::of(methods::CHARS_NEXT_CMP),
+ LintId::of(methods::CLONE_DOUBLE_REF),
+ LintId::of(methods::CLONE_ON_COPY),
+ LintId::of(methods::EXPECT_FUN_CALL),
+ LintId::of(methods::FILTER_MAP_IDENTITY),
+ LintId::of(methods::FILTER_NEXT),
+ LintId::of(methods::FLAT_MAP_IDENTITY),
+ LintId::of(methods::FROM_ITER_INSTEAD_OF_COLLECT),
+ LintId::of(methods::INSPECT_FOR_EACH),
+ LintId::of(methods::INTO_ITER_ON_REF),
+ LintId::of(methods::ITERATOR_STEP_BY_ZERO),
+ LintId::of(methods::ITER_CLONED_COLLECT),
+ LintId::of(methods::ITER_COUNT),
+ LintId::of(methods::ITER_NEXT_SLICE),
+ LintId::of(methods::ITER_NTH),
+ LintId::of(methods::ITER_NTH_ZERO),
+ LintId::of(methods::ITER_SKIP_NEXT),
+ LintId::of(methods::MANUAL_FILTER_MAP),
+ LintId::of(methods::MANUAL_FIND_MAP),
+ LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
+ LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
+ LintId::of(methods::NEW_RET_NO_SELF),
+ LintId::of(methods::OK_EXPECT),
+ LintId::of(methods::OPTION_AS_REF_DEREF),
+ LintId::of(methods::OPTION_FILTER_MAP),
+ LintId::of(methods::OPTION_MAP_OR_NONE),
+ LintId::of(methods::OR_FUN_CALL),
+ LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
+ LintId::of(methods::SEARCH_IS_SOME),
+ LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
+ LintId::of(methods::SINGLE_CHAR_ADD_STR),
+ LintId::of(methods::SINGLE_CHAR_PATTERN),
+ LintId::of(methods::SKIP_WHILE_NEXT),
+ LintId::of(methods::STRING_EXTEND_CHARS),
+ LintId::of(methods::SUSPICIOUS_MAP),
+ LintId::of(methods::UNINIT_ASSUMED_INIT),
+ LintId::of(methods::UNNECESSARY_FILTER_MAP),
+ LintId::of(methods::UNNECESSARY_FOLD),
+ LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS),
+ LintId::of(methods::USELESS_ASREF),
+ LintId::of(methods::WRONG_SELF_CONVENTION),
+ LintId::of(methods::ZST_OFFSET),
+ LintId::of(minmax::MIN_MAX),
+ LintId::of(misc::CMP_NAN),
+ LintId::of(misc::CMP_OWNED),
+ LintId::of(misc::FLOAT_CMP),
+ LintId::of(misc::MODULO_ONE),
+ LintId::of(misc::SHORT_CIRCUIT_STATEMENT),
+ LintId::of(misc::TOPLEVEL_REF_ARG),
+ LintId::of(misc::ZERO_PTR),
+ LintId::of(misc_early::BUILTIN_TYPE_SHADOW),
+ LintId::of(misc_early::DOUBLE_NEG),
+ LintId::of(misc_early::DUPLICATE_UNDERSCORE_ARGUMENT),
+ LintId::of(misc_early::MIXED_CASE_HEX_LITERALS),
+ LintId::of(misc_early::REDUNDANT_PATTERN),
+ LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN),
+ LintId::of(misc_early::ZERO_PREFIXED_LITERAL),
+ LintId::of(mut_key::MUTABLE_KEY_TYPE),
+ LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK),
+ LintId::of(mut_reference::UNNECESSARY_MUT_PASSED),
+ LintId::of(mutex_atomic::MUTEX_ATOMIC),
+ LintId::of(needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE),
+ LintId::of(needless_bool::BOOL_COMPARISON),
+ LintId::of(needless_bool::NEEDLESS_BOOL),
+ LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
+ 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),
+ LintId::of(neg_multiply::NEG_MULTIPLY),
+ LintId::of(new_without_default::NEW_WITHOUT_DEFAULT),
+ LintId::of(no_effect::NO_EFFECT),
+ LintId::of(no_effect::UNNECESSARY_OPERATION),
+ LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
+ LintId::of(non_expressive_names::MANY_SINGLE_CHAR_NAMES),
+ LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
+ LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
+ LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
+ LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
+ LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
+ LintId::of(precedence::PRECEDENCE),
+ LintId::of(ptr::CMP_NULL),
+ LintId::of(ptr::INVALID_NULL_PTR_USAGE),
+ LintId::of(ptr::MUT_FROM_REF),
+ LintId::of(ptr::PTR_ARG),
+ LintId::of(ptr_eq::PTR_EQ),
+ LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST),
+ LintId::of(question_mark::QUESTION_MARK),
+ LintId::of(ranges::MANUAL_RANGE_CONTAINS),
+ LintId::of(ranges::RANGE_ZIP_WITH_LEN),
+ LintId::of(ranges::REVERSED_EMPTY_RANGES),
+ LintId::of(redundant_clone::REDUNDANT_CLONE),
+ LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL),
+ LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES),
+ LintId::of(redundant_slicing::REDUNDANT_SLICING),
+ LintId::of(redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
+ LintId::of(reference::DEREF_ADDROF),
+ LintId::of(reference::REF_IN_DEREF),
+ LintId::of(regex::INVALID_REGEX),
+ LintId::of(repeat_once::REPEAT_ONCE),
+ LintId::of(returns::LET_AND_RETURN),
+ LintId::of(returns::NEEDLESS_RETURN),
+ LintId::of(self_assignment::SELF_ASSIGNMENT),
+ LintId::of(serde_api::SERDE_API_MISUSE),
+ LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
+ LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
+ LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
+ LintId::of(stable_sort_primitive::STABLE_SORT_PRIMITIVE),
+ LintId::of(strings::STRING_FROM_UTF8_AS_BYTES),
+ LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
+ LintId::of(swap::ALMOST_SWAPPED),
+ LintId::of(swap::MANUAL_SWAP),
+ LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
+ LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT),
+ LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME),
+ LintId::of(to_string_in_display::TO_STRING_IN_DISPLAY),
+ LintId::of(transmute::CROSSPOINTER_TRANSMUTE),
+ LintId::of(transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS),
+ LintId::of(transmute::TRANSMUTE_BYTES_TO_STR),
+ LintId::of(transmute::TRANSMUTE_FLOAT_TO_INT),
+ LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
+ LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
+ LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
+ LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
+ LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
+ LintId::of(transmute::WRONG_TRANSMUTE),
+ LintId::of(transmuting_null::TRANSMUTING_NULL),
+ LintId::of(try_err::TRY_ERR),
+ LintId::of(types::BORROWED_BOX),
+ LintId::of(types::BOX_VEC),
+ LintId::of(types::REDUNDANT_ALLOCATION),
+ LintId::of(types::TYPE_COMPLEXITY),
+ LintId::of(types::VEC_BOX),
+ LintId::of(undropped_manually_drops::UNDROPPED_MANUALLY_DROPS),
+ LintId::of(unicode::INVISIBLE_CHARACTERS),
+ LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD),
+ LintId::of(unit_types::UNIT_ARG),
+ LintId::of(unit_types::UNIT_CMP),
+ LintId::of(unnamed_address::FN_ADDRESS_COMPARISONS),
+ LintId::of(unnamed_address::VTABLE_ADDRESS_COMPARISONS),
+ LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY),
+ LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
+ LintId::of(unused_io_amount::UNUSED_IO_AMOUNT),
+ LintId::of(unused_unit::UNUSED_UNIT),
+ LintId::of(unwrap::PANICKING_UNWRAP),
+ LintId::of(unwrap::UNNECESSARY_UNWRAP),
+ LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS),
+ LintId::of(useless_conversion::USELESS_CONVERSION),
+ LintId::of(vec::USELESS_VEC),
+ LintId::of(vec_init_then_push::VEC_INIT_THEN_PUSH),
+ LintId::of(vec_resize_to_zero::VEC_RESIZE_TO_ZERO),
+ LintId::of(write::PRINTLN_EMPTY_STRING),
+ LintId::of(write::PRINT_LITERAL),
+ LintId::of(write::PRINT_WITH_NEWLINE),
+ LintId::of(write::WRITELN_EMPTY_STRING),
+ LintId::of(write::WRITE_LITERAL),
+ LintId::of(write::WRITE_WITH_NEWLINE),
+ LintId::of(zero_div_zero::ZERO_DIVIDED_BY_ZERO),
+ ]);
+
+ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
+ LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
+ LintId::of(assign_ops::ASSIGN_OP_PATTERN),
+ LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
+ LintId::of(blacklisted_name::BLACKLISTED_NAME),
+ LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
+ LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
+ LintId::of(casts::FN_TO_NUMERIC_CAST),
+ LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
+ LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
+ LintId::of(collapsible_if::COLLAPSIBLE_IF),
+ LintId::of(collapsible_match::COLLAPSIBLE_MATCH),
+ LintId::of(comparison_chain::COMPARISON_CHAIN),
+ LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
+ LintId::of(doc::MISSING_SAFETY_DOC),
+ LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
+ LintId::of(enum_variants::ENUM_VARIANT_NAMES),
+ LintId::of(enum_variants::MODULE_INCEPTION),
+ LintId::of(eq_op::OP_REF),
+ LintId::of(eta_reduction::REDUNDANT_CLOSURE),
+ LintId::of(float_literal::EXCESSIVE_PRECISION),
+ LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
+ LintId::of(from_over_into::FROM_OVER_INTO),
+ LintId::of(from_str_radix_10::FROM_STR_RADIX_10),
+ LintId::of(functions::DOUBLE_MUST_USE),
+ LintId::of(functions::MUST_USE_UNIT),
+ LintId::of(functions::RESULT_UNIT_ERR),
+ LintId::of(if_let_some_result::IF_LET_SOME_RESULT),
+ LintId::of(inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR),
+ LintId::of(inherent_to_string::INHERENT_TO_STRING),
+ LintId::of(len_zero::COMPARISON_TO_EMPTY),
+ LintId::of(len_zero::LEN_WITHOUT_IS_EMPTY),
+ LintId::of(len_zero::LEN_ZERO),
+ LintId::of(literal_representation::INCONSISTENT_DIGIT_GROUPING),
+ LintId::of(literal_representation::UNUSUAL_BYTE_GROUPINGS),
+ LintId::of(loops::EMPTY_LOOP),
+ LintId::of(loops::FOR_KV_MAP),
+ LintId::of(loops::NEEDLESS_RANGE_LOOP),
+ LintId::of(loops::SAME_ITEM_PUSH),
+ LintId::of(loops::WHILE_LET_ON_ITERATOR),
+ LintId::of(main_recursion::MAIN_RECURSION),
+ LintId::of(manual_async_fn::MANUAL_ASYNC_FN),
+ LintId::of(manual_map::MANUAL_MAP),
+ LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
+ LintId::of(map_clone::MAP_CLONE),
+ LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
+ LintId::of(matches::MATCH_LIKE_MATCHES_MACRO),
+ LintId::of(matches::MATCH_OVERLAPPING_ARM),
+ LintId::of(matches::MATCH_REF_PATS),
+ LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
+ LintId::of(matches::SINGLE_MATCH),
+ LintId::of(mem_replace::MEM_REPLACE_OPTION_WITH_NONE),
+ LintId::of(mem_replace::MEM_REPLACE_WITH_DEFAULT),
+ LintId::of(methods::BYTES_NTH),
+ LintId::of(methods::CHARS_LAST_CMP),
+ LintId::of(methods::CHARS_NEXT_CMP),
+ LintId::of(methods::FROM_ITER_INSTEAD_OF_COLLECT),
+ LintId::of(methods::INTO_ITER_ON_REF),
+ LintId::of(methods::ITER_CLONED_COLLECT),
+ LintId::of(methods::ITER_NEXT_SLICE),
+ LintId::of(methods::ITER_NTH_ZERO),
+ LintId::of(methods::ITER_SKIP_NEXT),
+ LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
+ LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
+ LintId::of(methods::NEW_RET_NO_SELF),
+ LintId::of(methods::OK_EXPECT),
+ LintId::of(methods::OPTION_MAP_OR_NONE),
+ LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
+ LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
+ LintId::of(methods::SINGLE_CHAR_ADD_STR),
+ LintId::of(methods::STRING_EXTEND_CHARS),
+ LintId::of(methods::UNNECESSARY_FOLD),
+ LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS),
+ LintId::of(methods::WRONG_SELF_CONVENTION),
+ LintId::of(misc::TOPLEVEL_REF_ARG),
+ LintId::of(misc::ZERO_PTR),
+ LintId::of(misc_early::BUILTIN_TYPE_SHADOW),
+ LintId::of(misc_early::DOUBLE_NEG),
+ LintId::of(misc_early::DUPLICATE_UNDERSCORE_ARGUMENT),
+ LintId::of(misc_early::MIXED_CASE_HEX_LITERALS),
+ LintId::of(misc_early::REDUNDANT_PATTERN),
+ LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK),
+ LintId::of(mut_reference::UNNECESSARY_MUT_PASSED),
+ LintId::of(neg_multiply::NEG_MULTIPLY),
+ LintId::of(new_without_default::NEW_WITHOUT_DEFAULT),
+ LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
+ LintId::of(non_expressive_names::MANY_SINGLE_CHAR_NAMES),
+ LintId::of(ptr::CMP_NULL),
+ LintId::of(ptr::PTR_ARG),
+ LintId::of(ptr_eq::PTR_EQ),
+ LintId::of(question_mark::QUESTION_MARK),
+ LintId::of(ranges::MANUAL_RANGE_CONTAINS),
+ LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES),
+ LintId::of(redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
+ LintId::of(returns::LET_AND_RETURN),
+ LintId::of(returns::NEEDLESS_RETURN),
+ LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
+ LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
+ LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
+ LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME),
+ LintId::of(try_err::TRY_ERR),
+ LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
+ LintId::of(unused_unit::UNUSED_UNIT),
+ LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS),
+ LintId::of(write::PRINTLN_EMPTY_STRING),
+ LintId::of(write::PRINT_LITERAL),
+ LintId::of(write::PRINT_WITH_NEWLINE),
+ LintId::of(write::WRITELN_EMPTY_STRING),
+ LintId::of(write::WRITE_LITERAL),
+ LintId::of(write::WRITE_WITH_NEWLINE),
+ ]);
+
+ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec![
+ LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP),
+ LintId::of(attrs::DEPRECATED_CFG_ATTR),
+ LintId::of(booleans::NONMINIMAL_BOOL),
+ LintId::of(casts::CHAR_LIT_AS_U8),
+ LintId::of(casts::UNNECESSARY_CAST),
+ LintId::of(copies::BRANCHES_SHARING_CODE),
+ LintId::of(double_comparison::DOUBLE_COMPARISONS),
+ LintId::of(double_parens::DOUBLE_PARENS),
+ LintId::of(duration_subsec::DURATION_SUBSEC),
+ LintId::of(eval_order_dependence::DIVERGING_SUB_EXPRESSION),
+ LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
+ LintId::of(explicit_write::EXPLICIT_WRITE),
+ LintId::of(format::USELESS_FORMAT),
+ LintId::of(functions::TOO_MANY_ARGUMENTS),
+ LintId::of(get_last_with_len::GET_LAST_WITH_LEN),
+ LintId::of(identity_op::IDENTITY_OP),
+ LintId::of(int_plus_one::INT_PLUS_ONE),
+ LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
+ LintId::of(lifetimes::NEEDLESS_LIFETIMES),
+ LintId::of(loops::EXPLICIT_COUNTER_LOOP),
+ LintId::of(loops::MANUAL_FLATTEN),
+ LintId::of(loops::MUT_RANGE_BOUND),
+ LintId::of(loops::SINGLE_ELEMENT_LOOP),
+ LintId::of(loops::WHILE_LET_LOOP),
+ LintId::of(manual_strip::MANUAL_STRIP),
+ LintId::of(manual_unwrap_or::MANUAL_UNWRAP_OR),
+ LintId::of(map_identity::MAP_IDENTITY),
+ LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
+ LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
+ LintId::of(matches::MATCH_AS_REF),
+ LintId::of(matches::MATCH_SINGLE_BINDING),
+ LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
+ LintId::of(methods::BIND_INSTEAD_OF_MAP),
+ LintId::of(methods::CLONE_ON_COPY),
+ LintId::of(methods::FILTER_MAP_IDENTITY),
+ LintId::of(methods::FILTER_NEXT),
+ LintId::of(methods::FLAT_MAP_IDENTITY),
+ LintId::of(methods::INSPECT_FOR_EACH),
+ LintId::of(methods::ITER_COUNT),
+ LintId::of(methods::MANUAL_FILTER_MAP),
+ LintId::of(methods::MANUAL_FIND_MAP),
+ LintId::of(methods::OPTION_AS_REF_DEREF),
+ LintId::of(methods::OPTION_FILTER_MAP),
+ LintId::of(methods::SEARCH_IS_SOME),
+ LintId::of(methods::SKIP_WHILE_NEXT),
+ LintId::of(methods::SUSPICIOUS_MAP),
+ LintId::of(methods::UNNECESSARY_FILTER_MAP),
+ LintId::of(methods::USELESS_ASREF),
+ LintId::of(misc::SHORT_CIRCUIT_STATEMENT),
+ LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN),
+ LintId::of(misc_early::ZERO_PREFIXED_LITERAL),
+ LintId::of(needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE),
+ LintId::of(needless_bool::BOOL_COMPARISON),
+ LintId::of(needless_bool::NEEDLESS_BOOL),
+ LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
+ 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),
+ LintId::of(no_effect::NO_EFFECT),
+ LintId::of(no_effect::UNNECESSARY_OPERATION),
+ LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
+ LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
+ LintId::of(precedence::PRECEDENCE),
+ LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST),
+ LintId::of(ranges::RANGE_ZIP_WITH_LEN),
+ LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL),
+ LintId::of(redundant_slicing::REDUNDANT_SLICING),
+ LintId::of(reference::DEREF_ADDROF),
+ LintId::of(reference::REF_IN_DEREF),
+ LintId::of(repeat_once::REPEAT_ONCE),
+ LintId::of(strings::STRING_FROM_UTF8_AS_BYTES),
+ LintId::of(swap::MANUAL_SWAP),
+ LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT),
+ LintId::of(transmute::CROSSPOINTER_TRANSMUTE),
+ LintId::of(transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS),
+ LintId::of(transmute::TRANSMUTE_BYTES_TO_STR),
+ LintId::of(transmute::TRANSMUTE_FLOAT_TO_INT),
+ LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
+ LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
+ LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
+ LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
+ LintId::of(types::BORROWED_BOX),
+ LintId::of(types::TYPE_COMPLEXITY),
+ LintId::of(types::VEC_BOX),
+ LintId::of(unit_types::UNIT_ARG),
+ LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY),
+ LintId::of(unwrap::UNNECESSARY_UNWRAP),
+ LintId::of(useless_conversion::USELESS_CONVERSION),
+ LintId::of(zero_div_zero::ZERO_DIVIDED_BY_ZERO),
+ ]);
+
+ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), vec![
+ LintId::of(absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS),
+ LintId::of(approx_const::APPROX_CONSTANT),
+ LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC),
+ LintId::of(atomic_ordering::INVALID_ATOMIC_ORDERING),
+ LintId::of(attrs::DEPRECATED_SEMVER),
+ LintId::of(attrs::MISMATCHED_TARGET_OS),
+ LintId::of(attrs::USELESS_ATTRIBUTE),
+ LintId::of(bit_mask::BAD_BIT_MASK),
+ LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
+ LintId::of(booleans::LOGIC_BUG),
+ LintId::of(casts::CAST_REF_TO_MUT),
+ LintId::of(copies::IFS_SAME_COND),
+ LintId::of(copies::IF_SAME_THEN_ELSE),
+ LintId::of(derive::DERIVE_HASH_XOR_EQ),
+ LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD),
+ LintId::of(drop_forget_ref::DROP_COPY),
+ LintId::of(drop_forget_ref::DROP_REF),
+ LintId::of(drop_forget_ref::FORGET_COPY),
+ LintId::of(drop_forget_ref::FORGET_REF),
+ LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
+ LintId::of(eq_op::EQ_OP),
+ LintId::of(erasing_op::ERASING_OP),
+ LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
+ LintId::of(formatting::POSSIBLE_MISSING_COMMA),
+ LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF),
+ LintId::of(if_let_mutex::IF_LET_MUTEX),
+ LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING),
+ LintId::of(infinite_iter::INFINITE_ITER),
+ LintId::of(inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY),
+ LintId::of(inline_fn_without_body::INLINE_FN_WITHOUT_BODY),
+ LintId::of(let_underscore::LET_UNDERSCORE_LOCK),
+ LintId::of(literal_representation::MISTYPED_LITERAL_SUFFIXES),
+ LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
+ LintId::of(loops::ITER_NEXT_LOOP),
+ LintId::of(loops::NEVER_LOOP),
+ LintId::of(loops::WHILE_IMMUTABLE_CONDITION),
+ LintId::of(mem_discriminant::MEM_DISCRIMINANT_NON_ENUM),
+ LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT),
+ LintId::of(methods::CLONE_DOUBLE_REF),
+ LintId::of(methods::ITERATOR_STEP_BY_ZERO),
+ LintId::of(methods::UNINIT_ASSUMED_INIT),
+ LintId::of(methods::ZST_OFFSET),
+ LintId::of(minmax::MIN_MAX),
+ LintId::of(misc::CMP_NAN),
+ LintId::of(misc::FLOAT_CMP),
+ LintId::of(misc::MODULO_ONE),
+ LintId::of(mut_key::MUTABLE_KEY_TYPE),
+ LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
+ LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
+ LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
+ LintId::of(ptr::INVALID_NULL_PTR_USAGE),
+ LintId::of(ptr::MUT_FROM_REF),
+ LintId::of(ranges::REVERSED_EMPTY_RANGES),
+ LintId::of(regex::INVALID_REGEX),
+ LintId::of(self_assignment::SELF_ASSIGNMENT),
+ LintId::of(serde_api::SERDE_API_MISUSE),
+ LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
+ LintId::of(swap::ALMOST_SWAPPED),
+ LintId::of(to_string_in_display::TO_STRING_IN_DISPLAY),
+ LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
+ LintId::of(transmute::WRONG_TRANSMUTE),
+ LintId::of(transmuting_null::TRANSMUTING_NULL),
+ LintId::of(undropped_manually_drops::UNDROPPED_MANUALLY_DROPS),
+ LintId::of(unicode::INVISIBLE_CHARACTERS),
+ LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD),
+ LintId::of(unit_types::UNIT_CMP),
+ LintId::of(unnamed_address::FN_ADDRESS_COMPARISONS),
+ LintId::of(unnamed_address::VTABLE_ADDRESS_COMPARISONS),
+ LintId::of(unused_io_amount::UNUSED_IO_AMOUNT),
+ LintId::of(unwrap::PANICKING_UNWRAP),
+ LintId::of(vec_resize_to_zero::VEC_RESIZE_TO_ZERO),
+ ]);
+
+ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
+ LintId::of(entry::MAP_ENTRY),
+ LintId::of(escape::BOXED_LOCAL),
+ LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
+ LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
+ LintId::of(loops::MANUAL_MEMCPY),
+ LintId::of(loops::NEEDLESS_COLLECT),
+ LintId::of(methods::EXPECT_FUN_CALL),
+ LintId::of(methods::ITER_NTH),
+ LintId::of(methods::OR_FUN_CALL),
+ LintId::of(methods::SINGLE_CHAR_PATTERN),
+ LintId::of(misc::CMP_OWNED),
+ LintId::of(mutex_atomic::MUTEX_ATOMIC),
+ LintId::of(redundant_clone::REDUNDANT_CLONE),
+ LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
+ LintId::of(stable_sort_primitive::STABLE_SORT_PRIMITIVE),
+ LintId::of(types::BOX_VEC),
+ LintId::of(types::REDUNDANT_ALLOCATION),
+ LintId::of(vec::USELESS_VEC),
+ LintId::of(vec_init_then_push::VEC_INIT_THEN_PUSH),
+ ]);
+
+ store.register_group(true, "clippy::cargo", Some("clippy_cargo"), vec![
+ LintId::of(cargo_common_metadata::CARGO_COMMON_METADATA),
+ LintId::of(multiple_crate_versions::MULTIPLE_CRATE_VERSIONS),
+ LintId::of(wildcard_dependencies::WILDCARD_DEPENDENCIES),
+ ]);
+
+ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
+ LintId::of(attrs::EMPTY_LINE_AFTER_OUTER_ATTR),
+ LintId::of(cognitive_complexity::COGNITIVE_COMPLEXITY),
+ LintId::of(disallowed_method::DISALLOWED_METHOD),
+ LintId::of(fallible_impl_from::FALLIBLE_IMPL_FROM),
+ LintId::of(floating_point_arithmetic::IMPRECISE_FLOPS),
+ LintId::of(floating_point_arithmetic::SUBOPTIMAL_FLOPS),
+ LintId::of(future_not_send::FUTURE_NOT_SEND),
+ LintId::of(let_if_seq::USELESS_LET_IF_SEQ),
+ LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN),
+ LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),
+ LintId::of(mutex_atomic::MUTEX_INTEGER),
+ LintId::of(needless_borrow::NEEDLESS_BORROW),
+ LintId::of(path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE),
+ LintId::of(redundant_pub_crate::REDUNDANT_PUB_CRATE),
+ LintId::of(regex::TRIVIAL_REGEX),
+ LintId::of(strings::STRING_LIT_AS_BYTES),
+ LintId::of(transmute::USELESS_TRANSMUTE),
+ LintId::of(use_self::USE_SELF),
+ ]);
+}
+
+#[rustfmt::skip]
+fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) {
+ store.register_removed(
+ "should_assert_eq",
+ "`assert!()` will be more flexible with RFC 2011",
+ );
+ store.register_removed(
+ "extend_from_slice",
+ "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice",
+ );
+ store.register_removed(
+ "range_step_by_zero",
+ "`iterator.step_by(0)` panics nowadays",
+ );
+ store.register_removed(
+ "unstable_as_slice",
+ "`Vec::as_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "unstable_as_mut_slice",
+ "`Vec::as_mut_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "misaligned_transmute",
+ "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr",
+ );
+ store.register_removed(
+ "assign_ops",
+ "using compound assignment operators (e.g., `+=`) is harmless",
+ );
+ store.register_removed(
+ "if_let_redundant_pattern_matching",
+ "this lint has been changed to redundant_pattern_matching",
+ );
+ store.register_removed(
+ "unsafe_vector_initialization",
+ "the replacement suggested by this lint had substantially different behavior",
+ );
+ store.register_removed(
+ "reverse_range_loop",
+ "this lint is now included in reversed_empty_ranges",
+ );
+}
+
+/// Register renamed lints.
+///
+/// Used in `./src/driver.rs`.
+pub fn register_renamed(ls: &mut rustc_lint::LintStore) {
+ ls.register_renamed("clippy::stutter", "clippy::module_name_repetitions");
+ ls.register_renamed("clippy::new_without_default_derive", "clippy::new_without_default");
+ ls.register_renamed("clippy::cyclomatic_complexity", "clippy::cognitive_complexity");
+ ls.register_renamed("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes");
+ ls.register_renamed("clippy::option_and_then_some", "clippy::bind_instead_of_map");
+ ls.register_renamed("clippy::block_in_if_condition_expr", "clippy::blocks_in_if_conditions");
+ ls.register_renamed("clippy::block_in_if_condition_stmt", "clippy::blocks_in_if_conditions");
+ ls.register_renamed("clippy::option_map_unwrap_or", "clippy::map_unwrap_or");
+ ls.register_renamed("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or");
+ ls.register_renamed("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or");
+ ls.register_renamed("clippy::option_unwrap_used", "clippy::unwrap_used");
+ ls.register_renamed("clippy::result_unwrap_used", "clippy::unwrap_used");
+ ls.register_renamed("clippy::option_expect_used", "clippy::expect_used");
+ ls.register_renamed("clippy::result_expect_used", "clippy::expect_used");
+ ls.register_renamed("clippy::for_loop_over_option", "clippy::for_loops_over_fallibles");
+ ls.register_renamed("clippy::for_loop_over_result", "clippy::for_loops_over_fallibles");
+ ls.register_renamed("clippy::identity_conversion", "clippy::useless_conversion");
+ ls.register_renamed("clippy::zero_width_space", "clippy::invisible_characters");
+ ls.register_renamed("clippy::single_char_push_str", "clippy::single_char_add_str");
+
+ // uplifted lints
+ ls.register_renamed("clippy::invalid_ref", "invalid_value");
+ ls.register_renamed("clippy::into_iter_on_array", "array_into_iter");
+ ls.register_renamed("clippy::unused_label", "unused_labels");
+ ls.register_renamed("clippy::drop_bounds", "drop_bounds");
+ ls.register_renamed("clippy::temporary_cstring_as_ptr", "temporary_cstring_as_ptr");
+ ls.register_renamed("clippy::panic_params", "non_fmt_panic");
+ ls.register_renamed("clippy::unknown_clippy_lints", "unknown_lints");
+}
+
+// only exists to let the dogfood integration test works.
+// Don't run clippy as an executable directly
+#[allow(dead_code)]
+fn main() {
+ panic!("Please use the cargo-clippy executable");
+}
--- /dev/null
- use rustc_hir::{Block, Expr, ExprKind, GenericArg, HirId, Local, Pat, PatKind, QPath, StmtKind};
+use super::NEEDLESS_COLLECT;
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::{is_type_diagnostic_item, match_type};
+use clippy_utils::{is_trait_method, path_to_local_id, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
- let ty = cx.typeck_results().node_type(ty.hir_id);
++use rustc_hir::{Block, Expr, ExprKind, GenericArg, GenericArgs, HirId, Local, Pat, PatKind, QPath, StmtKind, Ty};
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
++
+use rustc_span::symbol::{sym, Ident};
+use rustc_span::{MultiSpan, Span};
+
+const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
+
+pub(super) fn check<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
+ check_needless_collect_direct_usage(expr, cx);
+ check_needless_collect_indirect_usage(expr, cx);
+}
+fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
+ if_chain! {
+ if let ExprKind::MethodCall(method, _, args, _) = expr.kind;
+ if let ExprKind::MethodCall(chain_method, method0_span, _, _) = args[0].kind;
+ if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator);
+ if let Some(generic_args) = chain_method.args;
+ if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
- init: Some(init_expr), .. }
++ if let Some(ty) = cx.typeck_results().node_type_opt(ty.hir_id);
+ if is_type_diagnostic_item(cx, ty, sym::vec_type)
+ || is_type_diagnostic_item(cx, ty, sym::vecdeque_type)
+ || match_type(cx, ty, &paths::BTREEMAP)
+ || is_type_diagnostic_item(cx, ty, sym::hashmap_type);
+ if let Some(sugg) = match &*method.ident.name.as_str() {
+ "len" => Some("count()".to_string()),
+ "is_empty" => Some("next().is_none()".to_string()),
+ "contains" => {
+ let contains_arg = snippet(cx, args[1].span, "??");
+ let (arg, pred) = contains_arg
+ .strip_prefix('&')
+ .map_or(("&x", &*contains_arg), |s| ("x", s));
+ Some(format!("any(|{}| x == {})", arg, pred))
+ }
+ _ => None,
+ };
+ then {
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_COLLECT,
+ method0_span.with_hi(expr.span.hi()),
+ NEEDLESS_COLLECT_MSG,
+ "replace with",
+ sugg,
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
++ fn get_hir_id<'tcx>(ty: Option<&Ty<'tcx>>, method_args: Option<&GenericArgs<'tcx>>) -> Option<HirId> {
++ if let Some(ty) = ty {
++ return Some(ty.hir_id);
++ }
++
++ if let Some(generic_args) = method_args {
++ if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0) {
++ return Some(ty.hir_id);
++ }
++ }
++
++ None
++ }
+ if let ExprKind::Block(block, _) = expr.kind {
+ for stmt in block.stmts {
+ if_chain! {
+ if let StmtKind::Local(
+ Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. },
- if let Some(generic_args) = method_name.args;
- if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
- if let ty = cx.typeck_results().node_type(ty.hir_id);
++ init: Some(init_expr), ty, .. }
+ ) = stmt.kind;
+ if let ExprKind::MethodCall(method_name, collect_span, &[ref iter_source], ..) = init_expr.kind;
+ if method_name.ident.name == sym!(collect) && is_trait_method(cx, init_expr, sym::Iterator);
++ if let Some(hir_id) = get_hir_id(*ty, method_name.args);
++ if let Some(ty) = cx.typeck_results().node_type_opt(hir_id);
+ if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
+ is_type_diagnostic_item(cx, ty, sym::vecdeque_type) ||
++ is_type_diagnostic_item(cx, ty, sym::BinaryHeap) ||
+ match_type(cx, ty, &paths::LINKED_LIST);
+ if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident);
+ if let [iter_call] = &*iter_calls;
+ then {
+ let mut used_count_visitor = UsedCountVisitor {
+ cx,
+ id: *pat_id,
+ count: 0,
+ };
+ walk_block(&mut used_count_visitor, block);
+ if used_count_visitor.count > 1 {
+ return;
+ }
+
+ // Suggest replacing iter_call with iter_replacement, and removing stmt
+ let mut span = MultiSpan::from_span(collect_span);
+ span.push_span_label(iter_call.span, "the iterator could be used here instead".into());
+ span_lint_and_then(
+ cx,
+ super::NEEDLESS_COLLECT,
+ span,
+ NEEDLESS_COLLECT_MSG,
+ |diag| {
+ let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx));
+ diag.multipart_suggestion(
+ iter_call.get_suggestion_text(),
+ vec![
+ (stmt.span, String::new()),
+ (iter_call.span, iter_replacement)
+ ],
+ Applicability::MachineApplicable,// MaybeIncorrect,
+ );
+ },
+ );
+ }
+ }
+ }
+ }
+}
+
+struct IterFunction {
+ func: IterFunctionKind,
+ span: Span,
+}
+impl IterFunction {
+ fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
+ match &self.func {
+ IterFunctionKind::IntoIter => String::new(),
+ IterFunctionKind::Len => String::from(".count()"),
+ IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
+ IterFunctionKind::Contains(span) => {
+ let s = snippet(cx, *span, "..");
+ if let Some(stripped) = s.strip_prefix('&') {
+ format!(".any(|x| x == {})", stripped)
+ } else {
+ format!(".any(|x| x == *{})", s)
+ }
+ },
+ }
+ }
+ fn get_suggestion_text(&self) -> &'static str {
+ match &self.func {
+ IterFunctionKind::IntoIter => {
+ "use the original Iterator instead of collecting it and then producing a new one"
+ },
+ IterFunctionKind::Len => {
+ "take the original Iterator's count instead of collecting it and finding the length"
+ },
+ IterFunctionKind::IsEmpty => {
+ "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
+ },
+ IterFunctionKind::Contains(_) => {
+ "check if the original Iterator contains an element instead of collecting then checking"
+ },
+ }
+ }
+}
+enum IterFunctionKind {
+ IntoIter,
+ Len,
+ IsEmpty,
+ Contains(Span),
+}
+
+struct IterFunctionVisitor {
+ uses: Vec<IterFunction>,
+ seen_other: bool,
+ target: Ident,
+}
+impl<'tcx> Visitor<'tcx> for IterFunctionVisitor {
+ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+ // Check function calls on our collection
+ if_chain! {
+ if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind;
+ if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. }) = args.get(0);
+ if let &[name] = &path.segments;
+ if name.ident == self.target;
+ then {
+ let len = sym!(len);
+ let is_empty = sym!(is_empty);
+ let contains = sym!(contains);
+ match method_name.ident.name {
+ sym::into_iter => self.uses.push(
+ IterFunction { func: IterFunctionKind::IntoIter, span: expr.span }
+ ),
+ name if name == len => self.uses.push(
+ IterFunction { func: IterFunctionKind::Len, span: expr.span }
+ ),
+ name if name == is_empty => self.uses.push(
+ IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span }
+ ),
+ name if name == contains => self.uses.push(
+ IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span }
+ ),
+ _ => self.seen_other = true,
+ }
+ return
+ }
+ }
+ // Check if the collection is used for anything else
+ if_chain! {
+ if let Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. } = expr;
+ if let &[name] = &path.segments;
+ if name.ident == self.target;
+ then {
+ self.seen_other = true;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+ }
+
+ type Map = Map<'tcx>;
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+struct UsedCountVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ id: HirId,
+ count: usize,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if path_to_local_id(expr, self.id) {
+ self.count += 1;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
+}
+
+/// Detect the occurrences of calls to `iter` or `into_iter` for the
+/// given identifier
+fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option<Vec<IterFunction>> {
+ let mut visitor = IterFunctionVisitor {
+ uses: Vec::new(),
+ target: identifier,
+ seen_other: false,
+ };
+ visitor.visit_block(block);
+ if visitor.seen_other { None } else { Some(visitor.uses) }
+}
--- /dev/null
- let no_cond_variable_mutated = if let Some(used_mutably) = mutated_variables(expr, cx) {
- used_in_condition.is_disjoint(&used_mutably)
- } else {
- return;
- };
+use super::WHILE_IMMUTABLE_CONDITION;
+use crate::consts::constant;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::usage::mutated_variables;
+use if_chain::if_chain;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefIdMap;
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::HirIdSet;
+use rustc_hir::{Expr, ExprKind, QPath};
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
+
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) {
+ if constant(cx, cx.typeck_results(), cond).is_some() {
+ // A pure constant condition (e.g., `while false`) is not linted.
+ return;
+ }
+
+ let mut var_visitor = VarCollectorVisitor {
+ cx,
+ ids: HirIdSet::default(),
+ def_ids: DefIdMap::default(),
+ skip: false,
+ };
+ var_visitor.visit_expr(cond);
+ if var_visitor.skip {
+ return;
+ }
+ let used_in_condition = &var_visitor.ids;
++ let mutated_in_body = mutated_variables(expr, cx);
++ let mutated_in_condition = mutated_variables(cond, cx);
++ let no_cond_variable_mutated =
++ if let (Some(used_mutably_body), Some(used_mutably_cond)) = (mutated_in_body, mutated_in_condition) {
++ used_in_condition.is_disjoint(&used_mutably_body) && used_in_condition.is_disjoint(&used_mutably_cond)
++ } else {
++ return;
++ };
+ let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v);
+
+ let mut has_break_or_return_visitor = HasBreakOrReturnVisitor {
+ has_break_or_return: false,
+ };
+ has_break_or_return_visitor.visit_expr(expr);
+ let has_break_or_return = has_break_or_return_visitor.has_break_or_return;
+
+ if no_cond_variable_mutated && !mutable_static_in_cond {
+ span_lint_and_then(
+ cx,
+ WHILE_IMMUTABLE_CONDITION,
+ cond.span,
+ "variables in the condition are not mutated in the loop body",
+ |diag| {
+ diag.note("this may lead to an infinite or to a never running loop");
+
+ if has_break_or_return {
+ diag.note("this loop contains `return`s or `break`s");
+ diag.help("rewrite it as `if cond { loop { } }`");
+ }
+ },
+ );
+ }
+}
+
+struct HasBreakOrReturnVisitor {
+ has_break_or_return: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for HasBreakOrReturnVisitor {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.has_break_or_return {
+ return;
+ }
+
+ match expr.kind {
+ ExprKind::Ret(_) | ExprKind::Break(_, _) => {
+ self.has_break_or_return = true;
+ return;
+ },
+ _ => {},
+ }
+
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// Collects the set of variables in an expression
+/// Stops analysis if a function call is found
+/// Note: In some cases such as `self`, there are no mutable annotation,
+/// All variables definition IDs are collected
+struct VarCollectorVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ ids: HirIdSet,
+ def_ids: DefIdMap<bool>,
+ skip: bool,
+}
+
+impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> {
+ fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Path(ref qpath) = ex.kind;
+ if let QPath::Resolved(None, _) = *qpath;
+ then {
+ match self.cx.qpath_res(qpath, ex.hir_id) {
+ Res::Local(hir_id) => {
+ self.ids.insert(hir_id);
+ },
+ Res::Def(DefKind::Static, def_id) => {
+ let mutable = self.cx.tcx.is_mutable_static(def_id);
+ self.def_ids.insert(def_id, mutable);
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
+ match ex.kind {
+ ExprKind::Path(_) => self.insert_def_id(ex),
+ // If there is any function/method call… we just stop analysis
+ ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true,
+
+ _ => walk_expr(self, ex),
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
--- /dev/null
- if let PatKind::TupleStruct(ref qpath, pats, _) = arm.pat.kind;
+use crate::consts::{constant, miri_to_const, Constant};
+use clippy_utils::diagnostics::{
+ multispan_sugg, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
+};
+use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs};
+use clippy_utils::visitors::LocalUsedVisitor;
+use clippy_utils::{
+ get_parent_expr, in_macro, is_allowed, is_expn_of, is_lang_ctor, is_refutable, is_wild, meets_msrv, msrvs,
+ path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, remove_blocks,
+ strip_pat_refs,
+};
+use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::def::{CtorKind, DefKind, Res};
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_hir::{
+ self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource,
+ Mutability, Node, Pat, PatKind, PathSegment, QPath, RangeEnd, TyKind,
+};
+use rustc_hir::{HirIdMap, HirIdSet};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, Ty, TyS, VariantDef};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::{Span, Spanned};
+use rustc_span::sym;
+use std::cmp::Ordering;
+use std::collections::hash_map::Entry;
+use std::iter;
+use std::ops::Bound;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for matches with a single arm where an `if let`
+ /// will usually suffice.
+ ///
+ /// **Why is this bad?** Just readability – `if let` nests less than a `match`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # fn bar(stool: &str) {}
+ /// # let x = Some("abc");
+ /// // Bad
+ /// match x {
+ /// Some(ref foo) => bar(foo),
+ /// _ => (),
+ /// }
+ ///
+ /// // Good
+ /// if let Some(ref foo) = x {
+ /// bar(foo);
+ /// }
+ /// ```
+ pub SINGLE_MATCH,
+ style,
+ "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for matches with two arms where an `if let else` will
+ /// usually suffice.
+ ///
+ /// **Why is this bad?** Just readability – `if let` nests less than a `match`.
+ ///
+ /// **Known problems:** Personal style preferences may differ.
+ ///
+ /// **Example:**
+ ///
+ /// Using `match`:
+ ///
+ /// ```rust
+ /// # fn bar(foo: &usize) {}
+ /// # let other_ref: usize = 1;
+ /// # let x: Option<&usize> = Some(&1);
+ /// match x {
+ /// Some(ref foo) => bar(foo),
+ /// _ => bar(&other_ref),
+ /// }
+ /// ```
+ ///
+ /// Using `if let` with `else`:
+ ///
+ /// ```rust
+ /// # fn bar(foo: &usize) {}
+ /// # let other_ref: usize = 1;
+ /// # let x: Option<&usize> = Some(&1);
+ /// if let Some(ref foo) = x {
+ /// bar(foo);
+ /// } else {
+ /// bar(&other_ref);
+ /// }
+ /// ```
+ pub SINGLE_MATCH_ELSE,
+ pedantic,
+ "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for matches where all arms match a reference,
+ /// suggesting to remove the reference and deref the matched expression
+ /// instead. It also checks for `if let &foo = bar` blocks.
+ ///
+ /// **Why is this bad?** It just makes the code less readable. That reference
+ /// destructuring adds nothing to the code.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// // Bad
+ /// match x {
+ /// &A(ref y) => foo(y),
+ /// &B => bar(),
+ /// _ => frob(&x),
+ /// }
+ ///
+ /// // Good
+ /// match *x {
+ /// A(ref y) => foo(y),
+ /// B => bar(),
+ /// _ => frob(x),
+ /// }
+ /// ```
+ pub MATCH_REF_PATS,
+ style,
+ "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for matches where match expression is a `bool`. It
+ /// suggests to replace the expression with an `if...else` block.
+ ///
+ /// **Why is this bad?** It makes the code less readable.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # fn foo() {}
+ /// # fn bar() {}
+ /// let condition: bool = true;
+ /// match condition {
+ /// true => foo(),
+ /// false => bar(),
+ /// }
+ /// ```
+ /// Use if/else instead:
+ /// ```rust
+ /// # fn foo() {}
+ /// # fn bar() {}
+ /// let condition: bool = true;
+ /// if condition {
+ /// foo();
+ /// } else {
+ /// bar();
+ /// }
+ /// ```
+ pub MATCH_BOOL,
+ pedantic,
+ "a `match` on a boolean expression instead of an `if..else` block"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for overlapping match arms.
+ ///
+ /// **Why is this bad?** It is likely to be an error and if not, makes the code
+ /// less obvious.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let x = 5;
+ /// match x {
+ /// 1...10 => println!("1 ... 10"),
+ /// 5...15 => println!("5 ... 15"),
+ /// _ => (),
+ /// }
+ /// ```
+ pub MATCH_OVERLAPPING_ARM,
+ style,
+ "a `match` with overlapping arms"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for arm which matches all errors with `Err(_)`
+ /// and take drastic actions like `panic!`.
+ ///
+ /// **Why is this bad?** It is generally a bad practice, similar to
+ /// catching all exceptions in java with `catch(Exception)`
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let x: Result<i32, &str> = Ok(3);
+ /// match x {
+ /// Ok(_) => println!("ok"),
+ /// Err(_) => panic!("err"),
+ /// }
+ /// ```
+ pub MATCH_WILD_ERR_ARM,
+ pedantic,
+ "a `match` with `Err(_)` arm and take drastic actions"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for match which is used to add a reference to an
+ /// `Option` value.
+ ///
+ /// **Why is this bad?** Using `as_ref()` or `as_mut()` instead is shorter.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let x: Option<()> = None;
+ ///
+ /// // Bad
+ /// let r: Option<&()> = match x {
+ /// None => None,
+ /// Some(ref v) => Some(v),
+ /// };
+ ///
+ /// // Good
+ /// let r: Option<&()> = x.as_ref();
+ /// ```
+ pub MATCH_AS_REF,
+ complexity,
+ "a `match` on an Option value instead of using `as_ref()` or `as_mut`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for wildcard enum matches using `_`.
+ ///
+ /// **Why is this bad?** New enum variants added by library updates can be missed.
+ ///
+ /// **Known problems:** Suggested replacements may be incorrect if guards exhaustively cover some
+ /// variants, and also may not use correct path to enum if it's not present in the current scope.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # enum Foo { A(usize), B(usize) }
+ /// # let x = Foo::B(1);
+ /// // Bad
+ /// match x {
+ /// Foo::A(_) => {},
+ /// _ => {},
+ /// }
+ ///
+ /// // Good
+ /// match x {
+ /// Foo::A(_) => {},
+ /// Foo::B(_) => {},
+ /// }
+ /// ```
+ pub WILDCARD_ENUM_MATCH_ARM,
+ restriction,
+ "a wildcard enum match arm using `_`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for wildcard enum matches for a single variant.
+ ///
+ /// **Why is this bad?** New enum variants added by library updates can be missed.
+ ///
+ /// **Known problems:** Suggested replacements may not use correct path to enum
+ /// if it's not present in the current scope.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # enum Foo { A, B, C }
+ /// # let x = Foo::B;
+ /// // Bad
+ /// match x {
+ /// Foo::A => {},
+ /// Foo::B => {},
+ /// _ => {},
+ /// }
+ ///
+ /// // Good
+ /// match x {
+ /// Foo::A => {},
+ /// Foo::B => {},
+ /// Foo::C => {},
+ /// }
+ /// ```
+ pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
+ pedantic,
+ "a wildcard enum match for a single variant"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for wildcard pattern used with others patterns in same match arm.
+ ///
+ /// **Why is this bad?** Wildcard pattern already covers any other pattern as it will match anyway.
+ /// It makes the code less readable, especially to spot wildcard pattern use in match arm.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// // Bad
+ /// match "foo" {
+ /// "a" => {},
+ /// "bar" | _ => {},
+ /// }
+ ///
+ /// // Good
+ /// match "foo" {
+ /// "a" => {},
+ /// _ => {},
+ /// }
+ /// ```
+ pub WILDCARD_IN_OR_PATTERNS,
+ complexity,
+ "a wildcard pattern used with others patterns in same match arm"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for matches being used to destructure a single-variant enum
+ /// or tuple struct where a `let` will suffice.
+ ///
+ /// **Why is this bad?** Just readability – `let` doesn't nest, whereas a `match` does.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// enum Wrapper {
+ /// Data(i32),
+ /// }
+ ///
+ /// let wrapper = Wrapper::Data(42);
+ ///
+ /// let data = match wrapper {
+ /// Wrapper::Data(i) => i,
+ /// };
+ /// ```
+ ///
+ /// The correct use would be:
+ /// ```rust
+ /// enum Wrapper {
+ /// Data(i32),
+ /// }
+ ///
+ /// let wrapper = Wrapper::Data(42);
+ /// let Wrapper::Data(data) = wrapper;
+ /// ```
+ pub INFALLIBLE_DESTRUCTURING_MATCH,
+ style,
+ "a `match` statement with a single infallible arm instead of a `let`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for useless match that binds to only one value.
+ ///
+ /// **Why is this bad?** Readability and needless complexity.
+ ///
+ /// **Known problems:** Suggested replacements may be incorrect when `match`
+ /// is actually binding temporary value, bringing a 'dropped while borrowed' error.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let a = 1;
+ /// # let b = 2;
+ ///
+ /// // Bad
+ /// match (a, b) {
+ /// (c, d) => {
+ /// // useless match
+ /// }
+ /// }
+ ///
+ /// // Good
+ /// let (c, d) = (a, b);
+ /// ```
+ pub MATCH_SINGLE_BINDING,
+ complexity,
+ "a match with a single binding instead of using `let` statement"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched.
+ ///
+ /// **Why is this bad?** Correctness and readability. It's like having a wildcard pattern after
+ /// matching all enum variants explicitly.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # struct A { a: i32 }
+ /// let a = A { a: 5 };
+ ///
+ /// // Bad
+ /// match a {
+ /// A { a: 5, .. } => {},
+ /// _ => {},
+ /// }
+ ///
+ /// // Good
+ /// match a {
+ /// A { a: 5 } => {},
+ /// _ => {},
+ /// }
+ /// ```
+ pub REST_PAT_IN_FULLY_BOUND_STRUCTS,
+ restriction,
+ "a match on a struct that binds all fields but still uses the wildcard pattern"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Lint for redundant pattern matching over `Result`, `Option`,
+ /// `std::task::Poll` or `std::net::IpAddr`
+ ///
+ /// **Why is this bad?** It's more concise and clear to just use the proper
+ /// utility function
+ ///
+ /// **Known problems:** This will change the drop order for the matched type. Both `if let` and
+ /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
+ /// value before entering the block. For most types this change will not matter, but for a few
+ /// types this will not be an acceptable change (e.g. locks). See the
+ /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
+ /// drop order.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # use std::task::Poll;
+ /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ /// if let Ok(_) = Ok::<i32, i32>(42) {}
+ /// if let Err(_) = Err::<i32, i32>(42) {}
+ /// if let None = None::<()> {}
+ /// if let Some(_) = Some(42) {}
+ /// if let Poll::Pending = Poll::Pending::<()> {}
+ /// if let Poll::Ready(_) = Poll::Ready(42) {}
+ /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {}
+ /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {}
+ /// match Ok::<i32, i32>(42) {
+ /// Ok(_) => true,
+ /// Err(_) => false,
+ /// };
+ /// ```
+ ///
+ /// The more idiomatic use would be:
+ ///
+ /// ```rust
+ /// # use std::task::Poll;
+ /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ /// if Ok::<i32, i32>(42).is_ok() {}
+ /// if Err::<i32, i32>(42).is_err() {}
+ /// if None::<()>.is_none() {}
+ /// if Some(42).is_some() {}
+ /// if Poll::Pending::<()>.is_pending() {}
+ /// if Poll::Ready(42).is_ready() {}
+ /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
+ /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
+ /// Ok::<i32, i32>(42).is_ok();
+ /// ```
+ pub REDUNDANT_PATTERN_MATCHING,
+ style,
+ "use the proper utility function avoiding an `if let`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `match` or `if let` expressions producing a
+ /// `bool` that could be written using `matches!`
+ ///
+ /// **Why is this bad?** Readability and needless complexity.
+ ///
+ /// **Known problems:** This lint falsely triggers, if there are arms with
+ /// `cfg` attributes that remove an arm evaluating to `false`.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let x = Some(5);
+ ///
+ /// // Bad
+ /// let a = match x {
+ /// Some(0) => true,
+ /// _ => false,
+ /// };
+ ///
+ /// let a = if let Some(0) = x {
+ /// true
+ /// } else {
+ /// false
+ /// };
+ ///
+ /// // Good
+ /// let a = matches!(x, Some(0));
+ /// ```
+ pub MATCH_LIKE_MATCHES_MACRO,
+ style,
+ "a match that could be written with the matches! macro"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `match` with identical arm bodies.
+ ///
+ /// **Why is this bad?** This is probably a copy & paste error. If arm bodies
+ /// are the same on purpose, you can factor them
+ /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns).
+ ///
+ /// **Known problems:** False positive possible with order dependent `match`
+ /// (see issue
+ /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)).
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// match foo {
+ /// Bar => bar(),
+ /// Quz => quz(),
+ /// Baz => bar(), // <= oops
+ /// }
+ /// ```
+ ///
+ /// This should probably be
+ /// ```rust,ignore
+ /// match foo {
+ /// Bar => bar(),
+ /// Quz => quz(),
+ /// Baz => baz(), // <= fixed
+ /// }
+ /// ```
+ ///
+ /// or if the original code was not a typo:
+ /// ```rust,ignore
+ /// match foo {
+ /// Bar | Baz => bar(), // <= shows the intent better
+ /// Quz => quz(),
+ /// }
+ /// ```
+ pub MATCH_SAME_ARMS,
+ pedantic,
+ "`match` with identical arm bodies"
+}
+
+#[derive(Default)]
+pub struct Matches {
+ msrv: Option<RustcVersion>,
+ infallible_destructuring_match_linted: bool,
+}
+
+impl Matches {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self {
+ msrv,
+ ..Matches::default()
+ }
+ }
+}
+
+impl_lint_pass!(Matches => [
+ SINGLE_MATCH,
+ MATCH_REF_PATS,
+ MATCH_BOOL,
+ SINGLE_MATCH_ELSE,
+ MATCH_OVERLAPPING_ARM,
+ MATCH_WILD_ERR_ARM,
+ MATCH_AS_REF,
+ WILDCARD_ENUM_MATCH_ARM,
+ MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
+ WILDCARD_IN_OR_PATTERNS,
+ MATCH_SINGLE_BINDING,
+ INFALLIBLE_DESTRUCTURING_MATCH,
+ REST_PAT_IN_FULLY_BOUND_STRUCTS,
+ REDUNDANT_PATTERN_MATCHING,
+ MATCH_LIKE_MATCHES_MACRO,
+ MATCH_SAME_ARMS,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Matches {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if in_external_macro(cx.sess(), expr.span) || in_macro(expr.span) {
+ return;
+ }
+
+ redundant_pattern_match::check(cx, expr);
+
+ if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) {
+ if !check_match_like_matches(cx, expr) {
+ lint_match_arms(cx, expr);
+ }
+ } else {
+ lint_match_arms(cx, expr);
+ }
+
+ if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind {
+ check_single_match(cx, ex, arms, expr);
+ check_match_bool(cx, ex, arms, expr);
+ check_overlapping_arms(cx, ex, arms);
+ check_wild_err_arm(cx, ex, arms);
+ check_wild_enum_match(cx, ex, arms);
+ check_match_as_ref(cx, ex, arms, expr);
+ check_wild_in_or_pats(cx, arms);
+
+ if self.infallible_destructuring_match_linted {
+ self.infallible_destructuring_match_linted = false;
+ } else {
+ check_match_single_binding(cx, ex, arms, expr);
+ }
+ }
+ if let ExprKind::Match(ex, arms, _) = expr.kind {
+ check_match_ref_pats(cx, ex, arms, expr);
+ }
+ }
+
+ fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
+ if_chain! {
+ if !in_external_macro(cx.sess(), local.span);
+ if !in_macro(local.span);
+ if let Some(expr) = local.init;
+ if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind;
+ if arms.len() == 1 && arms[0].guard.is_none();
+ if let PatKind::TupleStruct(
+ QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind;
+ if args.len() == 1;
+ if let PatKind::Binding(_, arg, ..) = strip_pat_refs(args[0]).kind;
+ let body = remove_blocks(arms[0].body);
+ if path_to_local_id(body, arg);
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ self.infallible_destructuring_match_linted = true;
+ span_lint_and_sugg(
+ cx,
+ INFALLIBLE_DESTRUCTURING_MATCH,
+ local.span,
+ "you seem to be trying to use `match` to destructure a single infallible pattern. \
+ Consider using `let`",
+ "try this",
+ format!(
+ "let {}({}) = {};",
+ snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
+ snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
+ snippet_with_applicability(cx, target.span, "..", &mut applicability),
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+
+ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
+ if_chain! {
+ if !in_external_macro(cx.sess(), pat.span);
+ if !in_macro(pat.span);
+ if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind;
+ if let Some(def_id) = path.res.opt_def_id();
+ let ty = cx.tcx.type_of(def_id);
+ if let ty::Adt(def, _) = ty.kind();
+ if def.is_struct() || def.is_union();
+ if fields.len() == def.non_enum_variant().fields.len();
+
+ then {
+ span_lint_and_help(
+ cx,
+ REST_PAT_IN_FULLY_BOUND_STRUCTS,
+ pat.span,
+ "unnecessary use of `..` pattern in struct binding. All fields were already bound",
+ None,
+ "consider removing `..` from this binding",
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+#[rustfmt::skip]
+fn check_single_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
+ if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
+ if in_macro(expr.span) {
+ // Don't lint match expressions present in
+ // macro_rules! block
+ return;
+ }
+ if let PatKind::Or(..) = arms[0].pat.kind {
+ // don't lint for or patterns for now, this makes
+ // the lint noisy in unnecessary situations
+ return;
+ }
+ let els = arms[1].body;
+ let els = if is_unit_expr(remove_blocks(els)) {
+ None
+ } else if let ExprKind::Block(Block { stmts, expr: block_expr, .. }, _) = els.kind {
+ if stmts.len() == 1 && block_expr.is_none() || stmts.is_empty() && block_expr.is_some() {
+ // single statement/expr "else" block, don't lint
+ return;
+ }
+ // block with 2+ statements or 1 expr and 1+ statement
+ Some(els)
+ } else {
+ // not a block, don't lint
+ return;
+ };
+
+ let ty = cx.typeck_results().expr_ty(ex);
+ if *ty.kind() != ty::Bool || is_allowed(cx, MATCH_BOOL, ex.hir_id) {
+ check_single_match_single_pattern(cx, ex, arms, expr, els);
+ check_single_match_opt_like(cx, ex, arms, expr, ty, els);
+ }
+ }
+}
+
+fn check_single_match_single_pattern(
+ cx: &LateContext<'_>,
+ ex: &Expr<'_>,
+ arms: &[Arm<'_>],
+ expr: &Expr<'_>,
+ els: Option<&Expr<'_>>,
+) {
+ if is_wild(&arms[1].pat) {
+ report_single_match_single_pattern(cx, ex, arms, expr, els);
+ }
+}
+
+fn report_single_match_single_pattern(
+ cx: &LateContext<'_>,
+ ex: &Expr<'_>,
+ arms: &[Arm<'_>],
+ expr: &Expr<'_>,
+ els: Option<&Expr<'_>>,
+) {
+ let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH };
+ let els_str = els.map_or(String::new(), |els| {
+ format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span)))
+ });
+
+ let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat);
+ let (msg, sugg) = if_chain! {
+ if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind;
+ let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex));
+ if let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait();
+ if let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait();
+ if ty.is_integral() || ty.is_char() || ty.is_str()
+ || (implements_trait(cx, ty, spe_trait_id, &[])
+ && implements_trait(cx, ty, pe_trait_id, &[ty.into()]));
+ then {
+ // scrutinee derives PartialEq and the pattern is a constant.
+ let pat_ref_count = match pat.kind {
+ // string literals are already a reference.
+ PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1,
+ _ => pat_ref_count,
+ };
+ // References are only implicitly added to the pattern, so no overflow here.
+ // e.g. will work: match &Some(_) { Some(_) => () }
+ // will not: match Some(_) { &Some(_) => () }
+ let ref_count_diff = ty_ref_count - pat_ref_count;
+
+ // Try to remove address of expressions first.
+ let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff);
+ let ref_count_diff = ref_count_diff - removed;
+
+ let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`";
+ let sugg = format!(
+ "if {} == {}{} {}{}",
+ snippet(cx, ex.span, ".."),
+ // PartialEq for different reference counts may not exist.
+ "&".repeat(ref_count_diff),
+ snippet(cx, arms[0].pat.span, ".."),
+ expr_block(cx, arms[0].body, None, "..", Some(expr.span)),
+ els_str,
+ );
+ (msg, sugg)
+ } else {
+ let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`";
+ let sugg = format!(
+ "if let {} = {} {}{}",
+ snippet(cx, arms[0].pat.span, ".."),
+ snippet(cx, ex.span, ".."),
+ expr_block(cx, arms[0].body, None, "..", Some(expr.span)),
+ els_str,
+ );
+ (msg, sugg)
+ }
+ };
+
+ span_lint_and_sugg(
+ cx,
+ lint,
+ expr.span,
+ msg,
+ "try this",
+ sugg,
+ Applicability::HasPlaceholders,
+ );
+}
+
+fn check_single_match_opt_like(
+ cx: &LateContext<'_>,
+ ex: &Expr<'_>,
+ arms: &[Arm<'_>],
+ expr: &Expr<'_>,
+ ty: Ty<'_>,
+ els: Option<&Expr<'_>>,
+) {
+ // list of candidate `Enum`s we know will never get any more members
+ let candidates = &[
+ (&paths::COW, "Borrowed"),
+ (&paths::COW, "Cow::Borrowed"),
+ (&paths::COW, "Cow::Owned"),
+ (&paths::COW, "Owned"),
+ (&paths::OPTION, "None"),
+ (&paths::RESULT, "Err"),
+ (&paths::RESULT, "Ok"),
+ ];
+
+ let path = match arms[1].pat.kind {
+ PatKind::TupleStruct(ref path, inner, _) => {
+ // Contains any non wildcard patterns (e.g., `Err(err)`)?
+ if !inner.iter().all(is_wild) {
+ return;
+ }
+ rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false))
+ },
+ PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => ident.to_string(),
+ PatKind::Path(ref path) => {
+ rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false))
+ },
+ _ => return,
+ };
+
+ for &(ty_path, pat_path) in candidates {
+ if path == *pat_path && match_type(cx, ty, ty_path) {
+ report_single_match_single_pattern(cx, ex, arms, expr, els);
+ }
+ }
+}
+
+fn check_match_bool(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
+ // Type of expression is `bool`.
+ if *cx.typeck_results().expr_ty(ex).kind() == ty::Bool {
+ span_lint_and_then(
+ cx,
+ MATCH_BOOL,
+ expr.span,
+ "you seem to be trying to match on a boolean expression",
+ move |diag| {
+ if arms.len() == 2 {
+ // no guards
+ let exprs = if let PatKind::Lit(arm_bool) = arms[0].pat.kind {
+ if let ExprKind::Lit(ref lit) = arm_bool.kind {
+ match lit.node {
+ LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)),
+ LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ if let Some((true_expr, false_expr)) = exprs {
+ let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) {
+ (false, false) => Some(format!(
+ "if {} {} else {}",
+ snippet(cx, ex.span, "b"),
+ expr_block(cx, true_expr, None, "..", Some(expr.span)),
+ expr_block(cx, false_expr, None, "..", Some(expr.span))
+ )),
+ (false, true) => Some(format!(
+ "if {} {}",
+ snippet(cx, ex.span, "b"),
+ expr_block(cx, true_expr, None, "..", Some(expr.span))
+ )),
+ (true, false) => {
+ let test = Sugg::hir(cx, ex, "..");
+ Some(format!(
+ "if {} {}",
+ !test,
+ expr_block(cx, false_expr, None, "..", Some(expr.span))
+ ))
+ },
+ (true, true) => None,
+ };
+
+ if let Some(sugg) = sugg {
+ diag.span_suggestion(
+ expr.span,
+ "consider using an `if`/`else` expression",
+ sugg,
+ Applicability::HasPlaceholders,
+ );
+ }
+ }
+ }
+ },
+ );
+ }
+}
+
+fn check_overlapping_arms<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) {
+ if arms.len() >= 2 && cx.typeck_results().expr_ty(ex).is_integral() {
+ let ranges = all_ranges(cx, arms, cx.typeck_results().expr_ty(ex));
+ let type_ranges = type_ranges(&ranges);
+ if !type_ranges.is_empty() {
+ if let Some((start, end)) = overlapping(&type_ranges) {
+ span_lint_and_note(
+ cx,
+ MATCH_OVERLAPPING_ARM,
+ start.span,
+ "some ranges overlap",
+ Some(end.span),
+ "overlaps with this",
+ );
+ }
+ }
+ }
+}
+
+fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'tcx>]) {
+ let ex_ty = cx.typeck_results().expr_ty(ex).peel_refs();
+ if is_type_diagnostic_item(cx, ex_ty, sym::result_type) {
+ for arm in arms {
+ if let PatKind::TupleStruct(ref path, inner, _) = arm.pat.kind {
+ let path_str = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false));
+ if path_str == "Err" {
+ let mut matching_wild = inner.iter().any(is_wild);
+ let mut ident_bind_name = String::from("_");
+ if !matching_wild {
+ // Looking for unused bindings (i.e.: `_e`)
+ for pat in inner.iter() {
+ if let PatKind::Binding(_, id, ident, None) = pat.kind {
+ if ident.as_str().starts_with('_')
+ && !LocalUsedVisitor::new(cx, id).check_expr(arm.body)
+ {
+ ident_bind_name = (&ident.name.as_str()).to_string();
+ matching_wild = true;
+ }
+ }
+ }
+ }
+ if_chain! {
+ if matching_wild;
+ if let ExprKind::Block(block, _) = arm.body.kind;
+ if is_panic_block(block);
+ then {
+ // `Err(_)` or `Err(_e)` arm with `panic!` found
+ span_lint_and_note(cx,
+ MATCH_WILD_ERR_ARM,
+ arm.pat.span,
+ &format!("`Err({})` matches all errors", &ident_bind_name),
+ None,
+ "match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable",
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+enum CommonPrefixSearcher<'a> {
+ None,
+ Path(&'a [PathSegment<'a>]),
+ Mixed,
+}
+impl CommonPrefixSearcher<'a> {
+ fn with_path(&mut self, path: &'a [PathSegment<'a>]) {
+ match path {
+ [path @ .., _] => self.with_prefix(path),
+ [] => (),
+ }
+ }
+
+ fn with_prefix(&mut self, path: &'a [PathSegment<'a>]) {
+ match self {
+ Self::None => *self = Self::Path(path),
+ Self::Path(self_path)
+ if path
+ .iter()
+ .map(|p| p.ident.name)
+ .eq(self_path.iter().map(|p| p.ident.name)) => {},
+ Self::Path(_) => *self = Self::Mixed,
+ Self::Mixed => (),
+ }
+ }
+}
+
+fn is_doc_hidden(cx: &LateContext<'_>, variant_def: &VariantDef) -> bool {
+ let attrs = cx.tcx.get_attrs(variant_def.def_id);
+ clippy_utils::attrs::is_doc_hidden(attrs)
+}
+
+#[allow(clippy::too_many_lines)]
+fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
+ let ty = cx.typeck_results().expr_ty(ex).peel_refs();
+ let adt_def = match ty.kind() {
+ ty::Adt(adt_def, _)
+ if adt_def.is_enum()
+ && !(is_type_diagnostic_item(cx, ty, sym::option_type)
+ || is_type_diagnostic_item(cx, ty, sym::result_type)) =>
+ {
+ adt_def
+ },
+ _ => return,
+ };
+
+ // First pass - check for violation, but don't do much book-keeping because this is hopefully
+ // the uncommon case, and the book-keeping is slightly expensive.
+ let mut wildcard_span = None;
+ let mut wildcard_ident = None;
+ let mut has_non_wild = false;
+ for arm in arms {
+ match peel_hir_pat_refs(arm.pat).0.kind {
+ PatKind::Wild => wildcard_span = Some(arm.pat.span),
+ PatKind::Binding(_, _, ident, None) => {
+ wildcard_span = Some(arm.pat.span);
+ wildcard_ident = Some(ident);
+ },
+ _ => has_non_wild = true,
+ }
+ }
+ let wildcard_span = match wildcard_span {
+ Some(x) if has_non_wild => x,
+ _ => return,
+ };
+
+ // Accumulate the variants which should be put in place of the wildcard because they're not
+ // already covered.
+ let mut missing_variants: Vec<_> = adt_def.variants.iter().collect();
+
+ let mut path_prefix = CommonPrefixSearcher::None;
+ for arm in arms {
+ // Guards mean that this case probably isn't exhaustively covered. Technically
+ // this is incorrect, as we should really check whether each variant is exhaustively
+ // covered by the set of guards that cover it, but that's really hard to do.
+ recurse_or_patterns(arm.pat, |pat| {
+ let path = match &peel_hir_pat_refs(pat).0.kind {
+ PatKind::Path(path) => {
+ #[allow(clippy::match_same_arms)]
+ let id = match cx.qpath_res(path, pat.hir_id) {
+ Res::Def(DefKind::Const | DefKind::ConstParam | DefKind::AnonConst, _) => return,
+ Res::Def(_, id) => id,
+ _ => return,
+ };
+ if arm.guard.is_none() {
+ missing_variants.retain(|e| e.ctor_def_id != Some(id));
+ }
+ path
+ },
+ PatKind::TupleStruct(path, patterns, ..) => {
+ if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() {
+ if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p)) {
+ missing_variants.retain(|e| e.ctor_def_id != Some(id));
+ }
+ }
+ path
+ },
+ PatKind::Struct(path, patterns, ..) => {
+ if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() {
+ if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p.pat)) {
+ missing_variants.retain(|e| e.def_id != id);
+ }
+ }
+ path
+ },
+ _ => return,
+ };
+ match path {
+ QPath::Resolved(_, path) => path_prefix.with_path(path.segments),
+ QPath::TypeRelative(
+ hir::Ty {
+ kind: TyKind::Path(QPath::Resolved(_, path)),
+ ..
+ },
+ _,
+ ) => path_prefix.with_prefix(path.segments),
+ _ => (),
+ }
+ });
+ }
+
+ let format_suggestion = |variant: &VariantDef| {
+ format!(
+ "{}{}{}{}",
+ if let Some(ident) = wildcard_ident {
+ format!("{} @ ", ident.name)
+ } else {
+ String::new()
+ },
+ if let CommonPrefixSearcher::Path(path_prefix) = path_prefix {
+ let mut s = String::new();
+ for seg in path_prefix {
+ s.push_str(&seg.ident.as_str());
+ s.push_str("::");
+ }
+ s
+ } else {
+ let mut s = cx.tcx.def_path_str(adt_def.did);
+ s.push_str("::");
+ s
+ },
+ variant.ident.name,
+ match variant.ctor_kind {
+ CtorKind::Fn if variant.fields.len() == 1 => "(_)",
+ CtorKind::Fn => "(..)",
+ CtorKind::Const => "",
+ CtorKind::Fictive => "{ .. }",
+ }
+ )
+ };
+
+ match missing_variants.as_slice() {
+ [] => (),
+ [x] if !adt_def.is_variant_list_non_exhaustive() && !is_doc_hidden(cx, x) => span_lint_and_sugg(
+ cx,
+ MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
+ wildcard_span,
+ "wildcard matches only a single variant and will also match any future added variants",
+ "try this",
+ format_suggestion(x),
+ Applicability::MaybeIncorrect,
+ ),
+ variants => {
+ let mut suggestions: Vec<_> = variants.iter().copied().map(format_suggestion).collect();
+ let message = if adt_def.is_variant_list_non_exhaustive() {
+ suggestions.push("_".into());
+ "wildcard matches known variants and will also match future added variants"
+ } else {
+ "wildcard match will also match any future added variants"
+ };
+
+ span_lint_and_sugg(
+ cx,
+ WILDCARD_ENUM_MATCH_ARM,
+ wildcard_span,
+ message,
+ "try this",
+ suggestions.join(" | "),
+ Applicability::MaybeIncorrect,
+ )
+ },
+ };
+}
+
+// If the block contains only a `panic!` macro (as expression or statement)
+fn is_panic_block(block: &Block<'_>) -> bool {
+ match (&block.expr, block.stmts.len(), block.stmts.first()) {
+ (&Some(exp), 0, _) => is_expn_of(exp.span, "panic").is_some() && is_expn_of(exp.span, "unreachable").is_none(),
+ (&None, 1, Some(stmt)) => {
+ is_expn_of(stmt.span, "panic").is_some() && is_expn_of(stmt.span, "unreachable").is_none()
+ },
+ _ => false,
+ }
+}
+
+fn check_match_ref_pats(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
+ if has_only_ref_pats(arms) {
+ let mut suggs = Vec::with_capacity(arms.len() + 1);
+ let (title, msg) = if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = ex.kind {
+ let span = ex.span.source_callsite();
+ suggs.push((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string()));
+ (
+ "you don't need to add `&` to both the expression and the patterns",
+ "try",
+ )
+ } else {
+ let span = ex.span.source_callsite();
+ suggs.push((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string()));
+ (
+ "you don't need to add `&` to all patterns",
+ "instead of prefixing all patterns with `&`, you can dereference the expression",
+ )
+ };
+
+ suggs.extend(arms.iter().filter_map(|a| {
+ if let PatKind::Ref(refp, _) = a.pat.kind {
+ Some((a.pat.span, snippet(cx, refp.span, "..").to_string()))
+ } else {
+ None
+ }
+ }));
+
+ span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |diag| {
+ if !expr.span.from_expansion() {
+ multispan_sugg(diag, msg, suggs);
+ }
+ });
+ }
+}
+
+fn check_match_as_ref(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
+ if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
+ let arm_ref: Option<BindingAnnotation> = if is_none_arm(cx, &arms[0]) {
+ is_ref_some_arm(cx, &arms[1])
+ } else if is_none_arm(cx, &arms[1]) {
+ is_ref_some_arm(cx, &arms[0])
+ } else {
+ None
+ };
+ if let Some(rb) = arm_ref {
+ let suggestion = if rb == BindingAnnotation::Ref {
+ "as_ref"
+ } else {
+ "as_mut"
+ };
+
+ let output_ty = cx.typeck_results().expr_ty(expr);
+ let input_ty = cx.typeck_results().expr_ty(ex);
+
+ let cast = if_chain! {
+ if let ty::Adt(_, substs) = input_ty.kind();
+ let input_ty = substs.type_at(0);
+ if let ty::Adt(_, substs) = output_ty.kind();
+ let output_ty = substs.type_at(0);
+ if let ty::Ref(_, output_ty, _) = *output_ty.kind();
+ if input_ty != output_ty;
+ then {
+ ".map(|x| x as _)"
+ } else {
+ ""
+ }
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ MATCH_AS_REF,
+ expr.span,
+ &format!("use `{}()` instead", suggestion),
+ "try this",
+ format!(
+ "{}.{}(){}",
+ snippet_with_applicability(cx, ex.span, "_", &mut applicability),
+ suggestion,
+ cast,
+ ),
+ applicability,
+ )
+ }
+ }
+}
+
+fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) {
+ for arm in arms {
+ if let PatKind::Or(fields) = arm.pat.kind {
+ // look for multiple fields in this arm that contains at least one Wild pattern
+ if fields.len() > 1 && fields.iter().any(is_wild) {
+ span_lint_and_help(
+ cx,
+ WILDCARD_IN_OR_PATTERNS,
+ arm.pat.span,
+ "wildcard pattern covers any other pattern as it will match anyway",
+ None,
+ "consider handling `_` separately",
+ );
+ }
+ }
+ }
+}
+
+/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!`
+fn check_match_like_matches<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ if let ExprKind::Match(ex, arms, ref match_source) = &expr.kind {
+ match match_source {
+ MatchSource::Normal => find_matches_sugg(cx, ex, arms, expr, false),
+ MatchSource::IfLetDesugar { .. } => find_matches_sugg(cx, ex, arms, expr, true),
+ _ => false,
+ }
+ } else {
+ false
+ }
+}
+
+/// Lint a `match` or desugared `if let` for replacement by `matches!`
+fn find_matches_sugg(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>, desugared: bool) -> bool {
+ if_chain! {
+ if arms.len() >= 2;
+ if cx.typeck_results().expr_ty(expr).is_bool();
+ if let Some((b1_arm, b0_arms)) = arms.split_last();
+ if let Some(b0) = find_bool_lit(&b0_arms[0].body.kind, desugared);
+ if let Some(b1) = find_bool_lit(&b1_arm.body.kind, desugared);
+ if is_wild(&b1_arm.pat);
+ if b0 != b1;
+ let if_guard = &b0_arms[0].guard;
+ if if_guard.is_none() || b0_arms.len() == 1;
+ if cx.tcx.hir().attrs(b0_arms[0].hir_id).is_empty();
+ if b0_arms[1..].iter()
+ .all(|arm| {
+ find_bool_lit(&arm.body.kind, desugared).map_or(false, |b| b == b0) &&
+ arm.guard.is_none() && cx.tcx.hir().attrs(arm.hir_id).is_empty()
+ });
+ then {
+ // The suggestion may be incorrect, because some arms can have `cfg` attributes
+ // evaluated into `false` and so such arms will be stripped before.
+ let mut applicability = Applicability::MaybeIncorrect;
+ let pat = {
+ use itertools::Itertools as _;
+ b0_arms.iter()
+ .map(|arm| snippet_with_applicability(cx, arm.pat.span, "..", &mut applicability))
+ .join(" | ")
+ };
+ let pat_and_guard = if let Some(Guard::If(g)) = if_guard {
+ format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability))
+ } else {
+ pat
+ };
+
+ // strip potential borrows (#6503), but only if the type is a reference
+ let mut ex_new = ex;
+ if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind {
+ if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() {
+ ex_new = ex_inner;
+ }
+ };
+ span_lint_and_sugg(
+ cx,
+ MATCH_LIKE_MATCHES_MACRO,
+ expr.span,
+ &format!("{} expression looks like `matches!` macro", if desugared { "if let .. else" } else { "match" }),
+ "try this",
+ format!(
+ "{}matches!({}, {})",
+ if b0 { "" } else { "!" },
+ snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
+ pat_and_guard,
+ ),
+ applicability,
+ );
+ true
+ } else {
+ false
+ }
+ }
+}
+
+/// Extract a `bool` or `{ bool }`
+fn find_bool_lit(ex: &ExprKind<'_>, desugared: bool) -> Option<bool> {
+ match ex {
+ ExprKind::Lit(Spanned {
+ node: LitKind::Bool(b), ..
+ }) => Some(*b),
+ ExprKind::Block(
+ rustc_hir::Block {
+ stmts: &[],
+ expr: Some(exp),
+ ..
+ },
+ _,
+ ) if desugared => {
+ if let ExprKind::Lit(Spanned {
+ node: LitKind::Bool(b), ..
+ }) = exp.kind
+ {
+ Some(b)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+#[allow(clippy::too_many_lines)]
+fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) {
+ if in_macro(expr.span) || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
+ return;
+ }
+
+ // HACK:
+ // This is a hack to deal with arms that are excluded by macros like `#[cfg]`. It is only used here
+ // to prevent false positives as there is currently no better way to detect if code was excluded by
+ // a macro. See PR #6435
+ if_chain! {
+ if let Some(match_snippet) = snippet_opt(cx, expr.span);
+ if let Some(arm_snippet) = snippet_opt(cx, arms[0].span);
+ if let Some(ex_snippet) = snippet_opt(cx, ex.span);
+ let rest_snippet = match_snippet.replace(&arm_snippet, "").replace(&ex_snippet, "");
+ if rest_snippet.contains("=>");
+ then {
+ // The code it self contains another thick arrow "=>"
+ // -> Either another arm or a comment
+ return;
+ }
+ }
+
+ let matched_vars = ex.span;
+ let bind_names = arms[0].pat.span;
+ let match_body = remove_blocks(arms[0].body);
+ let mut snippet_body = if match_body.span.from_expansion() {
+ Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string()
+ } else {
+ snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string()
+ };
+
+ // Do we need to add ';' to suggestion ?
+ match match_body.kind {
+ ExprKind::Block(block, _) => {
+ // macro + expr_ty(body) == ()
+ if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() {
+ snippet_body.push(';');
+ }
+ },
+ _ => {
+ // expr_ty(body) == ()
+ if cx.typeck_results().expr_ty(match_body).is_unit() {
+ snippet_body.push(';');
+ }
+ },
+ }
+
+ let mut applicability = Applicability::MaybeIncorrect;
+ match arms[0].pat.kind {
+ PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => {
+ // If this match is in a local (`let`) stmt
+ let (target_span, sugg) = if let Some(parent_let_node) = opt_parent_let(cx, ex) {
+ (
+ parent_let_node.span,
+ format!(
+ "let {} = {};\n{}let {} = {};",
+ snippet_with_applicability(cx, bind_names, "..", &mut applicability),
+ snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
+ " ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
+ snippet_with_applicability(cx, parent_let_node.pat.span, "..", &mut applicability),
+ snippet_body
+ ),
+ )
+ } else {
+ // If we are in closure, we need curly braces around suggestion
+ let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
+ let (mut cbrace_start, mut cbrace_end) = ("".to_string(), "".to_string());
+ if let Some(parent_expr) = get_parent_expr(cx, expr) {
+ if let ExprKind::Closure(..) = parent_expr.kind {
+ cbrace_end = format!("\n{}}}", indent);
+ // Fix body indent due to the closure
+ indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
+ cbrace_start = format!("{{\n{}", indent);
+ }
+ }
+ // If the parent is already an arm, and the body is another match statement,
+ // we need curly braces around suggestion
+ let parent_node_id = cx.tcx.hir().get_parent_node(expr.hir_id);
+ if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) {
+ if let ExprKind::Match(..) = arm.body.kind {
+ cbrace_end = format!("\n{}}}", indent);
+ // Fix body indent due to the match
+ indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
+ cbrace_start = format!("{{\n{}", indent);
+ }
+ }
+ (
+ expr.span,
+ format!(
+ "{}let {} = {};\n{}{}{}",
+ cbrace_start,
+ snippet_with_applicability(cx, bind_names, "..", &mut applicability),
+ snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
+ indent,
+ snippet_body,
+ cbrace_end
+ ),
+ )
+ };
+ span_lint_and_sugg(
+ cx,
+ MATCH_SINGLE_BINDING,
+ target_span,
+ "this match could be written as a `let` statement",
+ "consider using `let` statement",
+ sugg,
+ applicability,
+ );
+ },
+ PatKind::Wild => {
+ span_lint_and_sugg(
+ cx,
+ MATCH_SINGLE_BINDING,
+ expr.span,
+ "this match could be replaced by its body itself",
+ "consider using the match body instead",
+ snippet_body,
+ Applicability::MachineApplicable,
+ );
+ },
+ _ => (),
+ }
+}
+
+/// Returns true if the `ex` match expression is in a local (`let`) statement
+fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> {
+ let map = &cx.tcx.hir();
+ if_chain! {
+ if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id));
+ if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id));
+ then {
+ return Some(parent_let_expr);
+ }
+ }
+ None
+}
+
+/// Gets all arms that are unbounded `PatRange`s.
+fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec<SpannedRange<Constant>> {
+ arms.iter()
+ .filter_map(|arm| {
+ if let Arm { pat, guard: None, .. } = *arm {
+ if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind {
+ let lhs = match lhs {
+ Some(lhs) => constant(cx, cx.typeck_results(), lhs)?.0,
+ None => miri_to_const(ty.numeric_min_val(cx.tcx)?)?,
+ };
+ let rhs = match rhs {
+ Some(rhs) => constant(cx, cx.typeck_results(), rhs)?.0,
+ None => miri_to_const(ty.numeric_max_val(cx.tcx)?)?,
+ };
+ let rhs = match range_end {
+ RangeEnd::Included => Bound::Included(rhs),
+ RangeEnd::Excluded => Bound::Excluded(rhs),
+ };
+ return Some(SpannedRange {
+ span: pat.span,
+ node: (lhs, rhs),
+ });
+ }
+
+ if let PatKind::Lit(value) = pat.kind {
+ let value = constant(cx, cx.typeck_results(), value)?.0;
+ return Some(SpannedRange {
+ span: pat.span,
+ node: (value.clone(), Bound::Included(value)),
+ });
+ }
+ }
+ None
+ })
+ .collect()
+}
+
+#[derive(Debug, Eq, PartialEq)]
+pub struct SpannedRange<T> {
+ pub span: Span,
+ pub node: (T, Bound<T>),
+}
+
+type TypedRanges = Vec<SpannedRange<u128>>;
+
+/// Gets all `Int` ranges or all `Uint` ranges. Mixed types are an error anyway
+/// and other types than
+/// `Uint` and `Int` probably don't make sense.
+fn type_ranges(ranges: &[SpannedRange<Constant>]) -> TypedRanges {
+ ranges
+ .iter()
+ .filter_map(|range| match range.node {
+ (Constant::Int(start), Bound::Included(Constant::Int(end))) => Some(SpannedRange {
+ span: range.span,
+ node: (start, Bound::Included(end)),
+ }),
+ (Constant::Int(start), Bound::Excluded(Constant::Int(end))) => Some(SpannedRange {
+ span: range.span,
+ node: (start, Bound::Excluded(end)),
+ }),
+ (Constant::Int(start), Bound::Unbounded) => Some(SpannedRange {
+ span: range.span,
+ node: (start, Bound::Unbounded),
+ }),
+ _ => None,
+ })
+ .collect()
+}
+
+fn is_unit_expr(expr: &Expr<'_>) -> bool {
+ match expr.kind {
+ ExprKind::Tup(v) if v.is_empty() => true,
+ ExprKind::Block(b, _) if b.stmts.is_empty() && b.expr.is_none() => true,
+ _ => false,
+ }
+}
+
+// Checks if arm has the form `None => None`
+fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ matches!(arm.pat.kind, PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone))
+}
+
+// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`)
+fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<BindingAnnotation> {
+ if_chain! {
- if let PatKind::Binding(rb, .., ident, _) = pats[0].kind;
++ if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind;
+ if is_lang_ctor(cx, qpath, OptionSome);
- ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop(cx, ty)),
- ty::Array(ty, _) => type_needs_ordered_drop(cx, ty),
++ if let PatKind::Binding(rb, .., ident, _) = first_pat.kind;
+ if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut;
+ if let ExprKind::Call(e, args) = remove_blocks(arm.body).kind;
+ if let ExprKind::Path(ref some_path) = e.kind;
+ if is_lang_ctor(cx, some_path, OptionSome) && args.len() == 1;
+ if let ExprKind::Path(QPath::Resolved(_, path2)) = args[0].kind;
+ if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
+ then {
+ return Some(rb)
+ }
+ }
+ None
+}
+
+fn has_only_ref_pats(arms: &[Arm<'_>]) -> bool {
+ let mapped = arms
+ .iter()
+ .map(|a| {
+ match a.pat.kind {
+ PatKind::Ref(..) => Some(true), // &-patterns
+ PatKind::Wild => Some(false), // an "anything" wildcard is also fine
+ _ => None, // any other pattern is not fine
+ }
+ })
+ .collect::<Option<Vec<bool>>>();
+ // look for Some(v) where there's at least one true element
+ mapped.map_or(false, |v| v.iter().any(|el| *el))
+}
+
+pub fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
+where
+ T: Copy + Ord,
+{
+ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+ enum Kind<'a, T> {
+ Start(T, &'a SpannedRange<T>),
+ End(Bound<T>, &'a SpannedRange<T>),
+ }
+
+ impl<'a, T: Copy> Kind<'a, T> {
+ fn range(&self) -> &'a SpannedRange<T> {
+ match *self {
+ Kind::Start(_, r) | Kind::End(_, r) => r,
+ }
+ }
+
+ fn value(self) -> Bound<T> {
+ match self {
+ Kind::Start(t, _) => Bound::Included(t),
+ Kind::End(t, _) => t,
+ }
+ }
+ }
+
+ impl<'a, T: Copy + Ord> PartialOrd for Kind<'a, T> {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+ }
+
+ impl<'a, T: Copy + Ord> Ord for Kind<'a, T> {
+ fn cmp(&self, other: &Self) -> Ordering {
+ match (self.value(), other.value()) {
+ (Bound::Included(a), Bound::Included(b)) | (Bound::Excluded(a), Bound::Excluded(b)) => a.cmp(&b),
+ // Range patterns cannot be unbounded (yet)
+ (Bound::Unbounded, _) | (_, Bound::Unbounded) => unimplemented!(),
+ (Bound::Included(a), Bound::Excluded(b)) => match a.cmp(&b) {
+ Ordering::Equal => Ordering::Greater,
+ other => other,
+ },
+ (Bound::Excluded(a), Bound::Included(b)) => match a.cmp(&b) {
+ Ordering::Equal => Ordering::Less,
+ other => other,
+ },
+ }
+ }
+ }
+
+ let mut values = Vec::with_capacity(2 * ranges.len());
+
+ for r in ranges {
+ values.push(Kind::Start(r.node.0, r));
+ values.push(Kind::End(r.node.1, r));
+ }
+
+ values.sort();
+
+ for (a, b) in iter::zip(&values, values.iter().skip(1)) {
+ match (a, b) {
+ (&Kind::Start(_, ra), &Kind::End(_, rb)) => {
+ if ra.node != rb.node {
+ return Some((ra, rb));
+ }
+ },
+ (&Kind::End(a, _), &Kind::Start(b, _)) if a != Bound::Included(b) => (),
+ _ => {
+ // skip if the range `a` is completely included into the range `b`
+ if let Ordering::Equal | Ordering::Less = a.cmp(b) {
+ let kind_a = Kind::End(a.range().node.1, a.range());
+ let kind_b = Kind::End(b.range().node.1, b.range());
+ if let Ordering::Equal | Ordering::Greater = kind_a.cmp(&kind_b) {
+ return None;
+ }
+ }
+ return Some((a.range(), b.range()));
+ },
+ }
+ }
+
+ None
+}
+
+mod redundant_pattern_match {
+ use super::REDUNDANT_PATTERN_MATCHING;
+ use clippy_utils::diagnostics::span_lint_and_then;
+ use clippy_utils::source::{snippet, snippet_with_applicability};
+ use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type};
+ use clippy_utils::{is_lang_ctor, is_qpath_def_path, is_trait_method, paths};
+ use if_chain::if_chain;
+ use rustc_ast::ast::LitKind;
++ use rustc_data_structures::fx::FxHashSet;
+ use rustc_errors::Applicability;
+ use rustc_hir::LangItem::{OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk};
+ use rustc_hir::{
+ intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
+ Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, PatKind, QPath,
+ };
+ use rustc_lint::LateContext;
+ use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
+ use rustc_span::sym;
+
+ pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Match(op, arms, ref match_source) = &expr.kind {
+ match match_source {
+ MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms),
+ MatchSource::IfLetDesugar { contains_else_clause } => {
+ find_sugg_for_if_let(cx, expr, op, &arms[0], "if", *contains_else_clause)
+ },
+ MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, &arms[0], "while", false),
+ _ => {},
+ }
+ }
+ }
+
+ /// Checks if the drop order for a type matters. Some std types implement drop solely to
+ /// deallocate memory. For these types, and composites containing them, changing the drop order
+ /// won't result in any observable side effects.
+ fn type_needs_ordered_drop(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
++ type_needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default())
++ }
++
++ fn type_needs_ordered_drop_inner(cx: &LateContext<'tcx>, ty: Ty<'tcx>, seen: &mut FxHashSet<Ty<'tcx>>) -> bool {
++ if !seen.insert(ty) {
++ return false;
++ }
+ if !ty.needs_drop(cx.tcx, cx.param_env) {
+ false
+ } else if !cx
+ .tcx
+ .lang_items()
+ .drop_trait()
+ .map_or(false, |id| implements_trait(cx, ty, id, &[]))
+ {
+ // This type doesn't implement drop, so no side effects here.
+ // Check if any component type has any.
+ match ty.kind() {
- .any(|ty| type_needs_ordered_drop(cx, ty)),
++ ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)),
++ ty::Array(ty, _) => type_needs_ordered_drop_inner(cx, ty, seen),
+ ty::Adt(adt, subs) => adt
+ .all_fields()
+ .map(|f| f.ty(cx.tcx, subs))
- subs.types().any(|ty| type_needs_ordered_drop(cx, ty))
++ .any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)),
+ _ => true,
+ }
+ }
+ // Check for std types which implement drop, but only for memory allocation.
+ else if is_type_diagnostic_item(cx, ty, sym::vec_type)
+ || is_type_lang_item(cx, ty, LangItem::OwnedBox)
+ || is_type_diagnostic_item(cx, ty, sym::Rc)
+ || is_type_diagnostic_item(cx, ty, sym::Arc)
+ || is_type_diagnostic_item(cx, ty, sym::cstring_type)
+ || match_type(cx, ty, &paths::BTREEMAP)
+ || match_type(cx, ty, &paths::LINKED_LIST)
+ || match_type(cx, ty, &paths::WEAK_RC)
+ || match_type(cx, ty, &paths::WEAK_ARC)
+ {
+ // Check all of the generic arguments.
+ if let ty::Adt(_, subs) = ty.kind() {
++ subs.types().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen))
+ } else {
+ true
+ }
+ } else {
+ true
+ }
+ }
+
+ // Extract the generic arguments out of a type
+ fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
+ if_chain! {
+ if let ty::Adt(_, subs) = ty.kind();
+ if let Some(sub) = subs.get(index);
+ if let GenericArgKind::Type(sub_ty) = sub.unpack();
+ then {
+ Some(sub_ty)
+ } else {
+ None
+ }
+ }
+ }
+
+ // Checks if there are any temporaries created in the given expression for which drop order
+ // matters.
+ fn temporaries_need_ordered_drop(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ struct V<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ res: bool,
+ }
+ impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
+ type Map = ErasedMap<'tcx>;
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+ match expr.kind {
+ // Taking the reference of a value leaves a temporary
+ // e.g. In `&String::new()` the string is a temporary value.
+ // Remaining fields are temporary values
+ // e.g. In `(String::new(), 0).1` the string is a temporary value.
+ ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => {
+ if !matches!(expr.kind, ExprKind::Path(_)) {
+ if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) {
+ self.res = true;
+ } else {
+ self.visit_expr(expr);
+ }
+ }
+ },
+ // the base type is alway taken by reference.
+ // e.g. In `(vec![0])[0]` the vector is a temporary value.
+ ExprKind::Index(base, index) => {
+ if !matches!(base.kind, ExprKind::Path(_)) {
+ if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) {
+ self.res = true;
+ } else {
+ self.visit_expr(base);
+ }
+ }
+ self.visit_expr(index);
+ },
+ // Method calls can take self by reference.
+ // e.g. In `String::new().len()` the string is a temporary value.
+ ExprKind::MethodCall(_, _, [self_arg, args @ ..], _) => {
+ if !matches!(self_arg.kind, ExprKind::Path(_)) {
+ let self_by_ref = self
+ .cx
+ .typeck_results()
+ .type_dependent_def_id(expr.hir_id)
+ .map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
+ if self_by_ref
+ && type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg))
+ {
+ self.res = true;
+ } else {
+ self.visit_expr(self_arg)
+ }
+ }
+ args.iter().for_each(|arg| self.visit_expr(arg));
+ },
+ // Either explicitly drops values, or changes control flow.
+ ExprKind::DropTemps(_)
+ | ExprKind::Ret(_)
+ | ExprKind::Break(..)
+ | ExprKind::Yield(..)
+ | ExprKind::Block(Block { expr: None, .. }, _)
+ | ExprKind::Loop(..) => (),
+
+ // Only consider the final expression.
+ ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr),
+
+ _ => walk_expr(self, expr),
+ }
+ }
+ }
+
+ let mut v = V { cx, res: false };
+ v.visit_expr(expr);
+ v.res
+ }
+
+ fn find_sugg_for_if_let<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ op: &'tcx Expr<'tcx>,
+ arm: &Arm<'_>,
+ keyword: &'static str,
+ has_else: bool,
+ ) {
+ // also look inside refs
+ let mut kind = &arm.pat.kind;
+ // if we have &None for example, peel it so we can detect "if let None = x"
+ if let PatKind::Ref(inner, _mutability) = kind {
+ kind = &inner.kind;
+ }
+ let op_ty = cx.typeck_results().expr_ty(op);
+ // Determine which function should be used, and the type contained by the corresponding
+ // variant.
+ let (good_method, inner_ty) = match kind {
+ PatKind::TupleStruct(ref path, [sub_pat], _) => {
+ if let PatKind::Wild = sub_pat.kind {
+ if is_lang_ctor(cx, path, ResultOk) {
+ ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
+ } else if is_lang_ctor(cx, path, ResultErr) {
+ ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
+ } else if is_lang_ctor(cx, path, OptionSome) {
+ ("is_some()", op_ty)
+ } else if is_lang_ctor(cx, path, PollReady) {
+ ("is_ready()", op_ty)
+ } else if is_qpath_def_path(cx, path, sub_pat.hir_id, &paths::IPADDR_V4) {
+ ("is_ipv4()", op_ty)
+ } else if is_qpath_def_path(cx, path, sub_pat.hir_id, &paths::IPADDR_V6) {
+ ("is_ipv6()", op_ty)
+ } else {
+ return;
+ }
+ } else {
+ return;
+ }
+ },
+ PatKind::Path(ref path) => {
+ let method = if is_lang_ctor(cx, path, OptionNone) {
+ "is_none()"
+ } else if is_lang_ctor(cx, path, PollPending) {
+ "is_pending()"
+ } else {
+ return;
+ };
+ // `None` and `Pending` don't have an inner type.
+ (method, cx.tcx.types.unit)
+ },
+ _ => return,
+ };
+
+ // If this is the last expression in a block or there is an else clause then the whole
+ // type needs to be considered, not just the inner type of the branch being matched on.
+ // Note the last expression in a block is dropped after all local bindings.
+ let check_ty = if has_else
+ || (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..)))))
+ {
+ op_ty
+ } else {
+ inner_ty
+ };
+
+ // All temporaries created in the scrutinee expression are dropped at the same time as the
+ // scrutinee would be, so they have to be considered as well.
+ // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
+ // for the duration if body.
+ let needs_drop = type_needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, op);
+
+ // check that `while_let_on_iterator` lint does not trigger
+ if_chain! {
+ if keyword == "while";
+ if let ExprKind::MethodCall(method_path, _, _, _) = op.kind;
+ if method_path.ident.name == sym::next;
+ if is_trait_method(cx, op, sym::Iterator);
+ then {
+ return;
+ }
+ }
+
+ let result_expr = match &op.kind {
+ ExprKind::AddrOf(_, _, borrowed) => borrowed,
+ _ => op,
+ };
+ span_lint_and_then(
+ cx,
+ REDUNDANT_PATTERN_MATCHING,
+ arm.pat.span,
+ &format!("redundant pattern matching, consider using `{}`", good_method),
+ |diag| {
+ // while let ... = ... { ... }
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ let expr_span = expr.span;
+
+ // while let ... = ... { ... }
+ // ^^^
+ let op_span = result_expr.span.source_callsite();
+
+ // while let ... = ... { ... }
+ // ^^^^^^^^^^^^^^^^^^^
+ let span = expr_span.until(op_span.shrink_to_hi());
+
+ let mut app = if needs_drop {
+ Applicability::MaybeIncorrect
+ } else {
+ Applicability::MachineApplicable
+ };
+ let sugg = snippet_with_applicability(cx, op_span, "_", &mut app);
+
+ diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app);
+
+ if needs_drop {
+ diag.note("this will change drop order of the result, as well as all temporaries");
+ diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important");
+ }
+ },
+ );
+ }
+
+ fn find_sugg_for_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) {
+ if arms.len() == 2 {
+ let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
+
+ let found_good_method = match node_pair {
+ (
+ PatKind::TupleStruct(ref path_left, patterns_left, _),
+ PatKind::TupleStruct(ref path_right, patterns_right, _),
+ ) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
+ if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ &paths::RESULT_OK,
+ &paths::RESULT_ERR,
+ "is_ok()",
+ "is_err()",
+ )
+ .or_else(|| {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ &paths::IPADDR_V4,
+ &paths::IPADDR_V6,
+ "is_ipv4()",
+ "is_ipv6()",
+ )
+ })
+ } else {
+ None
+ }
+ },
+ (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right))
+ | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _))
+ if patterns.len() == 1 =>
+ {
+ if let PatKind::Wild = patterns[0].kind {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ &paths::OPTION_SOME,
+ &paths::OPTION_NONE,
+ "is_some()",
+ "is_none()",
+ )
+ .or_else(|| {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ &paths::POLL_READY,
+ &paths::POLL_PENDING,
+ "is_ready()",
+ "is_pending()",
+ )
+ })
+ } else {
+ None
+ }
+ },
+ _ => None,
+ };
+
+ if let Some(good_method) = found_good_method {
+ let span = expr.span.to(op.span);
+ let result_expr = match &op.kind {
+ ExprKind::AddrOf(_, _, borrowed) => borrowed,
+ _ => op,
+ };
+ span_lint_and_then(
+ cx,
+ REDUNDANT_PATTERN_MATCHING,
+ expr.span,
+ &format!("redundant pattern matching, consider using `{}`", good_method),
+ |diag| {
+ diag.span_suggestion(
+ span,
+ "try this",
+ format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method),
+ Applicability::MaybeIncorrect, // snippet
+ );
+ },
+ );
+ }
+ }
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ fn find_good_method_for_match<'a>(
+ cx: &LateContext<'_>,
+ arms: &[Arm<'_>],
+ path_left: &QPath<'_>,
+ path_right: &QPath<'_>,
+ expected_left: &[&str],
+ expected_right: &[&str],
+ should_be_left: &'a str,
+ should_be_right: &'a str,
+ ) -> Option<&'a str> {
+ let body_node_pair = if is_qpath_def_path(cx, path_left, arms[0].pat.hir_id, expected_left)
+ && is_qpath_def_path(cx, path_right, arms[1].pat.hir_id, expected_right)
+ {
+ (&(*arms[0].body).kind, &(*arms[1].body).kind)
+ } else if is_qpath_def_path(cx, path_right, arms[1].pat.hir_id, expected_left)
+ && is_qpath_def_path(cx, path_left, arms[0].pat.hir_id, expected_right)
+ {
+ (&(*arms[1].body).kind, &(*arms[0].body).kind)
+ } else {
+ return None;
+ };
+
+ match body_node_pair {
+ (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) {
+ (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
+ (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),
+ _ => None,
+ },
+ _ => None,
+ }
+ }
+}
+
+#[test]
+fn test_overlapping() {
+ use rustc_span::source_map::DUMMY_SP;
+
+ let sp = |s, e| SpannedRange {
+ span: DUMMY_SP,
+ node: (s, e),
+ };
+
+ assert_eq!(None, overlapping::<u8>(&[]));
+ assert_eq!(None, overlapping(&[sp(1, Bound::Included(4))]));
+ assert_eq!(
+ None,
+ overlapping(&[sp(1, Bound::Included(4)), sp(5, Bound::Included(6))])
+ );
+ assert_eq!(
+ None,
+ overlapping(&[
+ sp(1, Bound::Included(4)),
+ sp(5, Bound::Included(6)),
+ sp(10, Bound::Included(11))
+ ],)
+ );
+ assert_eq!(
+ Some((&sp(1, Bound::Included(4)), &sp(3, Bound::Included(6)))),
+ overlapping(&[sp(1, Bound::Included(4)), sp(3, Bound::Included(6))])
+ );
+ assert_eq!(
+ Some((&sp(5, Bound::Included(6)), &sp(6, Bound::Included(11)))),
+ overlapping(&[
+ sp(1, Bound::Included(4)),
+ sp(5, Bound::Included(6)),
+ sp(6, Bound::Included(11))
+ ],)
+ );
+}
+
+/// Implementation of `MATCH_SAME_ARMS`.
+fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
+ if let ExprKind::Match(_, arms, MatchSource::Normal) = expr.kind {
+ let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 {
+ let mut h = SpanlessHash::new(cx);
+ h.hash_expr(arm.body);
+ h.finish()
+ };
+
+ let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool {
+ let min_index = usize::min(lindex, rindex);
+ let max_index = usize::max(lindex, rindex);
+
+ let mut local_map: HirIdMap<HirId> = HirIdMap::default();
+ let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
+ if_chain! {
+ if let Some(a_id) = path_to_local(a);
+ if let Some(b_id) = path_to_local(b);
+ let entry = match local_map.entry(a_id) {
+ Entry::Vacant(entry) => entry,
+ // check if using the same bindings as before
+ Entry::Occupied(entry) => return *entry.get() == b_id,
+ };
+ // the names technically don't have to match; this makes the lint more conservative
+ if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id);
+ if TyS::same_type(cx.typeck_results().expr_ty(a), cx.typeck_results().expr_ty(b));
+ if pat_contains_local(lhs.pat, a_id);
+ if pat_contains_local(rhs.pat, b_id);
+ then {
+ entry.insert(b_id);
+ true
+ } else {
+ false
+ }
+ }
+ };
+ // Arms with a guard are ignored, those can’t always be merged together
+ // This is also the case for arms in-between each there is an arm with a guard
+ (min_index..=max_index).all(|index| arms[index].guard.is_none())
+ && SpanlessEq::new(cx)
+ .expr_fallback(eq_fallback)
+ .eq_expr(lhs.body, rhs.body)
+ // these checks could be removed to allow unused bindings
+ && bindings_eq(lhs.pat, local_map.keys().copied().collect())
+ && bindings_eq(rhs.pat, local_map.values().copied().collect())
+ };
+
+ let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
+ for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) {
+ span_lint_and_then(
+ cx,
+ MATCH_SAME_ARMS,
+ j.body.span,
+ "this `match` has identical arm bodies",
+ |diag| {
+ diag.span_note(i.body.span, "same as this");
+
+ // Note: this does not use `span_suggestion` on purpose:
+ // there is no clean way
+ // to remove the other arm. Building a span and suggest to replace it to ""
+ // makes an even more confusing error message. Also in order not to make up a
+ // span for the whole pattern, the suggestion is only shown when there is only
+ // one pattern. The user should know about `|` if they are already using it…
+
+ let lhs = snippet(cx, i.pat.span, "<pat1>");
+ let rhs = snippet(cx, j.pat.span, "<pat2>");
+
+ if let PatKind::Wild = j.pat.kind {
+ // if the last arm is _, then i could be integrated into _
+ // note that i.pat cannot be _, because that would mean that we're
+ // hiding all the subsequent arms, and rust won't compile
+ diag.span_note(
+ i.body.span,
+ &format!(
+ "`{}` has the same arm body as the `_` wildcard, consider removing it",
+ lhs
+ ),
+ );
+ } else {
+ diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs));
+ }
+ },
+ );
+ }
+ }
+}
+
+fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool {
+ let mut result = false;
+ pat.walk_short(|p| {
+ result |= matches!(p.kind, PatKind::Binding(_, binding_id, ..) if binding_id == id);
+ !result
+ });
+ result
+}
+
+/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa
+fn bindings_eq(pat: &Pat<'_>, mut ids: HirIdSet) -> bool {
+ let mut result = true;
+ pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id));
+ result && ids.is_empty()
+}
--- /dev/null
- #[rustfmt::skip]
- const PATTERN_METHODS: [(&str, usize); 17] = [
- ("contains", 1),
- ("starts_with", 1),
- ("ends_with", 1),
- ("find", 1),
- ("rfind", 1),
- ("split", 1),
- ("rsplit", 1),
- ("split_terminator", 1),
- ("rsplit_terminator", 1),
- ("splitn", 2),
- ("rsplitn", 2),
- ("matches", 1),
- ("rmatches", 1),
- ("match_indices", 1),
- ("rmatch_indices", 1),
- ("trim_start_matches", 1),
- ("trim_end_matches", 1),
- ];
-
+mod bind_instead_of_map;
+mod bytes_nth;
+mod chars_cmp;
+mod chars_cmp_with_unwrap;
+mod chars_last_cmp;
+mod chars_last_cmp_with_unwrap;
+mod chars_next_cmp;
+mod chars_next_cmp_with_unwrap;
+mod clone_on_copy;
+mod clone_on_ref_ptr;
+mod cloned_instead_of_copied;
+mod expect_fun_call;
+mod expect_used;
+mod filetype_is_file;
+mod filter_map;
+mod filter_map_identity;
+mod filter_map_next;
+mod filter_next;
+mod flat_map_identity;
+mod flat_map_option;
+mod from_iter_instead_of_collect;
+mod get_unwrap;
+mod implicit_clone;
+mod inefficient_to_string;
+mod inspect_for_each;
+mod into_iter_on_ref;
+mod iter_cloned_collect;
+mod iter_count;
+mod iter_next_slice;
+mod iter_nth;
+mod iter_nth_zero;
+mod iter_skip_next;
+mod iterator_step_by_zero;
+mod manual_saturating_arithmetic;
+mod map_collect_result_unit;
+mod map_flatten;
+mod map_unwrap_or;
+mod ok_expect;
+mod option_as_ref_deref;
+mod option_map_or_none;
+mod option_map_unwrap_or;
+mod or_fun_call;
+mod search_is_some;
+mod single_char_add_str;
+mod single_char_insert_string;
+mod single_char_pattern;
+mod single_char_push_string;
+mod skip_while_next;
+mod string_extend_chars;
+mod suspicious_map;
+mod uninit_assumed_init;
+mod unnecessary_filter_map;
+mod unnecessary_fold;
+mod unnecessary_lazy_eval;
+mod unwrap_used;
+mod useless_asref;
+mod utils;
+mod wrong_self_convention;
+mod zst_offset;
+
+use bind_instead_of_map::BindInsteadOfMap;
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item};
+use clippy_utils::{contains_return, get_trait_def_id, in_macro, iter_input_pats, paths, return_ty};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::{Expr, ExprKind, PrimTy, QPath, TraitItem, TraitItemKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, TraitRef, Ty, TyS};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::SymbolStr;
+use rustc_span::{sym, Span};
+use rustc_typeck::hir_ty_to_ty;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usages of `cloned()` on an `Iterator` or `Option` where
+ /// `copied()` could be used instead.
+ ///
+ /// **Why is this bad?** `copied()` is better because it guarantees that the type being cloned
+ /// implements `Copy`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// [1, 2, 3].iter().cloned();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// [1, 2, 3].iter().copied();
+ /// ```
+ pub CLONED_INSTEAD_OF_COPIED,
+ pedantic,
+ "used `cloned` where `copied` could be used instead"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usages of `Iterator::flat_map()` where `filter_map()` could be
+ /// used instead.
+ ///
+ /// **Why is this bad?** When applicable, `filter_map()` is more clear since it shows that
+ /// `Option` is used to produce 0 or 1 items.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// let nums: Vec<i32> = ["1", "2", "whee!"].iter().flat_map(|x| x.parse().ok()).collect();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let nums: Vec<i32> = ["1", "2", "whee!"].iter().filter_map(|x| x.parse().ok()).collect();
+ /// ```
+ pub FLAT_MAP_OPTION,
+ pedantic,
+ "used `flat_map` where `filter_map` could be used instead"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `.unwrap()` calls on `Option`s and on `Result`s.
+ ///
+ /// **Why is this bad?** It is better to handle the `None` or `Err` case,
+ /// or at least call `.expect(_)` with a more helpful message. Still, for a lot of
+ /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is
+ /// `Allow` by default.
+ ///
+ /// `result.unwrap()` will let the thread panic on `Err` values.
+ /// Normally, you want to implement more sophisticated error handling,
+ /// and propagate errors upwards with `?` operator.
+ ///
+ /// Even if you want to panic on errors, not all `Error`s implement good
+ /// messages on display. Therefore, it may be beneficial to look at the places
+ /// where they may get displayed. Activate this lint to do just that.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Examples:**
+ /// ```rust
+ /// # let opt = Some(1);
+ ///
+ /// // Bad
+ /// opt.unwrap();
+ ///
+ /// // Good
+ /// opt.expect("more helpful message");
+ /// ```
+ ///
+ /// // or
+ ///
+ /// ```rust
+ /// # let res: Result<usize, ()> = Ok(1);
+ ///
+ /// // Bad
+ /// res.unwrap();
+ ///
+ /// // Good
+ /// res.expect("more helpful message");
+ /// ```
+ pub UNWRAP_USED,
+ restriction,
+ "using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `.expect()` calls on `Option`s and `Result`s.
+ ///
+ /// **Why is this bad?** Usually it is better to handle the `None` or `Err` case.
+ /// Still, for a lot of quick-and-dirty code, `expect` is a good choice, which is why
+ /// this lint is `Allow` by default.
+ ///
+ /// `result.expect()` will let the thread panic on `Err`
+ /// values. Normally, you want to implement more sophisticated error handling,
+ /// and propagate errors upwards with `?` operator.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Examples:**
+ /// ```rust,ignore
+ /// # let opt = Some(1);
+ ///
+ /// // Bad
+ /// opt.expect("one");
+ ///
+ /// // Good
+ /// let opt = Some(1);
+ /// opt?;
+ /// ```
+ ///
+ /// // or
+ ///
+ /// ```rust
+ /// # let res: Result<usize, ()> = Ok(1);
+ ///
+ /// // Bad
+ /// res.expect("one");
+ ///
+ /// // Good
+ /// res?;
+ /// # Ok::<(), ()>(())
+ /// ```
+ pub EXPECT_USED,
+ restriction,
+ "using `.expect()` on `Result` or `Option`, which might be better handled"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for methods that should live in a trait
+ /// implementation of a `std` trait (see [llogiq's blog
+ /// post](http://llogiq.github.io/2015/07/30/traits.html) for further
+ /// information) instead of an inherent implementation.
+ ///
+ /// **Why is this bad?** Implementing the traits improve ergonomics for users of
+ /// the code, often with very little cost. Also people seeing a `mul(...)`
+ /// method
+ /// may expect `*` to work equally, so you should have good reason to disappoint
+ /// them.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// struct X;
+ /// impl X {
+ /// fn add(&self, other: &X) -> X {
+ /// // ..
+ /// # X
+ /// }
+ /// }
+ /// ```
+ pub SHOULD_IMPLEMENT_TRAIT,
+ style,
+ "defining a method that should be implementing a std trait"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for methods with certain name prefixes and which
+ /// doesn't match how self is taken. The actual rules are:
+ ///
+ /// |Prefix |Postfix |`self` taken | `self` type |
+ /// |-------|------------|-----------------------|--------------|
+ /// |`as_` | none |`&self` or `&mut self` | any |
+ /// |`from_`| none | none | any |
+ /// |`into_`| none |`self` | any |
+ /// |`is_` | none |`&self` or none | any |
+ /// |`to_` | `_mut` |`&mut self` | any |
+ /// |`to_` | not `_mut` |`self` | `Copy` |
+ /// |`to_` | not `_mut` |`&self` | not `Copy` |
+ ///
+ /// Note: Clippy doesn't trigger methods with `to_` prefix in:
+ /// - Traits definition.
+ /// Clippy can not tell if a type that implements a trait is `Copy` or not.
+ /// - Traits implementation, when `&self` is taken.
+ /// The method signature is controlled by the trait and often `&self` is required for all types that implement the trait
+ /// (see e.g. the `std::string::ToString` trait).
+ ///
+ /// Please find more info here:
+ /// https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
+ ///
+ /// **Why is this bad?** Consistency breeds readability. If you follow the
+ /// conventions, your users won't be surprised that they, e.g., need to supply a
+ /// mutable reference to a `as_..` function.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # struct X;
+ /// impl X {
+ /// fn as_str(self) -> &'static str {
+ /// // ..
+ /// # ""
+ /// }
+ /// }
+ /// ```
+ pub WRONG_SELF_CONVENTION,
+ style,
+ "defining a method named with an established prefix (like \"into_\") that takes `self` with the wrong convention"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** This is the same as
+ /// [`wrong_self_convention`](#wrong_self_convention), but for public items.
+ ///
+ /// **Why is this bad?** See [`wrong_self_convention`](#wrong_self_convention).
+ ///
+ /// **Known problems:** Actually *renaming* the function may break clients if
+ /// the function is part of the public interface. In that case, be mindful of
+ /// the stability guarantees you've given your users.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # struct X;
+ /// impl<'a> X {
+ /// pub fn as_str(self) -> &'a str {
+ /// "foo"
+ /// }
+ /// }
+ /// ```
+ pub WRONG_PUB_SELF_CONVENTION,
+ restriction,
+ "defining a public method named with an established prefix (like \"into_\") that takes `self` with the wrong convention"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `ok().expect(..)`.
+ ///
+ /// **Why is this bad?** Because you usually call `expect()` on the `Result`
+ /// directly to get a better error message.
+ ///
+ /// **Known problems:** The error type needs to implement `Debug`
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let x = Ok::<_, ()>(());
+ ///
+ /// // Bad
+ /// x.ok().expect("why did I do this again?");
+ ///
+ /// // Good
+ /// x.expect("why did I do this again?");
+ /// ```
+ pub OK_EXPECT,
+ style,
+ "using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `option.map(_).unwrap_or(_)` or `option.map(_).unwrap_or_else(_)` or
+ /// `result.map(_).unwrap_or_else(_)`.
+ ///
+ /// **Why is this bad?** Readability, these can be written more concisely (resp.) as
+ /// `option.map_or(_, _)`, `option.map_or_else(_, _)` and `result.map_or_else(_, _)`.
+ ///
+ /// **Known problems:** The order of the arguments is not in execution order
+ ///
+ /// **Examples:**
+ /// ```rust
+ /// # let x = Some(1);
+ ///
+ /// // Bad
+ /// x.map(|a| a + 1).unwrap_or(0);
+ ///
+ /// // Good
+ /// x.map_or(0, |a| a + 1);
+ /// ```
+ ///
+ /// // or
+ ///
+ /// ```rust
+ /// # let x: Result<usize, ()> = Ok(1);
+ /// # fn some_function(foo: ()) -> usize { 1 }
+ ///
+ /// // Bad
+ /// x.map(|a| a + 1).unwrap_or_else(some_function);
+ ///
+ /// // Good
+ /// x.map_or_else(some_function, |a| a + 1);
+ /// ```
+ pub MAP_UNWRAP_OR,
+ pedantic,
+ "using `.map(f).unwrap_or(a)` or `.map(f).unwrap_or_else(func)`, which are more succinctly expressed as `map_or(a, f)` or `map_or_else(a, f)`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.map_or(None, _)`.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as
+ /// `_.and_then(_)`.
+ ///
+ /// **Known problems:** The order of the arguments is not in execution order.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let opt = Some(1);
+ ///
+ /// // Bad
+ /// opt.map_or(None, |a| Some(a + 1));
+ ///
+ /// // Good
+ /// opt.and_then(|a| Some(a + 1));
+ /// ```
+ pub OPTION_MAP_OR_NONE,
+ style,
+ "using `Option.map_or(None, f)`, which is more succinctly expressed as `and_then(f)`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.map_or(None, Some)`.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as
+ /// `_.ok()`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// Bad:
+ /// ```rust
+ /// # let r: Result<u32, &str> = Ok(1);
+ /// assert_eq!(Some(1), r.map_or(None, Some));
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// # let r: Result<u32, &str> = Ok(1);
+ /// assert_eq!(Some(1), r.ok());
+ /// ```
+ pub RESULT_MAP_OR_INTO_OPTION,
+ style,
+ "using `Result.map_or(None, Some)`, which is more succinctly expressed as `ok()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` or
+ /// `_.or_else(|x| Err(y))`.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as
+ /// `_.map(|x| y)` or `_.map_err(|x| y)`.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # fn opt() -> Option<&'static str> { Some("42") }
+ /// # fn res() -> Result<&'static str, &'static str> { Ok("42") }
+ /// let _ = opt().and_then(|s| Some(s.len()));
+ /// let _ = res().and_then(|s| if s.len() == 42 { Ok(10) } else { Ok(20) });
+ /// let _ = res().or_else(|s| if s.len() == 42 { Err(10) } else { Err(20) });
+ /// ```
+ ///
+ /// The correct use would be:
+ ///
+ /// ```rust
+ /// # fn opt() -> Option<&'static str> { Some("42") }
+ /// # fn res() -> Result<&'static str, &'static str> { Ok("42") }
+ /// let _ = opt().map(|s| s.len());
+ /// let _ = res().map(|s| if s.len() == 42 { 10 } else { 20 });
+ /// let _ = res().map_err(|s| if s.len() == 42 { 10 } else { 20 });
+ /// ```
+ pub BIND_INSTEAD_OF_MAP,
+ complexity,
+ "using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.filter(_).next()`.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as
+ /// `_.find(_)`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().filter(|x| **x == 0).next();
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().find(|x| **x == 0);
+ /// ```
+ pub FILTER_NEXT,
+ complexity,
+ "using `filter(p).next()`, which is more succinctly expressed as `.find(p)`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.skip_while(condition).next()`.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as
+ /// `_.find(!condition)`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().skip_while(|x| **x == 0).next();
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// # let vec = vec![1];
+ /// vec.iter().find(|x| **x != 0);
+ /// ```
+ pub SKIP_WHILE_NEXT,
+ complexity,
+ "using `skip_while(p).next()`, which is more succinctly expressed as `.find(!p)`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.map(_).flatten(_)` on `Iterator` and `Option`
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as
+ /// `_.flat_map(_)`
+ ///
+ /// **Known problems:**
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let vec = vec![vec![1]];
+ ///
+ /// // Bad
+ /// vec.iter().map(|x| x.iter()).flatten();
+ ///
+ /// // Good
+ /// vec.iter().flat_map(|x| x.iter());
+ /// ```
+ pub MAP_FLATTEN,
+ pedantic,
+ "using combinations of `flatten` and `map` which can usually be written as a single method call"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.filter(_).map(_)` that can be written more simply
+ /// as `filter_map(_)`.
+ ///
+ /// **Why is this bad?** Redundant code in the `filter` and `map` operations is poor style and
+ /// less performant.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// Bad:
+ /// ```rust
+ /// (0_i32..10)
+ /// .filter(|n| n.checked_add(1).is_some())
+ /// .map(|n| n.checked_add(1).unwrap());
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// (0_i32..10).filter_map(|n| n.checked_add(1));
+ /// ```
+ pub MANUAL_FILTER_MAP,
+ complexity,
+ "using `_.filter(_).map(_)` in a way that can be written more simply as `filter_map(_)`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.find(_).map(_)` that can be written more simply
+ /// as `find_map(_)`.
+ ///
+ /// **Why is this bad?** Redundant code in the `find` and `map` operations is poor style and
+ /// less performant.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// Bad:
+ /// ```rust
+ /// (0_i32..10)
+ /// .find(|n| n.checked_add(1).is_some())
+ /// .map(|n| n.checked_add(1).unwrap());
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// (0_i32..10).find_map(|n| n.checked_add(1));
+ /// ```
+ pub MANUAL_FIND_MAP,
+ complexity,
+ "using `_.find(_).map(_)` in a way that can be written more simply as `find_map(_)`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.filter_map(_).next()`.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as
+ /// `_.find_map(_)`.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```rust
+ /// (0..3).filter_map(|x| if x == 2 { Some(x) } else { None }).next();
+ /// ```
+ /// Can be written as
+ ///
+ /// ```rust
+ /// (0..3).find_map(|x| if x == 2 { Some(x) } else { None });
+ /// ```
+ pub FILTER_MAP_NEXT,
+ pedantic,
+ "using combination of `filter_map` and `next` which can usually be written as a single method call"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `flat_map(|x| x)`.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely by using `flatten`.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let iter = vec![vec![0]].into_iter();
+ /// iter.flat_map(|x| x);
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// # let iter = vec![vec![0]].into_iter();
+ /// iter.flatten();
+ /// ```
+ pub FLAT_MAP_IDENTITY,
+ complexity,
+ "call to `flat_map` where `flatten` is sufficient"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for an iterator or string search (such as `find()`,
+ /// `position()`, or `rposition()`) followed by a call to `is_some()` or `is_none()`.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as:
+ /// * `_.any(_)`, or `_.contains(_)` for `is_some()`,
+ /// * `!_.any(_)`, or `!_.contains(_)` for `is_none()`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let vec = vec![1];
+ /// vec.iter().find(|x| **x == 0).is_some();
+ ///
+ /// let _ = "hello world".find("world").is_none();
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// let vec = vec![1];
+ /// vec.iter().any(|x| *x == 0);
+ ///
+ /// let _ = !"hello world".contains("world");
+ /// ```
+ pub SEARCH_IS_SOME,
+ complexity,
+ "using an iterator or string search followed by `is_some()` or `is_none()`, which is more succinctly expressed as a call to `any()` or `contains()` (with negation in case of `is_none()`)"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `.chars().next()` on a `str` to check
+ /// if it starts with a given char.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as
+ /// `_.starts_with(_)`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let name = "foo";
+ /// if name.chars().next() == Some('_') {};
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// let name = "foo";
+ /// if name.starts_with('_') {};
+ /// ```
+ pub CHARS_NEXT_CMP,
+ style,
+ "using `.chars().next()` to check if a string starts with a char"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`,
+ /// etc., and suggests to use `or_else`, `unwrap_or_else`, etc., or
+ /// `unwrap_or_default` instead.
+ ///
+ /// **Why is this bad?** The function will always be called and potentially
+ /// allocate an object acting as the default.
+ ///
+ /// **Known problems:** If the function has side-effects, not calling it will
+ /// change the semantic of the program, but you shouldn't rely on that anyway.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or(String::new());
+ /// ```
+ /// this can instead be written:
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or_else(String::new);
+ /// ```
+ /// or
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// foo.unwrap_or_default();
+ /// ```
+ pub OR_FUN_CALL,
+ perf,
+ "using any `*or` method with a function call, which suggests `*or_else`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`,
+ /// etc., and suggests to use `unwrap_or_else` instead
+ ///
+ /// **Why is this bad?** The function will always be called.
+ ///
+ /// **Known problems:** If the function has side-effects, not calling it will
+ /// change the semantics of the program, but you shouldn't rely on that anyway.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// # let err_code = "418";
+ /// # let err_msg = "I'm a teapot";
+ /// foo.expect(&format!("Err {}: {}", err_code, err_msg));
+ /// ```
+ /// or
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// # let err_code = "418";
+ /// # let err_msg = "I'm a teapot";
+ /// foo.expect(format!("Err {}: {}", err_code, err_msg).as_str());
+ /// ```
+ /// this can instead be written:
+ /// ```rust
+ /// # let foo = Some(String::new());
+ /// # let err_code = "418";
+ /// # let err_msg = "I'm a teapot";
+ /// foo.unwrap_or_else(|| panic!("Err {}: {}", err_code, err_msg));
+ /// ```
+ pub EXPECT_FUN_CALL,
+ perf,
+ "using any `expect` method with a function call"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `.clone()` on a `Copy` type.
+ ///
+ /// **Why is this bad?** The only reason `Copy` types implement `Clone` is for
+ /// generics, not for using the `clone` method on a concrete type.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// 42u64.clone();
+ /// ```
+ pub CLONE_ON_COPY,
+ complexity,
+ "using `clone` on a `Copy` type"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `.clone()` on a ref-counted pointer,
+ /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified
+ /// function syntax instead (e.g., `Rc::clone(foo)`).
+ ///
+ /// **Why is this bad?** Calling '.clone()' on an Rc, Arc, or Weak
+ /// can obscure the fact that only the pointer is being cloned, not the underlying
+ /// data.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # use std::rc::Rc;
+ /// let x = Rc::new(1);
+ ///
+ /// // Bad
+ /// x.clone();
+ ///
+ /// // Good
+ /// Rc::clone(&x);
+ /// ```
+ pub CLONE_ON_REF_PTR,
+ restriction,
+ "using 'clone' on a ref-counted pointer"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `.clone()` on an `&&T`.
+ ///
+ /// **Why is this bad?** Cloning an `&&T` copies the inner `&T`, instead of
+ /// cloning the underlying `T`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// fn main() {
+ /// let x = vec![1];
+ /// let y = &&x;
+ /// let z = y.clone();
+ /// println!("{:p} {:p}", *y, z); // prints out the same pointer
+ /// }
+ /// ```
+ pub CLONE_DOUBLE_REF,
+ correctness,
+ "using `clone` on `&&T`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `.to_string()` on an `&&T` where
+ /// `T` implements `ToString` directly (like `&&str` or `&&String`).
+ ///
+ /// **Why is this bad?** This bypasses the specialized implementation of
+ /// `ToString` and instead goes through the more expensive string formatting
+ /// facilities.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// // Generic implementation for `T: Display` is used (slow)
+ /// ["foo", "bar"].iter().map(|s| s.to_string());
+ ///
+ /// // OK, the specialized impl is used
+ /// ["foo", "bar"].iter().map(|&s| s.to_string());
+ /// ```
+ pub INEFFICIENT_TO_STRING,
+ pedantic,
+ "using `to_string` on `&&T` where `T: ToString`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `new` not returning a type that contains `Self`.
+ ///
+ /// **Why is this bad?** As a convention, `new` methods are used to make a new
+ /// instance of a type.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// In an impl block:
+ /// ```rust
+ /// # struct Foo;
+ /// # struct NotAFoo;
+ /// impl Foo {
+ /// fn new() -> NotAFoo {
+ /// # NotAFoo
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// # struct Foo;
+ /// struct Bar(Foo);
+ /// impl Foo {
+ /// // Bad. The type name must contain `Self`
+ /// fn new() -> Bar {
+ /// # Bar(Foo)
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// # struct Foo;
+ /// # struct FooError;
+ /// impl Foo {
+ /// // Good. Return type contains `Self`
+ /// fn new() -> Result<Foo, FooError> {
+ /// # Ok(Foo)
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Or in a trait definition:
+ /// ```rust
+ /// pub trait Trait {
+ /// // Bad. The type name must contain `Self`
+ /// fn new();
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// pub trait Trait {
+ /// // Good. Return type contains `Self`
+ /// fn new() -> Self;
+ /// }
+ /// ```
+ pub NEW_RET_NO_SELF,
+ style,
+ "not returning type containing `Self` in a `new` method"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for string methods that receive a single-character
+ /// `str` as an argument, e.g., `_.split("x")`.
+ ///
+ /// **Why is this bad?** Performing these methods using a `char` is faster than
+ /// using a `str`.
+ ///
+ /// **Known problems:** Does not catch multi-byte unicode characters.
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// // Bad
+ /// _.split("x");
+ ///
+ /// // Good
+ /// _.split('x');
+ pub SINGLE_CHAR_PATTERN,
+ perf,
+ "using a single-character str where a char could be used, e.g., `_.split(\"x\")`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for calling `.step_by(0)` on iterators which panics.
+ ///
+ /// **Why is this bad?** This very much looks like an oversight. Use `panic!()` instead if you
+ /// actually intend to panic.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust,should_panic
+ /// for x in (0..100).step_by(0) {
+ /// //..
+ /// }
+ /// ```
+ pub ITERATOR_STEP_BY_ZERO,
+ correctness,
+ "using `Iterator::step_by(0)`, which will panic at runtime"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for indirect collection of populated `Option`
+ ///
+ /// **Why is this bad?** `Option` is like a collection of 0-1 things, so `flatten`
+ /// automatically does this without suspicious-looking `unwrap` calls.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// let _ = std::iter::empty::<Option<i32>>().filter(Option::is_some).map(Option::unwrap);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let _ = std::iter::empty::<Option<i32>>().flatten();
+ /// ```
+ pub OPTION_FILTER_MAP,
+ complexity,
+ "filtering `Option` for `Some` then force-unwrapping, which can be one type-safe operation"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for the use of `iter.nth(0)`.
+ ///
+ /// **Why is this bad?** `iter.next()` is equivalent to
+ /// `iter.nth(0)`, as they both consume the next element,
+ /// but is more readable.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// // Bad
+ /// # let mut s = HashSet::new();
+ /// # s.insert(1);
+ /// let x = s.iter().nth(0);
+ ///
+ /// // Good
+ /// # let mut s = HashSet::new();
+ /// # s.insert(1);
+ /// let x = s.iter().next();
+ /// ```
+ pub ITER_NTH_ZERO,
+ style,
+ "replace `iter.nth(0)` with `iter.next()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for use of `.iter().nth()` (and the related
+ /// `.iter_mut().nth()`) on standard library types with O(1) element access.
+ ///
+ /// **Why is this bad?** `.get()` and `.get_mut()` are more efficient and more
+ /// readable.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().nth(3);
+ /// let bad_slice = &some_vec[..].iter().nth(3);
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.get(3);
+ /// let bad_slice = &some_vec[..].get(3);
+ /// ```
+ pub ITER_NTH,
+ perf,
+ "using `.iter().nth()` on a standard library type with O(1) element access"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for use of `.skip(x).next()` on iterators.
+ ///
+ /// **Why is this bad?** `.nth(x)` is cleaner
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().skip(3).next();
+ /// let bad_slice = &some_vec[..].iter().skip(3).next();
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let bad_vec = some_vec.iter().nth(3);
+ /// let bad_slice = &some_vec[..].iter().nth(3);
+ /// ```
+ pub ITER_SKIP_NEXT,
+ style,
+ "using `.skip(x).next()` on an iterator"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for use of `.get().unwrap()` (or
+ /// `.get_mut().unwrap`) on a standard library type which implements `Index`
+ ///
+ /// **Why is this bad?** Using the Index trait (`[]`) is more clear and more
+ /// concise.
+ ///
+ /// **Known problems:** Not a replacement for error handling: Using either
+ /// `.unwrap()` or the Index trait (`[]`) carries the risk of causing a `panic`
+ /// if the value being accessed is `None`. If the use of `.get().unwrap()` is a
+ /// temporary placeholder for dealing with the `Option` type, then this does
+ /// not mitigate the need for error handling. If there is a chance that `.get()`
+ /// will be `None` in your program, then it is advisable that the `None` case
+ /// is handled in a future refactor instead of using `.unwrap()` or the Index
+ /// trait.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let mut some_vec = vec![0, 1, 2, 3];
+ /// let last = some_vec.get(3).unwrap();
+ /// *some_vec.get_mut(0).unwrap() = 1;
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let mut some_vec = vec![0, 1, 2, 3];
+ /// let last = some_vec[3];
+ /// some_vec[0] = 1;
+ /// ```
+ pub GET_UNWRAP,
+ restriction,
+ "using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for the use of `.extend(s.chars())` where s is a
+ /// `&str` or `String`.
+ ///
+ /// **Why is this bad?** `.push_str(s)` is clearer
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let abc = "abc";
+ /// let def = String::from("def");
+ /// let mut s = String::new();
+ /// s.extend(abc.chars());
+ /// s.extend(def.chars());
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let abc = "abc";
+ /// let def = String::from("def");
+ /// let mut s = String::new();
+ /// s.push_str(abc);
+ /// s.push_str(&def);
+ /// ```
+ pub STRING_EXTEND_CHARS,
+ style,
+ "using `x.extend(s.chars())` where s is a `&str` or `String`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for the use of `.cloned().collect()` on slice to
+ /// create a `Vec`.
+ ///
+ /// **Why is this bad?** `.to_vec()` is clearer
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let s = [1, 2, 3, 4, 5];
+ /// let s2: Vec<isize> = s[..].iter().cloned().collect();
+ /// ```
+ /// The better use would be:
+ /// ```rust
+ /// let s = [1, 2, 3, 4, 5];
+ /// let s2: Vec<isize> = s.to_vec();
+ /// ```
+ pub ITER_CLONED_COLLECT,
+ style,
+ "using `.cloned().collect()` on slice to create a `Vec`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.chars().last()` or
+ /// `_.chars().next_back()` on a `str` to check if it ends with a given char.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as
+ /// `_.ends_with(_)`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let name = "_";
+ ///
+ /// // Bad
+ /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-');
+ ///
+ /// // Good
+ /// name.ends_with('_') || name.ends_with('-');
+ /// ```
+ pub CHARS_LAST_CMP,
+ style,
+ "using `.chars().last()` or `.chars().next_back()` to check if a string ends with a char"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `.as_ref()` or `.as_mut()` where the
+ /// types before and after the call are the same.
+ ///
+ /// **Why is this bad?** The call is unnecessary.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # fn do_stuff(x: &[i32]) {}
+ /// let x: &[i32] = &[1, 2, 3, 4, 5];
+ /// do_stuff(x.as_ref());
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// # fn do_stuff(x: &[i32]) {}
+ /// let x: &[i32] = &[1, 2, 3, 4, 5];
+ /// do_stuff(x);
+ /// ```
+ pub USELESS_ASREF,
+ complexity,
+ "using `as_ref` where the types before and after the call are the same"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for using `fold` when a more succinct alternative exists.
+ /// Specifically, this checks for `fold`s which could be replaced by `any`, `all`,
+ /// `sum` or `product`.
+ ///
+ /// **Why is this bad?** Readability.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let _ = (0..3).fold(false, |acc, x| acc || x > 2);
+ /// ```
+ /// This could be written as:
+ /// ```rust
+ /// let _ = (0..3).any(|x| x > 2);
+ /// ```
+ pub UNNECESSARY_FOLD,
+ style,
+ "using `fold` when a more succinct alternative exists"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `filter_map` calls which could be replaced by `filter` or `map`.
+ /// More specifically it checks if the closure provided is only performing one of the
+ /// filter or map operations and suggests the appropriate option.
+ ///
+ /// **Why is this bad?** Complexity. The intent is also clearer if only a single
+ /// operation is being performed.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let _ = (0..3).filter_map(|x| if x > 2 { Some(x) } else { None });
+ ///
+ /// // As there is no transformation of the argument this could be written as:
+ /// let _ = (0..3).filter(|&x| x > 2);
+ /// ```
+ ///
+ /// ```rust
+ /// let _ = (0..4).filter_map(|x| Some(x + 1));
+ ///
+ /// // As there is no conditional check on the argument this could be written as:
+ /// let _ = (0..4).map(|x| x + 1);
+ /// ```
+ pub UNNECESSARY_FILTER_MAP,
+ complexity,
+ "using `filter_map` when a more succinct alternative exists"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `into_iter` calls on references which should be replaced by `iter`
+ /// or `iter_mut`.
+ ///
+ /// **Why is this bad?** Readability. Calling `into_iter` on a reference will not move out its
+ /// content into the resulting iterator, which is confusing. It is better just call `iter` or
+ /// `iter_mut` directly.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// // Bad
+ /// let _ = (&vec![3, 4, 5]).into_iter();
+ ///
+ /// // Good
+ /// let _ = (&vec![3, 4, 5]).iter();
+ /// ```
+ pub INTO_ITER_ON_REF,
+ style,
+ "using `.into_iter()` on a reference"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for calls to `map` followed by a `count`.
+ ///
+ /// **Why is this bad?** It looks suspicious. Maybe `map` was confused with `filter`.
+ /// If the `map` call is intentional, this should be rewritten. Or, if you intend to
+ /// drive the iterator to completion, you can just use `for_each` instead.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// let _ = (0..3).map(|x| x + 2).count();
+ /// ```
+ pub SUSPICIOUS_MAP,
+ complexity,
+ "suspicious usage of map"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `MaybeUninit::uninit().assume_init()`.
+ ///
+ /// **Why is this bad?** For most types, this is undefined behavior.
+ ///
+ /// **Known problems:** For now, we accept empty tuples and tuples / arrays
+ /// of `MaybeUninit`. There may be other types that allow uninitialized
+ /// data, but those are not yet rigorously defined.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// // Beware the UB
+ /// use std::mem::MaybeUninit;
+ ///
+ /// let _: usize = unsafe { MaybeUninit::uninit().assume_init() };
+ /// ```
+ ///
+ /// Note that the following is OK:
+ ///
+ /// ```rust
+ /// use std::mem::MaybeUninit;
+ ///
+ /// let _: [MaybeUninit<bool>; 5] = unsafe {
+ /// MaybeUninit::uninit().assume_init()
+ /// };
+ /// ```
+ pub UNINIT_ASSUMED_INIT,
+ correctness,
+ "`MaybeUninit::uninit().assume_init()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `.checked_add/sub(x).unwrap_or(MAX/MIN)`.
+ ///
+ /// **Why is this bad?** These can be written simply with `saturating_add/sub` methods.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # let y: u32 = 0;
+ /// # let x: u32 = 100;
+ /// let add = x.checked_add(y).unwrap_or(u32::MAX);
+ /// let sub = x.checked_sub(y).unwrap_or(u32::MIN);
+ /// ```
+ ///
+ /// can be written using dedicated methods for saturating addition/subtraction as:
+ ///
+ /// ```rust
+ /// # let y: u32 = 0;
+ /// # let x: u32 = 100;
+ /// let add = x.saturating_add(y);
+ /// let sub = x.saturating_sub(y);
+ /// ```
+ pub MANUAL_SATURATING_ARITHMETIC,
+ style,
+ "`.chcked_add/sub(x).unwrap_or(MAX/MIN)`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `offset(_)`, `wrapping_`{`add`, `sub`}, etc. on raw pointers to
+ /// zero-sized types
+ ///
+ /// **Why is this bad?** This is a no-op, and likely unintended
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```rust
+ /// unsafe { (&() as *const ()).offset(1) };
+ /// ```
+ pub ZST_OFFSET,
+ correctness,
+ "Check for offset calculations on raw pointers to zero-sized types"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `FileType::is_file()`.
+ ///
+ /// **Why is this bad?** When people testing a file type with `FileType::is_file`
+ /// they are testing whether a path is something they can get bytes from. But
+ /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover
+ /// symlink in windows. Using `!FileType::is_dir()` is a better way to that intention.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # || {
+ /// let metadata = std::fs::metadata("foo.txt")?;
+ /// let filetype = metadata.file_type();
+ ///
+ /// if filetype.is_file() {
+ /// // read file
+ /// }
+ /// # Ok::<_, std::io::Error>(())
+ /// # };
+ /// ```
+ ///
+ /// should be written as:
+ ///
+ /// ```rust
+ /// # || {
+ /// let metadata = std::fs::metadata("foo.txt")?;
+ /// let filetype = metadata.file_type();
+ ///
+ /// if !filetype.is_dir() {
+ /// // read file
+ /// }
+ /// # Ok::<_, std::io::Error>(())
+ /// # };
+ /// ```
+ pub FILETYPE_IS_FILE,
+ restriction,
+ "`FileType::is_file` is not recommended to test for readable file type"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.as_ref().map(Deref::deref)` or it's aliases (such as String::as_str).
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely as
+ /// `_.as_deref()`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let opt = Some("".to_string());
+ /// opt.as_ref().map(String::as_str)
+ /// # ;
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// # let opt = Some("".to_string());
+ /// opt.as_deref()
+ /// # ;
+ /// ```
+ pub OPTION_AS_REF_DEREF,
+ complexity,
+ "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `iter().next()` on a Slice or an Array
+ ///
+ /// **Why is this bad?** These can be shortened into `.get()`
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// # let a = [1, 2, 3];
+ /// # let b = vec![1, 2, 3];
+ /// a[2..].iter().next();
+ /// b.iter().next();
+ /// ```
+ /// should be written as:
+ /// ```rust
+ /// # let a = [1, 2, 3];
+ /// # let b = vec![1, 2, 3];
+ /// a.get(2);
+ /// b.get(0);
+ /// ```
+ pub ITER_NEXT_SLICE,
+ style,
+ "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Warns when using `push_str`/`insert_str` with a single-character string literal
+ /// where `push`/`insert` with a `char` would work fine.
+ ///
+ /// **Why is this bad?** It's less clear that we are pushing a single character.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// ```rust
+ /// let mut string = String::new();
+ /// string.insert_str(0, "R");
+ /// string.push_str("R");
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// let mut string = String::new();
+ /// string.insert(0, 'R');
+ /// string.push('R');
+ /// ```
+ pub SINGLE_CHAR_ADD_STR,
+ style,
+ "`push_str()` or `insert_str()` used with a single-character string literal as parameter"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** As the counterpart to `or_fun_call`, this lint looks for unnecessary
+ /// lazily evaluated closures on `Option` and `Result`.
+ ///
+ /// This lint suggests changing the following functions, when eager evaluation results in
+ /// simpler code:
+ /// - `unwrap_or_else` to `unwrap_or`
+ /// - `and_then` to `and`
+ /// - `or_else` to `or`
+ /// - `get_or_insert_with` to `get_or_insert`
+ /// - `ok_or_else` to `ok_or`
+ ///
+ /// **Why is this bad?** Using eager evaluation is shorter and simpler in some cases.
+ ///
+ /// **Known problems:** It is possible, but not recommended for `Deref` and `Index` to have
+ /// side effects. Eagerly evaluating them can change the semantics of the program.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// // example code where clippy issues a warning
+ /// let opt: Option<u32> = None;
+ ///
+ /// opt.unwrap_or_else(|| 42);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let opt: Option<u32> = None;
+ ///
+ /// opt.unwrap_or(42);
+ /// ```
+ pub UNNECESSARY_LAZY_EVALUATIONS,
+ style,
+ "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `_.map(_).collect::<Result<(), _>()`.
+ ///
+ /// **Why is this bad?** Using `try_for_each` instead is more readable and idiomatic.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// (0..3).map(|t| Err(t)).collect::<Result<(), _>>();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// (0..3).try_for_each(|t| Err(t));
+ /// ```
+ pub MAP_COLLECT_RESULT_UNIT,
+ style,
+ "using `.map(_).collect::<Result<(),_>()`, which can be replaced with `try_for_each`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for `from_iter()` function calls on types that implement the `FromIterator`
+ /// trait.
+ ///
+ /// **Why is this bad?** It is recommended style to use collect. See
+ /// [FromIterator documentation](https://doc.rust-lang.org/std/iter/trait.FromIterator.html)
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// use std::iter::FromIterator;
+ ///
+ /// let five_fives = std::iter::repeat(5).take(5);
+ ///
+ /// let v = Vec::from_iter(five_fives);
+ ///
+ /// assert_eq!(v, vec![5, 5, 5, 5, 5]);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let five_fives = std::iter::repeat(5).take(5);
+ ///
+ /// let v: Vec<i32> = five_fives.collect();
+ ///
+ /// assert_eq!(v, vec![5, 5, 5, 5, 5]);
+ /// ```
+ pub FROM_ITER_INSTEAD_OF_COLLECT,
+ style,
+ "use `.collect()` instead of `::from_iter()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `inspect().for_each()`.
+ ///
+ /// **Why is this bad?** It is the same as performing the computation
+ /// inside `inspect` at the beginning of the closure in `for_each`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// [1,2,3,4,5].iter()
+ /// .inspect(|&x| println!("inspect the number: {}", x))
+ /// .for_each(|&x| {
+ /// assert!(x >= 0);
+ /// });
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// [1,2,3,4,5].iter()
+ /// .for_each(|&x| {
+ /// println!("inspect the number: {}", x);
+ /// assert!(x >= 0);
+ /// });
+ /// ```
+ pub INSPECT_FOR_EACH,
+ complexity,
+ "using `.inspect().for_each()`, which can be replaced with `.for_each()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for usage of `filter_map(|x| x)`.
+ ///
+ /// **Why is this bad?** Readability, this can be written more concisely by using `flatten`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// # let iter = vec![Some(1)].into_iter();
+ /// iter.filter_map(|x| x);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let iter = vec![Some(1)].into_iter();
+ /// iter.flatten();
+ /// ```
+ pub FILTER_MAP_IDENTITY,
+ complexity,
+ "call to `filter_map` where `flatten` is sufficient"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for the use of `.bytes().nth()`.
+ ///
+ /// **Why is this bad?** `.as_bytes().get()` is more efficient and more
+ /// readable.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// // Bad
+ /// let _ = "Hello".bytes().nth(3);
+ ///
+ /// // Good
+ /// let _ = "Hello".as_bytes().get(3);
+ /// ```
+ pub BYTES_NTH,
+ style,
+ "replace `.bytes().nth()` with `.as_bytes().get()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for the usage of `_.to_owned()`, `vec.to_vec()`, or similar when calling `_.clone()` would be clearer.
+ ///
+ /// **Why is this bad?** These methods do the same thing as `_.clone()` but may be confusing as
+ /// to why we are calling `to_vec` on something that is already a `Vec` or calling `to_owned` on something that is already owned.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// let a = vec![1, 2, 3];
+ /// let b = a.to_vec();
+ /// let c = a.to_owned();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let a = vec![1, 2, 3];
+ /// let b = a.clone();
+ /// let c = a.clone();
+ /// ```
+ pub IMPLICIT_CLONE,
+ pedantic,
+ "implicitly cloning a value by invoking a function on its dereferenced type"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for the use of `.iter().count()`.
+ ///
+ /// **Why is this bad?** `.len()` is more efficient and more
+ /// readable.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// // Bad
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let _ = some_vec.iter().count();
+ /// let _ = &some_vec[..].iter().count();
+ ///
+ /// // Good
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let _ = some_vec.len();
+ /// let _ = &some_vec[..].len();
+ /// ```
+ pub ITER_COUNT,
+ complexity,
+ "replace `.iter().count()` with `.len()`"
+}
+
+pub struct Methods {
+ msrv: Option<RustcVersion>,
+}
+
+impl Methods {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(Methods => [
+ UNWRAP_USED,
+ EXPECT_USED,
+ SHOULD_IMPLEMENT_TRAIT,
+ WRONG_SELF_CONVENTION,
+ WRONG_PUB_SELF_CONVENTION,
+ OK_EXPECT,
+ MAP_UNWRAP_OR,
+ RESULT_MAP_OR_INTO_OPTION,
+ OPTION_MAP_OR_NONE,
+ BIND_INSTEAD_OF_MAP,
+ OR_FUN_CALL,
+ EXPECT_FUN_CALL,
+ CHARS_NEXT_CMP,
+ CHARS_LAST_CMP,
+ CLONE_ON_COPY,
+ CLONE_ON_REF_PTR,
+ CLONE_DOUBLE_REF,
+ CLONED_INSTEAD_OF_COPIED,
+ FLAT_MAP_OPTION,
+ INEFFICIENT_TO_STRING,
+ NEW_RET_NO_SELF,
+ SINGLE_CHAR_PATTERN,
+ SINGLE_CHAR_ADD_STR,
+ SEARCH_IS_SOME,
+ FILTER_NEXT,
+ SKIP_WHILE_NEXT,
+ FILTER_MAP_IDENTITY,
+ MANUAL_FILTER_MAP,
+ MANUAL_FIND_MAP,
+ OPTION_FILTER_MAP,
+ FILTER_MAP_NEXT,
+ FLAT_MAP_IDENTITY,
+ MAP_FLATTEN,
+ ITERATOR_STEP_BY_ZERO,
+ ITER_NEXT_SLICE,
+ ITER_COUNT,
+ ITER_NTH,
+ ITER_NTH_ZERO,
+ BYTES_NTH,
+ ITER_SKIP_NEXT,
+ GET_UNWRAP,
+ STRING_EXTEND_CHARS,
+ ITER_CLONED_COLLECT,
+ USELESS_ASREF,
+ UNNECESSARY_FOLD,
+ UNNECESSARY_FILTER_MAP,
+ INTO_ITER_ON_REF,
+ SUSPICIOUS_MAP,
+ UNINIT_ASSUMED_INIT,
+ MANUAL_SATURATING_ARITHMETIC,
+ ZST_OFFSET,
+ FILETYPE_IS_FILE,
+ OPTION_AS_REF_DEREF,
+ UNNECESSARY_LAZY_EVALUATIONS,
+ MAP_COLLECT_RESULT_UNIT,
+ FROM_ITER_INSTEAD_OF_COLLECT,
+ INSPECT_FOR_EACH,
+ IMPLICIT_CLONE
+]);
+
+/// Extracts a method call name, args, and `Span` of the method name.
+fn method_call<'tcx>(recv: &'tcx hir::Expr<'tcx>) -> Option<(SymbolStr, &'tcx [hir::Expr<'tcx>], Span)> {
+ if let ExprKind::MethodCall(path, span, args, _) = recv.kind {
+ if !args.iter().any(|e| e.span.from_expansion()) {
+ return Some((path.ident.name.as_str(), args, span));
+ }
+ }
+ None
+}
+
+/// Same as `method_call` but the `SymbolStr` is dereferenced into a temporary `&str`
+macro_rules! method_call {
+ ($expr:expr) => {
+ method_call($expr)
+ .as_ref()
+ .map(|&(ref name, args, span)| (&**name, args, span))
+ };
+}
+
+impl<'tcx> LateLintPass<'tcx> for Methods {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if in_macro(expr.span) {
+ return;
+ }
+
+ check_methods(cx, expr, self.msrv.as_ref());
+
+ match expr.kind {
+ hir::ExprKind::Call(func, args) => {
+ from_iter_instead_of_collect::check(cx, expr, args, func);
+ },
+ hir::ExprKind::MethodCall(method_call, ref method_span, args, _) => {
+ or_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
+ expect_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
+ clone_on_copy::check(cx, expr, method_call.ident.name, args);
+ clone_on_ref_ptr::check(cx, expr, method_call.ident.name, args);
+ inefficient_to_string::check(cx, expr, method_call.ident.name, args);
+ single_char_add_str::check(cx, expr, args);
+ into_iter_on_ref::check(cx, expr, *method_span, method_call.ident.name, args);
+ single_char_pattern::check(cx, expr, method_call.ident.name, args);
+ },
+ hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
+ let mut info = BinaryExprInfo {
+ expr,
+ chain: lhs,
+ other: rhs,
+ eq: op.node == hir::BinOpKind::Eq,
+ };
+ lint_binary_expr_with_method_call(cx, &mut info);
+ },
+ _ => (),
+ }
+ }
+
+ #[allow(clippy::too_many_lines)]
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ if in_external_macro(cx.sess(), impl_item.span) {
+ return;
+ }
+ let name = impl_item.ident.name.as_str();
+ let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id());
+ let item = cx.tcx.hir().expect_item(parent);
+ let self_ty = cx.tcx.type_of(item.def_id);
+
+ let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }));
+ if_chain! {
+ if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind;
+ if let Some(first_arg) = iter_input_pats(sig.decl, cx.tcx.hir().body(id)).next();
+
+ let method_sig = cx.tcx.fn_sig(impl_item.def_id);
+ let method_sig = cx.tcx.erase_late_bound_regions(method_sig);
+
+ let first_arg_ty = &method_sig.inputs().iter().next();
+
+ // check conventions w.r.t. conversion method names and predicates
+ if let Some(first_arg_ty) = first_arg_ty;
+
+ then {
+ // if this impl block implements a trait, lint in trait definition instead
+ if !implements_trait && cx.access_levels.is_exported(impl_item.hir_id()) {
+ // check missing trait implementations
+ for method_config in &TRAIT_METHODS {
+ if name == method_config.method_name &&
+ sig.decl.inputs.len() == method_config.param_count &&
+ method_config.output_type.matches(&sig.decl.output) &&
+ method_config.self_kind.matches(cx, self_ty, first_arg_ty) &&
+ fn_header_equals(method_config.fn_header, sig.header) &&
+ method_config.lifetime_param_cond(impl_item)
+ {
+ span_lint_and_help(
+ cx,
+ SHOULD_IMPLEMENT_TRAIT,
+ impl_item.span,
+ &format!(
+ "method `{}` can be confused for the standard trait method `{}::{}`",
+ method_config.method_name,
+ method_config.trait_name,
+ method_config.method_name
+ ),
+ None,
+ &format!(
+ "consider implementing the trait `{}` or choosing a less ambiguous method name",
+ method_config.trait_name
+ )
+ );
+ }
+ }
+ }
+
+ wrong_self_convention::check(
+ cx,
+ &name,
+ item.vis.node.is_pub(),
+ self_ty,
+ first_arg_ty,
+ first_arg.pat.span,
+ implements_trait,
+ false
+ );
+ }
+ }
+
+ // if this impl block implements a trait, lint in trait definition instead
+ if implements_trait {
+ return;
+ }
+
+ if let hir::ImplItemKind::Fn(_, _) = impl_item.kind {
+ let ret_ty = return_ty(cx, impl_item.hir_id());
+
+ // walk the return type and check for Self (this does not check associated types)
+ if let Some(self_adt) = self_ty.ty_adt_def() {
+ if contains_adt_constructor(ret_ty, self_adt) {
+ return;
+ }
+ } else if contains_ty(ret_ty, self_ty) {
+ return;
+ }
+
+ // if return type is impl trait, check the associated types
+ if let ty::Opaque(def_id, _) = *ret_ty.kind() {
+ // one of the associated types must be Self
+ for &(predicate, _span) in cx.tcx.explicit_item_bounds(def_id) {
+ if let ty::PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder() {
+ // walk the associated type and check for Self
+ if let Some(self_adt) = self_ty.ty_adt_def() {
+ if contains_adt_constructor(projection_predicate.ty, self_adt) {
+ return;
+ }
+ } else if contains_ty(projection_predicate.ty, self_ty) {
+ return;
+ }
+ }
+ }
+ }
+
+ if name == "new" && !TyS::same_type(ret_ty, self_ty) {
+ span_lint(
+ cx,
+ NEW_RET_NO_SELF,
+ impl_item.span,
+ "methods called `new` usually return `Self`",
+ );
+ }
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if in_external_macro(cx.tcx.sess, item.span) {
+ return;
+ }
+
+ if_chain! {
+ if let TraitItemKind::Fn(ref sig, _) = item.kind;
+ if let Some(first_arg_ty) = sig.decl.inputs.iter().next();
+ then {
+ let first_arg_span = first_arg_ty.span;
+ let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty);
+ let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty();
+ wrong_self_convention::check(
+ cx,
+ &item.ident.name.as_str(),
+ false,
+ self_ty,
+ first_arg_ty,
+ first_arg_span,
+ false,
+ true
+ );
+ }
+ }
+
+ if_chain! {
+ if item.ident.name == sym::new;
+ if let TraitItemKind::Fn(_, _) = item.kind;
+ let ret_ty = return_ty(cx, item.hir_id());
+ let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty();
+ if !contains_ty(ret_ty, self_ty);
+
+ then {
+ span_lint(
+ cx,
+ NEW_RET_NO_SELF,
+ item.span,
+ "methods called `new` usually return `Self`",
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+#[allow(clippy::too_many_lines)]
+fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Option<&RustcVersion>) {
+ if let Some((name, [recv, args @ ..], span)) = method_call!(expr) {
+ match (name, args) {
+ ("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [recv, _]) => {
+ zst_offset::check(cx, expr, recv)
+ },
+ ("and_then", [arg]) => {
+ let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, recv, arg);
+ let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, recv, arg);
+ if !biom_option_linted && !biom_result_linted {
+ unnecessary_lazy_eval::check(cx, expr, recv, arg, "and");
+ }
+ },
+ ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
+ ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
+ ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
+ ("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, msrv),
+ ("collect", []) => match method_call!(recv) {
+ Some(("cloned", [recv2], _)) => iter_cloned_collect::check(cx, expr, recv2),
+ Some(("map", [m_recv, m_arg], _)) => {
+ map_collect_result_unit::check(cx, expr, m_recv, m_arg, recv);
+ },
+ _ => {},
+ },
+ ("count", []) => match method_call!(recv) {
+ Some((name @ ("into_iter" | "iter" | "iter_mut"), [recv2], _)) => {
+ iter_count::check(cx, expr, recv2, name);
+ },
+ Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg),
+ _ => {},
+ },
+ ("expect", [_]) => match method_call!(recv) {
+ Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
+ _ => expect_used::check(cx, expr, recv),
+ },
+ ("extend", [arg]) => string_extend_chars::check(cx, expr, recv, arg),
+ ("filter_map", [arg]) => {
+ unnecessary_filter_map::check(cx, expr, arg);
+ filter_map_identity::check(cx, expr, arg, span);
+ },
+ ("flat_map", [arg]) => {
+ flat_map_identity::check(cx, expr, arg, span);
+ flat_map_option::check(cx, expr, arg, span);
+ },
+ ("flatten", []) => {
+ if let Some(("map", [recv, map_arg], _)) = method_call!(recv) {
+ map_flatten::check(cx, expr, recv, map_arg);
+ }
+ },
+ ("fold", [init, acc]) => unnecessary_fold::check(cx, expr, init, acc, span),
+ ("for_each", [_]) => {
+ if let Some(("inspect", [_, _], span2)) = method_call!(recv) {
+ inspect_for_each::check(cx, expr, span2);
+ }
+ },
+ ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"),
+ ("is_file", []) => filetype_is_file::check(cx, expr, recv),
+ ("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
+ ("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
+ ("map", [m_arg]) => {
+ if let Some((name, [recv2, args @ ..], span2)) = method_call!(recv) {
+ match (name, args) {
+ ("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, msrv),
+ ("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, msrv),
+ ("filter", [f_arg]) => {
+ filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false)
+ },
+ ("find", [f_arg]) => filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, true),
+ _ => {},
+ }
+ }
+ },
+ ("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map),
+ ("next", []) => {
+ if let Some((name, [recv, args @ ..], _)) = method_call!(recv) {
+ match (name, args) {
+ ("filter", [arg]) => filter_next::check(cx, expr, recv, arg),
+ ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv, arg, msrv),
+ ("iter", []) => iter_next_slice::check(cx, expr, recv),
+ ("skip", [arg]) => iter_skip_next::check(cx, expr, recv, arg),
+ ("skip_while", [_]) => skip_while_next::check(cx, expr),
+ _ => {},
+ }
+ }
+ },
+ ("nth", [n_arg]) => match method_call!(recv) {
+ Some(("bytes", [recv2], _)) => bytes_nth::check(cx, expr, recv2, n_arg),
+ 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),
+ },
+ ("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"),
+ ("or_else", [arg]) => {
+ if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, recv, arg) {
+ unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
+ }
+ },
+ ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
+ ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
+ implicit_clone::check(cx, name, expr, recv, span);
+ },
+ ("unwrap", []) => match method_call!(recv) {
+ Some(("get", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, false),
+ Some(("get_mut", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, true),
+ _ => unwrap_used::check(cx, expr, recv),
+ },
+ ("unwrap_or", [u_arg]) => match method_call!(recv) {
+ Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => {
+ manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
+ },
+ Some(("map", [m_recv, m_arg], span)) => {
+ option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span)
+ },
+ _ => {},
+ },
+ ("unwrap_or_else", [u_arg]) => match method_call!(recv) {
+ Some(("map", [recv, map_arg], _)) if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, msrv) => {},
+ _ => unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or"),
+ },
+ _ => {},
+ }
+ }
+}
+
+fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, is_some: bool) {
+ if let Some((name @ ("find" | "position" | "rposition"), [f_recv, arg], span)) = method_call!(recv) {
+ search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span)
+ }
+}
+
+/// Used for `lint_binary_expr_with_method_call`.
+#[derive(Copy, Clone)]
+struct BinaryExprInfo<'a> {
+ expr: &'a hir::Expr<'a>,
+ chain: &'a hir::Expr<'a>,
+ other: &'a hir::Expr<'a>,
+ eq: bool,
+}
+
+/// Checks for the `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
+fn lint_binary_expr_with_method_call(cx: &LateContext<'_>, info: &mut BinaryExprInfo<'_>) {
+ macro_rules! lint_with_both_lhs_and_rhs {
+ ($func:expr, $cx:expr, $info:ident) => {
+ if !$func($cx, $info) {
+ ::std::mem::swap(&mut $info.chain, &mut $info.other);
+ if $func($cx, $info) {
+ return;
+ }
+ }
+ };
+ }
+
+ lint_with_both_lhs_and_rhs!(chars_next_cmp::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_last_cmp::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_next_cmp_with_unwrap::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_last_cmp_with_unwrap::check, cx, info);
+}
+
+const FN_HEADER: hir::FnHeader = hir::FnHeader {
+ unsafety: hir::Unsafety::Normal,
+ constness: hir::Constness::NotConst,
+ asyncness: hir::IsAsync::NotAsync,
+ abi: rustc_target::spec::abi::Abi::Rust,
+};
+
+struct ShouldImplTraitCase {
+ trait_name: &'static str,
+ method_name: &'static str,
+ param_count: usize,
+ fn_header: hir::FnHeader,
+ // implicit self kind expected (none, self, &self, ...)
+ self_kind: SelfKind,
+ // checks against the output type
+ output_type: OutType,
+ // certain methods with explicit lifetimes can't implement the equivalent trait method
+ lint_explicit_lifetime: bool,
+}
+impl ShouldImplTraitCase {
+ const fn new(
+ trait_name: &'static str,
+ method_name: &'static str,
+ param_count: usize,
+ fn_header: hir::FnHeader,
+ self_kind: SelfKind,
+ output_type: OutType,
+ lint_explicit_lifetime: bool,
+ ) -> ShouldImplTraitCase {
+ ShouldImplTraitCase {
+ trait_name,
+ method_name,
+ param_count,
+ fn_header,
+ self_kind,
+ output_type,
+ lint_explicit_lifetime,
+ }
+ }
+
+ fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool {
+ self.lint_explicit_lifetime
+ || !impl_item.generics.params.iter().any(|p| {
+ matches!(
+ p.kind,
+ hir::GenericParamKind::Lifetime {
+ kind: hir::LifetimeParamKind::Explicit
+ }
+ )
+ })
+ }
+}
+
+#[rustfmt::skip]
+const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
+ ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::convert::AsRef", "as_ref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::BitAnd", "bitand", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::BitOr", "bitor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::BitXor", "bitxor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::borrow::Borrow", "borrow", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::borrow::BorrowMut", "borrow_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::clone::Clone", "clone", 1, FN_HEADER, SelfKind::Ref, OutType::Any, true),
+ ShouldImplTraitCase::new("std::cmp::Ord", "cmp", 2, FN_HEADER, SelfKind::Ref, OutType::Any, true),
+ // FIXME: default doesn't work
+ ShouldImplTraitCase::new("std::default::Default", "default", 0, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Deref", "deref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::DerefMut", "deref_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::Div", "div", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Drop", "drop", 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true),
+ ShouldImplTraitCase::new("std::cmp::PartialEq", "eq", 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true),
+ ShouldImplTraitCase::new("std::iter::FromIterator", "from_iter", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::str::FromStr", "from_str", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::hash::Hash", "hash", 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true),
+ ShouldImplTraitCase::new("std::ops::Index", "index", 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::IndexMut", "index_mut", 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::iter::IntoIterator", "into_iter", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Mul", "mul", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Neg", "neg", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::iter::Iterator", "next", 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false),
+ ShouldImplTraitCase::new("std::ops::Not", "not", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Rem", "rem", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Shl", "shl", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Shr", "shr", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+];
+
+#[derive(Clone, Copy, PartialEq, Debug)]
+enum SelfKind {
+ Value,
+ Ref,
+ RefMut,
+ No,
+}
+
+impl SelfKind {
+ fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'_>, ty: Ty<'_>) -> bool {
+ if ty == parent_ty {
+ true
+ } else if ty.is_box() {
+ ty.boxed_ty() == parent_ty
+ } else if is_type_diagnostic_item(cx, ty, sym::Rc) || is_type_diagnostic_item(cx, ty, sym::Arc) {
+ if let ty::Adt(_, substs) = ty.kind() {
+ substs.types().next().map_or(false, |t| t == parent_ty)
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ }
+
+ fn matches_ref<'a>(cx: &LateContext<'a>, mutability: hir::Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ if let ty::Ref(_, t, m) = *ty.kind() {
+ return m == mutability && t == parent_ty;
+ }
+
+ let trait_path = match mutability {
+ hir::Mutability::Not => &paths::ASREF_TRAIT,
+ hir::Mutability::Mut => &paths::ASMUT_TRAIT,
+ };
+
+ let trait_def_id = match get_trait_def_id(cx, trait_path) {
+ Some(did) => did,
+ None => return false,
+ };
+ implements_trait(cx, ty, trait_def_id, &[parent_ty.into()])
+ }
+
+ match self {
+ Self::Value => matches_value(cx, parent_ty, ty),
+ Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty),
+ Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty),
+ Self::No => ty != parent_ty,
+ }
+ }
+
+ #[must_use]
+ fn description(self) -> &'static str {
+ match self {
+ Self::Value => "`self` by value",
+ Self::Ref => "`self` by reference",
+ Self::RefMut => "`self` by mutable reference",
+ Self::No => "no `self`",
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum OutType {
+ Unit,
+ Bool,
+ Any,
+ Ref,
+}
+
+impl OutType {
+ fn matches(self, ty: &hir::FnRetTy<'_>) -> bool {
+ let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[]));
+ match (self, ty) {
+ (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true,
+ (Self::Unit, &hir::FnRetTy::Return(ty)) if is_unit(ty) => true,
+ (Self::Bool, &hir::FnRetTy::Return(ty)) if is_bool(ty) => true,
+ (Self::Any, &hir::FnRetTy::Return(ty)) if !is_unit(ty) => true,
+ (Self::Ref, &hir::FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Rptr(_, _)),
+ _ => false,
+ }
+ }
+}
+
+fn is_bool(ty: &hir::Ty<'_>) -> bool {
+ if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
+ matches!(path.res, Res::PrimTy(PrimTy::Bool))
+ } else {
+ false
+ }
+}
+
+fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool {
+ expected.constness == actual.constness
+ && expected.unsafety == actual.unsafety
+ && expected.asyncness == actual.asyncness
+}
--- /dev/null
- for &(method, pos) in &crate::methods::PATTERN_METHODS {
+use super::utils::get_hint_if_single_char_arg;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::symbol::Symbol;
+
+use super::SINGLE_CHAR_PATTERN;
+
++const PATTERN_METHODS: [(&str, usize); 19] = [
++ ("contains", 1),
++ ("starts_with", 1),
++ ("ends_with", 1),
++ ("find", 1),
++ ("rfind", 1),
++ ("split", 1),
++ ("rsplit", 1),
++ ("split_terminator", 1),
++ ("rsplit_terminator", 1),
++ ("splitn", 2),
++ ("rsplitn", 2),
++ ("matches", 1),
++ ("rmatches", 1),
++ ("match_indices", 1),
++ ("rmatch_indices", 1),
++ ("strip_prefix", 1),
++ ("strip_suffix", 1),
++ ("trim_start_matches", 1),
++ ("trim_end_matches", 1),
++];
++
+/// lint for length-1 `str`s for methods in `PATTERN_METHODS`
+pub(super) fn check(cx: &LateContext<'_>, _expr: &hir::Expr<'_>, method_name: Symbol, args: &[hir::Expr<'_>]) {
++ for &(method, pos) in &PATTERN_METHODS {
+ if_chain! {
+ if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(&args[0]).kind();
+ if *ty.kind() == ty::Str;
+ if method_name.as_str() == method && args.len() > pos;
+ let arg = &args[pos];
+ let mut applicability = Applicability::MachineApplicable;
+ if let Some(hint) = get_hint_if_single_char_arg(cx, arg, &mut applicability);
+ then {
+ span_lint_and_sugg(
+ cx,
+ SINGLE_CHAR_PATTERN,
+ arg.span,
+ "single-character string constant used as pattern",
+ "try using a `char` instead",
+ hint,
+ applicability,
+ );
+ }
+ }
+ }
+}
--- /dev/null
- if !found_filtering {
- span_lint(
- cx,
- UNNECESSARY_FILTER_MAP,
- expr.span,
- "this `.filter_map` can be written more simply using `.map`",
- );
- return;
- }
-
- if !found_mapping && !mutates_arg {
- span_lint(
- cx,
- UNNECESSARY_FILTER_MAP,
- expr.span,
- "this `.filter_map` can be written more simply using `.filter`",
- );
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::usage::mutated_variables;
+use clippy_utils::{is_lang_ctor, is_trait_method, path_to_local_id};
+use rustc_hir as hir;
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
++use rustc_middle::ty::{self, TyS};
+use rustc_span::sym;
+
+use super::UNNECESSARY_FILTER_MAP;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
+ if !is_trait_method(cx, expr, sym::Iterator) {
+ return;
+ }
+
+ if let hir::ExprKind::Closure(_, _, body_id, ..) = arg.kind {
+ let body = cx.tcx.hir().body(body_id);
+ let arg_id = body.params[0].pat.hir_id;
+ let mutates_arg =
+ mutated_variables(&body.value, cx).map_or(true, |used_mutably| used_mutably.contains(&arg_id));
+
+ let (mut found_mapping, mut found_filtering) = check_expression(cx, arg_id, &body.value);
+
+ let mut return_visitor = ReturnVisitor::new(cx, arg_id);
+ return_visitor.visit_expr(&body.value);
+ found_mapping |= return_visitor.found_mapping;
+ found_filtering |= return_visitor.found_filtering;
+
- }
++ let sugg = if !found_filtering {
++ "map"
++ } else if !found_mapping && !mutates_arg {
++ let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
++ match cx.typeck_results().expr_ty(&body.value).kind() {
++ ty::Adt(adt, subst)
++ if cx.tcx.is_diagnostic_item(sym::option_type, adt.did)
++ && TyS::same_type(in_ty, subst.type_at(0)) =>
++ {
++ "filter"
++ },
++ _ => return,
++ }
++ } else {
+ return;
++ };
++ span_lint(
++ cx,
++ UNNECESSARY_FILTER_MAP,
++ expr.span,
++ &format!("this `.filter_map` can be written more simply using `.{}`", sugg),
++ );
+ }
+}
+
+// returns (found_mapping, found_filtering)
+fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tcx hir::Expr<'_>) -> (bool, bool) {
+ match &expr.kind {
+ hir::ExprKind::Call(func, args) => {
+ if let hir::ExprKind::Path(ref path) = func.kind {
+ if is_lang_ctor(cx, path, OptionSome) {
+ if path_to_local_id(&args[0], arg_id) {
+ return (false, false);
+ }
+ return (true, false);
+ }
+ }
+ (true, true)
+ },
+ hir::ExprKind::Block(block, _) => block
+ .expr
+ .as_ref()
+ .map_or((false, false), |expr| check_expression(cx, arg_id, expr)),
+ hir::ExprKind::Match(_, arms, _) => {
+ let mut found_mapping = false;
+ let mut found_filtering = false;
+ for arm in *arms {
+ let (m, f) = check_expression(cx, arg_id, arm.body);
+ found_mapping |= m;
+ found_filtering |= f;
+ }
+ (found_mapping, found_filtering)
+ },
+ // There must be an else_arm or there will be a type error
+ hir::ExprKind::If(_, if_arm, Some(else_arm)) => {
+ let if_check = check_expression(cx, arg_id, if_arm);
+ let else_check = check_expression(cx, arg_id, else_arm);
+ (if_check.0 | else_check.0, if_check.1 | else_check.1)
+ },
+ hir::ExprKind::Path(path) if is_lang_ctor(cx, path, OptionNone) => (false, true),
+ _ => (true, true),
+ }
+}
+
+struct ReturnVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ arg_id: hir::HirId,
+ // Found a non-None return that isn't Some(input)
+ found_mapping: bool,
+ // Found a return that isn't Some
+ found_filtering: bool,
+}
+
+impl<'a, 'tcx> ReturnVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>, arg_id: hir::HirId) -> ReturnVisitor<'a, 'tcx> {
+ ReturnVisitor {
+ cx,
+ arg_id,
+ found_mapping: false,
+ found_filtering: false,
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for ReturnVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+ if let hir::ExprKind::Ret(Some(expr)) = &expr.kind {
+ let (found_mapping, found_filtering) = check_expression(self.cx, self.arg_id, expr);
+ self.found_mapping |= found_mapping;
+ self.found_filtering |= found_filtering;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint;
++use rustc_ast::ast::{GenericParam, GenericParamKind};
++use rustc_hir::PrimTy;
++use rustc_lint::EarlyContext;
++
++use super::BUILTIN_TYPE_SHADOW;
++
++pub(super) fn check(cx: &EarlyContext<'_>, param: &GenericParam) {
++ if let GenericParamKind::Type { .. } = param.kind {
++ if let Some(prim_ty) = PrimTy::from_name(param.ident.name) {
++ span_lint(
++ cx,
++ BUILTIN_TYPE_SHADOW,
++ param.ident.span,
++ &format!("this generic shadows the built-in type `{}`", prim_ty.name()),
++ );
++ }
++ }
++}
--- /dev/null
--- /dev/null
++use super::MiscEarlyLints;
++use clippy_utils::diagnostics::span_lint;
++use rustc_ast::ast::{Expr, ExprKind, UnOp};
++use rustc_lint::EarlyContext;
++
++use super::DOUBLE_NEG;
++
++pub(super) fn check(cx: &EarlyContext<'_>, expr: &Expr) {
++ match expr.kind {
++ ExprKind::Unary(UnOp::Neg, ref inner) => {
++ if let ExprKind::Unary(UnOp::Neg, _) = inner.kind {
++ span_lint(
++ cx,
++ DOUBLE_NEG,
++ expr.span,
++ "`--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op",
++ );
++ }
++ },
++ ExprKind::Lit(ref lit) => MiscEarlyLints::check_lit(cx, lit),
++ _ => (),
++ }
++}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint;
++use rustc_ast::ast::Lit;
++use rustc_lint::EarlyContext;
++
++use super::MIXED_CASE_HEX_LITERALS;
++
++pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, suffix: &str, lit_snip: &str) {
++ let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) {
++ val
++ } else {
++ return; // It's useless so shouldn't lint.
++ };
++ if maybe_last_sep_idx <= 2 {
++ // It's meaningless or causes range error.
++ return;
++ }
++ let mut seen = (false, false);
++ for ch in lit_snip.as_bytes()[2..=maybe_last_sep_idx].iter() {
++ match ch {
++ b'a'..=b'f' => seen.0 = true,
++ b'A'..=b'F' => seen.1 = true,
++ _ => {},
++ }
++ if seen.0 && seen.1 {
++ span_lint(
++ cx,
++ MIXED_CASE_HEX_LITERALS,
++ lit.span,
++ "inconsistent casing in hexadecimal literal",
++ );
++ break;
++ }
++ }
++}
--- /dev/null
--- /dev/null
++mod builtin_type_shadow;
++mod double_neg;
++mod mixed_case_hex_literals;
++mod redundant_pattern;
++mod unneeded_field_pattern;
++mod unneeded_wildcard_pattern;
++mod unseparated_literal_suffix;
++mod zero_prefixed_literal;
++
++use clippy_utils::diagnostics::span_lint;
++use clippy_utils::source::snippet_opt;
++use rustc_ast::ast::{Expr, Generics, Lit, LitFloatType, LitIntType, LitKind, NodeId, Pat, PatKind};
++use rustc_ast::visit::FnKind;
++use rustc_data_structures::fx::FxHashMap;
++use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
++use rustc_middle::lint::in_external_macro;
++use rustc_session::{declare_lint_pass, declare_tool_lint};
++use rustc_span::source_map::Span;
++
++declare_clippy_lint! {
++ /// **What it does:** Checks for structure field patterns bound to wildcards.
++ ///
++ /// **Why is this bad?** Using `..` instead is shorter and leaves the focus on
++ /// the fields that are actually bound.
++ ///
++ /// **Known problems:** None.
++ ///
++ /// **Example:**
++ /// ```rust
++ /// # struct Foo {
++ /// # a: i32,
++ /// # b: i32,
++ /// # c: i32,
++ /// # }
++ /// let f = Foo { a: 0, b: 0, c: 0 };
++ ///
++ /// // Bad
++ /// match f {
++ /// Foo { a: _, b: 0, .. } => {},
++ /// Foo { a: _, b: _, c: _ } => {},
++ /// }
++ ///
++ /// // Good
++ /// match f {
++ /// Foo { b: 0, .. } => {},
++ /// Foo { .. } => {},
++ /// }
++ /// ```
++ pub UNNEEDED_FIELD_PATTERN,
++ restriction,
++ "struct fields bound to a wildcard instead of using `..`"
++}
++
++declare_clippy_lint! {
++ /// **What it does:** Checks for function arguments having the similar names
++ /// differing by an underscore.
++ ///
++ /// **Why is this bad?** It affects code readability.
++ ///
++ /// **Known problems:** None.
++ ///
++ /// **Example:**
++ /// ```rust
++ /// // Bad
++ /// fn foo(a: i32, _a: i32) {}
++ ///
++ /// // Good
++ /// fn bar(a: i32, _b: i32) {}
++ /// ```
++ pub DUPLICATE_UNDERSCORE_ARGUMENT,
++ style,
++ "function arguments having names which only differ by an underscore"
++}
++
++declare_clippy_lint! {
++ /// **What it does:** Detects expressions of the form `--x`.
++ ///
++ /// **Why is this bad?** It can mislead C/C++ programmers to think `x` was
++ /// decremented.
++ ///
++ /// **Known problems:** None.
++ ///
++ /// **Example:**
++ /// ```rust
++ /// let mut x = 3;
++ /// --x;
++ /// ```
++ pub DOUBLE_NEG,
++ style,
++ "`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++"
++}
++
++declare_clippy_lint! {
++ /// **What it does:** Warns on hexadecimal literals with mixed-case letter
++ /// digits.
++ ///
++ /// **Why is this bad?** It looks confusing.
++ ///
++ /// **Known problems:** None.
++ ///
++ /// **Example:**
++ /// ```rust
++ /// // Bad
++ /// let y = 0x1a9BAcD;
++ ///
++ /// // Good
++ /// let y = 0x1A9BACD;
++ /// ```
++ pub MIXED_CASE_HEX_LITERALS,
++ style,
++ "hex literals whose letter digits are not consistently upper- or lowercased"
++}
++
++declare_clippy_lint! {
++ /// **What it does:** Warns if literal suffixes are not separated by an
++ /// underscore.
++ ///
++ /// **Why is this bad?** It is much less readable.
++ ///
++ /// **Known problems:** None.
++ ///
++ /// **Example:**
++ /// ```rust
++ /// // Bad
++ /// let y = 123832i32;
++ ///
++ /// // Good
++ /// let y = 123832_i32;
++ /// ```
++ pub UNSEPARATED_LITERAL_SUFFIX,
++ pedantic,
++ "literals whose suffix is not separated by an underscore"
++}
++
++declare_clippy_lint! {
++ /// **What it does:** Warns if an integral constant literal starts with `0`.
++ ///
++ /// **Why is this bad?** In some languages (including the infamous C language
++ /// and most of its
++ /// family), this marks an octal constant. In Rust however, this is a decimal
++ /// constant. This could
++ /// be confusing for both the writer and a reader of the constant.
++ ///
++ /// **Known problems:** None.
++ ///
++ /// **Example:**
++ ///
++ /// In Rust:
++ /// ```rust
++ /// fn main() {
++ /// let a = 0123;
++ /// println!("{}", a);
++ /// }
++ /// ```
++ ///
++ /// prints `123`, while in C:
++ ///
++ /// ```c
++ /// #include <stdio.h>
++ ///
++ /// int main() {
++ /// int a = 0123;
++ /// printf("%d\n", a);
++ /// }
++ /// ```
++ ///
++ /// prints `83` (as `83 == 0o123` while `123 == 0o173`).
++ pub ZERO_PREFIXED_LITERAL,
++ complexity,
++ "integer literals starting with `0`"
++}
++
++declare_clippy_lint! {
++ /// **What it does:** Warns if a generic shadows a built-in type.
++ ///
++ /// **Why is this bad?** This gives surprising type errors.
++ ///
++ /// **Known problems:** None.
++ ///
++ /// **Example:**
++ ///
++ /// ```ignore
++ /// impl<u32> Foo<u32> {
++ /// fn impl_func(&self) -> u32 {
++ /// 42
++ /// }
++ /// }
++ /// ```
++ pub BUILTIN_TYPE_SHADOW,
++ style,
++ "shadowing a builtin type"
++}
++
++declare_clippy_lint! {
++ /// **What it does:** Checks for patterns in the form `name @ _`.
++ ///
++ /// **Why is this bad?** It's almost always more readable to just use direct
++ /// bindings.
++ ///
++ /// **Known problems:** None.
++ ///
++ /// **Example:**
++ /// ```rust
++ /// # let v = Some("abc");
++ ///
++ /// // Bad
++ /// match v {
++ /// Some(x) => (),
++ /// y @ _ => (),
++ /// }
++ ///
++ /// // Good
++ /// match v {
++ /// Some(x) => (),
++ /// y => (),
++ /// }
++ /// ```
++ pub REDUNDANT_PATTERN,
++ style,
++ "using `name @ _` in a pattern"
++}
++
++declare_clippy_lint! {
++ /// **What it does:** Checks for tuple patterns with a wildcard
++ /// pattern (`_`) is next to a rest pattern (`..`).
++ ///
++ /// _NOTE_: While `_, ..` means there is at least one element left, `..`
++ /// 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.
++ ///
++ /// **Known problems:** None.
++ ///
++ /// **Example:**
++ /// ```rust
++ /// # struct TupleStruct(u32, u32, u32);
++ /// # let t = TupleStruct(1, 2, 3);
++ /// // Bad
++ /// match t {
++ /// TupleStruct(0, .., _) => (),
++ /// _ => (),
++ /// }
++ ///
++ /// // Good
++ /// match t {
++ /// TupleStruct(0, ..) => (),
++ /// _ => (),
++ /// }
++ /// ```
++ pub UNNEEDED_WILDCARD_PATTERN,
++ complexity,
++ "tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)"
++}
++
++declare_lint_pass!(MiscEarlyLints => [
++ UNNEEDED_FIELD_PATTERN,
++ DUPLICATE_UNDERSCORE_ARGUMENT,
++ DOUBLE_NEG,
++ MIXED_CASE_HEX_LITERALS,
++ UNSEPARATED_LITERAL_SUFFIX,
++ ZERO_PREFIXED_LITERAL,
++ BUILTIN_TYPE_SHADOW,
++ REDUNDANT_PATTERN,
++ UNNEEDED_WILDCARD_PATTERN,
++]);
++
++impl EarlyLintPass for MiscEarlyLints {
++ fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) {
++ for param in &gen.params {
++ builtin_type_shadow::check(cx, param);
++ }
++ }
++
++ fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
++ unneeded_field_pattern::check(cx, pat);
++ redundant_pattern::check(cx, pat);
++ unneeded_wildcard_pattern::check(cx, pat);
++ }
++
++ fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
++ let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();
++
++ for arg in &fn_kind.decl().inputs {
++ if let PatKind::Ident(_, ident, None) = arg.pat.kind {
++ let arg_name = ident.to_string();
++
++ if let Some(arg_name) = arg_name.strip_prefix('_') {
++ if let Some(correspondence) = registered_names.get(arg_name) {
++ span_lint(
++ cx,
++ DUPLICATE_UNDERSCORE_ARGUMENT,
++ *correspondence,
++ &format!(
++ "`{}` already exists, having another argument having almost the same \
++ name makes code comprehension and documentation more difficult",
++ arg_name
++ ),
++ );
++ }
++ } else {
++ registered_names.insert(arg_name, arg.pat.span);
++ }
++ }
++ }
++ }
++
++ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
++ if in_external_macro(cx.sess(), expr.span) {
++ return;
++ }
++ double_neg::check(cx, expr)
++ }
++}
++
++impl MiscEarlyLints {
++ fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) {
++ // We test if first character in snippet is a number, because the snippet could be an expansion
++ // from a built-in macro like `line!()` or a proc-macro like `#[wasm_bindgen]`.
++ // Note that this check also covers special case that `line!()` is eagerly expanded by compiler.
++ // See <https://github.com/rust-lang/rust-clippy/issues/4507> for a regression.
++ // FIXME: Find a better way to detect those cases.
++ let lit_snip = match snippet_opt(cx, lit.span) {
++ Some(snip) if snip.chars().next().map_or(false, |c| c.is_digit(10)) => snip,
++ _ => return,
++ };
++
++ if let LitKind::Int(value, lit_int_type) = lit.kind {
++ let suffix = match lit_int_type {
++ LitIntType::Signed(ty) => ty.name_str(),
++ LitIntType::Unsigned(ty) => ty.name_str(),
++ LitIntType::Unsuffixed => "",
++ };
++ unseparated_literal_suffix::check(cx, lit, &lit_snip, suffix, "integer");
++ if lit_snip.starts_with("0x") {
++ mixed_case_hex_literals::check(cx, lit, suffix, &lit_snip)
++ } else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") {
++ /* nothing to do */
++ } else if value != 0 && lit_snip.starts_with('0') {
++ zero_prefixed_literal::check(cx, lit, &lit_snip)
++ }
++ } else if let LitKind::Float(_, LitFloatType::Suffixed(float_ty)) = lit.kind {
++ let suffix = float_ty.name_str();
++ unseparated_literal_suffix::check(cx, lit, &lit_snip, suffix, "float")
++ }
++ }
++}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_sugg;
++use rustc_ast::ast::{BindingMode, Mutability, Pat, PatKind};
++use rustc_errors::Applicability;
++use rustc_lint::EarlyContext;
++
++use super::REDUNDANT_PATTERN;
++
++pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) {
++ if let PatKind::Ident(left, ident, Some(ref right)) = pat.kind {
++ let left_binding = match left {
++ BindingMode::ByRef(Mutability::Mut) => "ref mut ",
++ BindingMode::ByRef(Mutability::Not) => "ref ",
++ BindingMode::ByValue(..) => "",
++ };
++
++ if let PatKind::Wild = right.kind {
++ span_lint_and_sugg(
++ cx,
++ REDUNDANT_PATTERN,
++ pat.span,
++ &format!(
++ "the `{} @ _` pattern can be written as just `{}`",
++ ident.name, ident.name,
++ ),
++ "try",
++ format!("{}{}", left_binding, ident.name),
++ Applicability::MachineApplicable,
++ );
++ }
++ }
++}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
++use rustc_ast::ast::{Pat, PatKind};
++use rustc_lint::{EarlyContext, LintContext};
++
++use super::UNNEEDED_FIELD_PATTERN;
++
++pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) {
++ if let PatKind::Struct(ref npat, ref pfields, _) = pat.kind {
++ let mut wilds = 0;
++ let type_name = npat
++ .segments
++ .last()
++ .expect("A path must have at least one segment")
++ .ident
++ .name;
++
++ for field in pfields {
++ if let PatKind::Wild = field.pat.kind {
++ wilds += 1;
++ }
++ }
++ if !pfields.is_empty() && wilds == pfields.len() {
++ span_lint_and_help(
++ cx,
++ UNNEEDED_FIELD_PATTERN,
++ pat.span,
++ "all the struct fields are matched to a wildcard pattern, consider using `..`",
++ None,
++ &format!("try with `{} {{ .. }}` instead", type_name),
++ );
++ return;
++ }
++ if wilds > 0 {
++ for field in pfields {
++ if let PatKind::Wild = field.pat.kind {
++ wilds -= 1;
++ if wilds > 0 {
++ span_lint(
++ cx,
++ UNNEEDED_FIELD_PATTERN,
++ field.span,
++ "you matched a field with a wildcard pattern, consider using `..` instead",
++ );
++ } else {
++ let mut normal = vec![];
++
++ for field in pfields {
++ match field.pat.kind {
++ PatKind::Wild => {},
++ _ => {
++ if let Ok(n) = cx.sess().source_map().span_to_snippet(field.span) {
++ normal.push(n);
++ }
++ },
++ }
++ }
++
++ span_lint_and_help(
++ cx,
++ UNNEEDED_FIELD_PATTERN,
++ field.span,
++ "you matched a field with a wildcard pattern, consider using `..` \
++ instead",
++ None,
++ &format!("try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")),
++ );
++ }
++ }
++ }
++ }
++ }
++}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_sugg;
++use rustc_ast::ast::{Pat, PatKind};
++use rustc_errors::Applicability;
++use rustc_lint::EarlyContext;
++use rustc_span::source_map::Span;
++
++use super::UNNEEDED_WILDCARD_PATTERN;
++
++pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) {
++ if let PatKind::TupleStruct(_, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind {
++ if let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) {
++ if let Some((left_index, left_pat)) = patterns[..rest_index]
++ .iter()
++ .rev()
++ .take_while(|pat| matches!(pat.kind, PatKind::Wild))
++ .enumerate()
++ .last()
++ {
++ span_lint(cx, left_pat.span.until(patterns[rest_index].span), left_index == 0);
++ }
++
++ if let Some((right_index, right_pat)) = patterns[rest_index + 1..]
++ .iter()
++ .take_while(|pat| matches!(pat.kind, PatKind::Wild))
++ .enumerate()
++ .last()
++ {
++ span_lint(
++ cx,
++ patterns[rest_index].span.shrink_to_hi().to(right_pat.span),
++ right_index == 0,
++ );
++ }
++ }
++ }
++}
++
++fn span_lint(cx: &EarlyContext<'_>, span: Span, only_one: bool) {
++ span_lint_and_sugg(
++ cx,
++ UNNEEDED_WILDCARD_PATTERN,
++ span,
++ if only_one {
++ "this pattern is unneeded as the `..` pattern can match that element"
++ } else {
++ "these patterns are unneeded as the `..` pattern can match those elements"
++ },
++ if only_one { "remove it" } else { "remove them" },
++ "".to_string(),
++ Applicability::MachineApplicable,
++ );
++}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_sugg;
++use rustc_ast::ast::Lit;
++use rustc_errors::Applicability;
++use rustc_lint::EarlyContext;
++
++use super::UNSEPARATED_LITERAL_SUFFIX;
++
++pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str, suffix: &str, sugg_type: &str) {
++ let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) {
++ val
++ } else {
++ return; // It's useless so shouldn't lint.
++ };
++ // Do not lint when literal is unsuffixed.
++ if !suffix.is_empty() && lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' {
++ span_lint_and_sugg(
++ cx,
++ UNSEPARATED_LITERAL_SUFFIX,
++ lit.span,
++ &format!("{} type suffix should be separated by an underscore", sugg_type),
++ "add an underscore",
++ format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix),
++ Applicability::MachineApplicable,
++ );
++ }
++}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_then;
++use rustc_ast::ast::Lit;
++use rustc_errors::Applicability;
++use rustc_lint::EarlyContext;
++
++use super::ZERO_PREFIXED_LITERAL;
++
++pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str) {
++ span_lint_and_then(
++ cx,
++ ZERO_PREFIXED_LITERAL,
++ lit.span,
++ "this is a decimal constant",
++ |diag| {
++ diag.span_suggestion(
++ lit.span,
++ "if you mean to use a decimal constant, remove the `0` to avoid confusion",
++ lit_snip.trim_start_matches(|c| c == '_' || c == '0').to_string(),
++ Applicability::MaybeIncorrect,
++ );
++ diag.span_suggestion(
++ lit.span,
++ "if you mean to use an octal constant, use `0o`",
++ format!("0o{}", lit_snip.trim_start_matches(|c| c == '_' || c == '0')),
++ Applicability::MaybeIncorrect,
++ );
++ },
++ );
++}
--- /dev/null
- use rustc_lint::{LateContext, LateLintPass, Lint};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{get_enclosing_block, is_expr_path_def_path, path_to_local, path_to_local_id, paths, SpanlessEq};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, NestedVisitorMap, Visitor};
+use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, QPath, Stmt, StmtKind};
- InitializationType::Extend(e) | InitializationType::Resize(e) => Self::emit_lint(
- cx,
- e,
- vec_alloc,
- "slow zero-filling initialization",
- SLOW_VECTOR_INITIALIZATION,
- ),
++use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks slow zero-filled vector initialization
+ ///
+ /// **Why is this bad?** These structures are non-idiomatic and less efficient than simply using
+ /// `vec![0; len]`.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```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
+ /// let mut vec1 = vec![0; len];
+ /// let mut vec2 = vec![0; len];
+ /// ```
+ pub SLOW_VECTOR_INITIALIZATION,
+ perf,
+ "slow vector initialization"
+}
+
+declare_lint_pass!(SlowVectorInit => [SLOW_VECTOR_INITIALIZATION]);
+
+/// `VecAllocation` contains data regarding a vector allocated with `with_capacity` and then
+/// assigned to a variable. For example, `let mut vec = Vec::with_capacity(0)` or
+/// `vec = Vec::with_capacity(0)`
+struct VecAllocation<'tcx> {
+ /// HirId of the variable
+ local_id: HirId,
+
+ /// Reference to the expression which allocates the vector
+ allocation_expr: &'tcx Expr<'tcx>,
+
+ /// Reference to the expression used as argument on `with_capacity` call. This is used
+ /// to only match slow zero-filling idioms of the same length than vector initialization.
+ len_expr: &'tcx Expr<'tcx>,
+}
+
+/// Type of slow initialization
+enum InitializationType<'tcx> {
+ /// Extend is a slow initialization with the form `vec.extend(repeat(0).take(..))`
+ Extend(&'tcx Expr<'tcx>),
+
+ /// Resize is a slow initialization with the form `vec.resize(.., 0)`
+ Resize(&'tcx Expr<'tcx>),
+}
+
+impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // Matches initialization on reassignements. For example: `vec = Vec::with_capacity(100)`
+ if_chain! {
+ if let ExprKind::Assign(left, right, _) = expr.kind;
+
+ // Extract variable
+ if let Some(local_id) = path_to_local(left);
+
+ // Extract len argument
+ if let Some(len_arg) = Self::is_vec_with_capacity(cx, right);
+
+ then {
+ let vi = VecAllocation {
+ local_id,
+ allocation_expr: right,
+ len_expr: len_arg,
+ };
+
+ Self::search_initialization(cx, vi, expr.hir_id);
+ }
+ }
+ }
+
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ // Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)`
+ if_chain! {
+ if let StmtKind::Local(local) = stmt.kind;
+ if let PatKind::Binding(BindingAnnotation::Mutable, local_id, _, None) = local.pat.kind;
+ if let Some(init) = local.init;
+ if let Some(len_arg) = Self::is_vec_with_capacity(cx, init);
+
+ then {
+ let vi = VecAllocation {
+ local_id,
+ allocation_expr: init,
+ len_expr: len_arg,
+ };
+
+ Self::search_initialization(cx, vi, stmt.hir_id);
+ }
+ }
+ }
+}
+
+impl SlowVectorInit {
+ /// Checks if the given expression is `Vec::with_capacity(..)`. It will return the expression
+ /// of the first argument of `with_capacity` call if it matches or `None` if it does not.
+ fn is_vec_with_capacity<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ if_chain! {
+ if let ExprKind::Call(func, [arg]) = expr.kind;
+ if let ExprKind::Path(QPath::TypeRelative(ty, name)) = func.kind;
+ if name.ident.as_str() == "with_capacity";
+ if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::vec_type);
+ then {
+ Some(arg)
+ } else {
+ None
+ }
+ }
+ }
+
+ /// Search initialization for the given vector
+ fn search_initialization<'tcx>(cx: &LateContext<'tcx>, vec_alloc: VecAllocation<'tcx>, parent_node: HirId) {
+ let enclosing_body = get_enclosing_block(cx, parent_node);
+
+ if enclosing_body.is_none() {
+ return;
+ }
+
+ let mut v = VectorInitializationVisitor {
+ cx,
+ vec_alloc,
+ slow_expression: None,
+ initialization_found: false,
+ };
+
+ v.visit_block(enclosing_body.unwrap());
+
+ if let Some(ref allocation_expr) = v.slow_expression {
+ Self::lint_initialization(cx, allocation_expr, &v.vec_alloc);
+ }
+ }
+
+ fn lint_initialization<'tcx>(
+ cx: &LateContext<'tcx>,
+ initialization: &InitializationType<'tcx>,
+ vec_alloc: &VecAllocation<'_>,
+ ) {
+ match initialization {
- fn emit_lint<'tcx>(
- cx: &LateContext<'tcx>,
- slow_fill: &Expr<'_>,
- vec_alloc: &VecAllocation<'_>,
- msg: &str,
- lint: &'static Lint,
- ) {
++ InitializationType::Extend(e) | InitializationType::Resize(e) => {
++ Self::emit_lint(cx, e, vec_alloc, "slow zero-filling initialization")
++ },
+ };
+ }
+
- span_lint_and_then(cx, lint, slow_fill.span, msg, |diag| {
++ fn emit_lint<'tcx>(cx: &LateContext<'tcx>, slow_fill: &Expr<'_>, vec_alloc: &VecAllocation<'_>, msg: &str) {
+ let len_expr = Sugg::hir(cx, vec_alloc.len_expr, "len");
+
++ span_lint_and_then(cx, SLOW_VECTOR_INITIALIZATION, slow_fill.span, msg, |diag| {
+ diag.span_suggestion(
+ vec_alloc.allocation_expr.span,
+ "consider replace allocation with",
+ format!("vec![0; {}]", len_expr),
+ Applicability::Unspecified,
+ );
+ });
+ }
+}
+
+/// `VectorInitializationVisitor` searches for unsafe or slow vector initializations for the given
+/// vector.
+struct VectorInitializationVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+
+ /// Contains the information.
+ vec_alloc: VecAllocation<'tcx>,
+
+ /// Contains the slow initialization expression, if one was found.
+ slow_expression: Option<InitializationType<'tcx>>,
+
+ /// `true` if the initialization of the vector has been found on the visited block.
+ initialization_found: bool,
+}
+
+impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
+ /// Checks if the given expression is extending a vector with `repeat(0).take(..)`
+ fn search_slow_extend_filling(&mut self, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if self.initialization_found;
+ if let ExprKind::MethodCall(path, _, [self_arg, extend_arg], _) = expr.kind;
+ if path_to_local_id(self_arg, self.vec_alloc.local_id);
+ if path.ident.name == sym!(extend);
+ if self.is_repeat_take(extend_arg);
+
+ then {
+ self.slow_expression = Some(InitializationType::Extend(expr));
+ }
+ }
+ }
+
+ /// Checks if the given expression is resizing a vector with 0
+ fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if self.initialization_found;
+ if let ExprKind::MethodCall(path, _, [self_arg, len_arg, fill_arg], _) = expr.kind;
+ if path_to_local_id(self_arg, self.vec_alloc.local_id);
+ if path.ident.name == sym!(resize);
+
+ // Check that is filled with 0
+ if let ExprKind::Lit(ref lit) = fill_arg.kind;
+ if let LitKind::Int(0, _) = lit.node;
+
+ // Check that len expression is equals to `with_capacity` expression
+ if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr);
+
+ then {
+ self.slow_expression = Some(InitializationType::Resize(expr));
+ }
+ }
+ }
+
+ /// Returns `true` if give expression is `repeat(0).take(...)`
+ fn is_repeat_take(&self, expr: &Expr<'_>) -> bool {
+ if_chain! {
+ if let ExprKind::MethodCall(take_path, _, take_args, _) = expr.kind;
+ if take_path.ident.name == sym!(take);
+
+ // Check that take is applied to `repeat(0)`
+ if let Some(repeat_expr) = take_args.get(0);
+ if self.is_repeat_zero(repeat_expr);
+
+ // Check that len expression is equals to `with_capacity` expression
+ if let Some(len_arg) = take_args.get(1);
+ if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr);
+
+ then {
+ return true;
+ }
+ }
+
+ false
+ }
+
+ /// Returns `true` if given expression is `repeat(0)`
+ fn is_repeat_zero(&self, expr: &Expr<'_>) -> bool {
+ if_chain! {
+ if let ExprKind::Call(fn_expr, [repeat_arg]) = expr.kind;
+ if is_expr_path_def_path(self.cx, fn_expr, &paths::ITER_REPEAT);
+ if let ExprKind::Lit(ref lit) = repeat_arg.kind;
+ if let LitKind::Int(0, _) = lit.node;
+
+ then {
+ true
+ } else {
+ false
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for VectorInitializationVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
+ if self.initialization_found {
+ match stmt.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
+ self.search_slow_extend_filling(expr);
+ self.search_slow_resize_filling(expr);
+ },
+ _ => (),
+ }
+
+ self.initialization_found = false;
+ } else {
+ walk_stmt(self, stmt);
+ }
+ }
+
+ fn visit_block(&mut self, block: &'tcx Block<'_>) {
+ if self.initialization_found {
+ if let Some(s) = block.stmts.get(0) {
+ self.visit_stmt(s)
+ }
+
+ self.initialization_found = false;
+ } else {
+ walk_block(self, block);
+ }
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ // Skip all the expressions previous to the vector initialization
+ if self.vec_alloc.allocation_expr.hir_id == expr.hir_id {
+ self.initialization_found = true;
+ }
+
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
--- /dev/null
- if is_unit_expr(expr) && !stmt.span.from_expansion();
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::position_before_rarrow;
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_ast::visit::FnKind;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::BytePos;
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for unit (`()`) expressions that can be removed.
+ ///
+ /// **Why is this bad?** Such expressions add no value, but can make the code
+ /// less readable. Depending on formatting they can make a `break` or `return`
+ /// statement look like a function call.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// ```rust
+ /// fn return_unit() -> () {
+ /// ()
+ /// }
+ /// ```
+ pub UNUSED_UNIT,
+ style,
+ "needless unit expression"
+}
+
+declare_lint_pass!(UnusedUnit => [UNUSED_UNIT]);
+
+impl EarlyLintPass for UnusedUnit {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) {
+ if_chain! {
+ if let ast::FnRetTy::Ty(ref ty) = kind.decl().output;
+ if let ast::TyKind::Tup(ref vals) = ty.kind;
+ if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span);
+ then {
+ lint_unneeded_unit_return(cx, ty, span);
+ }
+ }
+ }
+
+ fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
+ if_chain! {
+ if let Some(stmt) = block.stmts.last();
+ if let ast::StmtKind::Expr(ref expr) = stmt.kind;
++ if is_unit_expr(expr);
++ let ctxt = block.span.ctxt();
++ if stmt.span.ctxt() == ctxt && expr.span.ctxt() == ctxt;
+ then {
+ let sp = expr.span;
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ sp,
+ "unneeded unit expression",
+ "remove the final `()`",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+ match e.kind {
+ ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => {
+ if is_unit_expr(expr) && !expr.span.from_expansion() {
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ expr.span,
+ "unneeded `()`",
+ "remove the `()`",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ _ => (),
+ }
+ }
+
+ fn check_poly_trait_ref(&mut self, cx: &EarlyContext<'_>, poly: &ast::PolyTraitRef, _: &ast::TraitBoundModifier) {
+ let segments = &poly.trait_ref.path.segments;
+
+ if_chain! {
+ if segments.len() == 1;
+ if ["Fn", "FnMut", "FnOnce"].contains(&&*segments[0].ident.name.as_str());
+ if let Some(args) = &segments[0].args;
+ if let ast::GenericArgs::Parenthesized(generic_args) = &**args;
+ if let ast::FnRetTy::Ty(ty) = &generic_args.output;
+ if ty.kind.is_unit();
+ then {
+ lint_unneeded_unit_return(cx, ty, generic_args.span);
+ }
+ }
+ }
+}
+
+// get the def site
+#[must_use]
+fn get_def(span: Span) -> Option<Span> {
+ if span.from_expansion() {
+ Some(span.ctxt().outer_expn_data().def_site)
+ } else {
+ None
+ }
+}
+
+// is this expr a `()` unit?
+fn is_unit_expr(expr: &ast::Expr) -> bool {
+ if let ast::ExprKind::Tup(ref vals) = expr.kind {
+ vals.is_empty()
+ } else {
+ false
+ }
+}
+
+fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) {
+ let (ret_span, appl) = if let Ok(fn_source) = cx.sess().source_map().span_to_snippet(span.with_hi(ty.span.hi())) {
+ position_before_rarrow(&fn_source).map_or((ty.span, Applicability::MaybeIncorrect), |rpos| {
+ (
+ #[allow(clippy::cast_possible_truncation)]
+ ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
+ Applicability::MachineApplicable,
+ )
+ })
+ } else {
+ (ty.span, Applicability::MaybeIncorrect)
+ };
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ ret_span,
+ "unneeded unit return type",
+ "remove the `-> ()`",
+ String::new(),
+ appl,
+ );
+}
--- /dev/null
- #![deny(clippy::missing_docs_in_private_items)]
+//! Read configurations files.
+
- use rustc_ast::ast::{LitKind, MetaItemKind, NestedMetaItem};
- use rustc_span::source_map;
- use source_map::Span;
- use std::lazy::SyncLazy;
++#![allow(clippy::module_name_repetitions)]
+
- use std::sync::Mutex;
++use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor};
++use serde::Deserialize;
++use std::error::Error;
+use std::path::{Path, PathBuf};
- /// Gets the configuration file from arguments.
- pub fn file_from_args(args: &[NestedMetaItem]) -> Result<Option<PathBuf>, (&'static str, Span)> {
- for arg in args.iter().filter_map(NestedMetaItem::meta_item) {
- if arg.has_name(sym!(conf_file)) {
- return match arg.kind {
- MetaItemKind::Word | MetaItemKind::List(_) => Err(("`conf_file` must be a named value", arg.span)),
- MetaItemKind::NameValue(ref value) => {
- if let LitKind::Str(ref file, _) = value.kind {
- Ok(Some(file.to_string().into()))
- } else {
- Err(("`conf_file` value must be a string", value.span))
- }
- },
- };
- }
- }
-
- Ok(None)
- }
-
- /// Error from reading a configuration file.
- #[derive(Debug)]
- pub enum Error {
- /// An I/O error.
- Io(io::Error),
- /// Not valid toml or doesn't fit the expected config format
- Toml(String),
+use std::{env, fmt, fs, io};
+
- impl fmt::Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::Io(err) => err.fmt(f),
- Self::Toml(err) => err.fmt(f),
++/// Conf with parse errors
++#[derive(Default)]
++pub struct TryConf {
++ pub conf: Conf,
++ pub errors: Vec<String>,
+}
+
- impl From<io::Error> for Error {
- fn from(e: io::Error) -> Self {
- Self::Io(e)
- }
- }
++impl TryConf {
++ fn from_error(error: impl Error) -> Self {
++ Self {
++ conf: Conf::default(),
++ errors: vec![error.to_string()],
+ }
+ }
+}
+
- /// Vec of errors that might be collected during config toml parsing
- static ERRORS: SyncLazy<Mutex<Vec<Error>>> = SyncLazy::new(|| Mutex::new(Vec::new()));
++macro_rules! define_Conf {
++ ($(
++ #[$doc:meta]
++ $(#[conf_deprecated($dep:literal)])?
++ ($name:ident: $ty:ty = $default:expr),
++ )*) => {
++ /// Clippy lint configuration
++ pub struct Conf {
++ $(#[$doc] pub $name: $ty,)*
++ }
+
- macro_rules! define_Conf {
- ($(#[$doc:meta] ($config:ident, $config_str:literal: $Ty:ty, $default:expr),)+) => {
- mod helpers {
- use serde::Deserialize;
- /// Type used to store lint configuration.
- #[derive(Deserialize)]
- #[serde(rename_all = "kebab-case", deny_unknown_fields)]
- pub struct Conf {
- $(
- #[$doc]
- #[serde(default = $config_str)]
- #[serde(with = $config_str)]
- pub $config: $Ty,
- )+
- #[allow(dead_code)]
- #[serde(default)]
- third_party: Option<::toml::Value>,
++ mod defaults {
++ $(pub fn $name() -> $ty { $default })*
++ }
+
- $(
- mod $config {
- use serde::Deserialize;
- pub fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<$Ty, D::Error> {
- use super::super::{ERRORS, Error};
++ impl Default for Conf {
++ fn default() -> Self {
++ Self { $($name: defaults::$name(),)* }
+ }
++ }
+
- Ok(
- <$Ty>::deserialize(deserializer).unwrap_or_else(|e| {
- ERRORS
- .lock()
- .expect("no threading here")
- .push(Error::Toml(e.to_string()));
- super::$config()
- })
- )
- }
- }
++ impl<'de> Deserialize<'de> for TryConf {
++ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
++ deserializer.deserialize_map(ConfVisitor)
++ }
++ }
+
- #[must_use]
- fn $config() -> $Ty {
- let x = $default;
- x
++ #[derive(Deserialize)]
++ #[serde(field_identifier, rename_all = "kebab-case")]
++ #[allow(non_camel_case_types)]
++ enum Field { $($name,)* third_party, }
++
++ struct ConfVisitor;
++
++ impl<'de> Visitor<'de> for ConfVisitor {
++ type Value = TryConf;
+
- )+
++ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
++ formatter.write_str("Conf")
++ }
++
++ fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
++ let mut errors = Vec::new();
++ $(let mut $name = None;)*
++ // could get `Field` here directly, but get `str` first for diagnostics
++ while let Some(name) = map.next_key::<&str>()? {
++ match Field::deserialize(name.into_deserializer())? {
++ $(Field::$name => {
++ $(errors.push(format!("deprecated field `{}`. {}", name, $dep));)?
++ match map.next_value() {
++ Err(e) => errors.push(e.to_string()),
++ Ok(value) => match $name {
++ Some(_) => errors.push(format!("duplicate field `{}`", name)),
++ None => $name = Some(value),
++ }
++ }
++ })*
++ // white-listed; ignore
++ Field::third_party => drop(map.next_value::<IgnoredAny>())
++ }
+ }
- pub use self::helpers::Conf;
++ let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
++ Ok(TryConf { conf, errors })
++ }
+ }
+ };
+}
+
- /// Lint: CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR. The minimum rust version that the project supports
- (msrv, "msrv": Option<String>, None),
++// N.B., this macro is parsed by util/lintlib.py
+define_Conf! {
- (blacklisted_names, "blacklisted_names": Vec<String>, ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()),
++ /// Lint: CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE. The minimum rust version that the project supports
++ (msrv: Option<String> = None),
+ /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
- (cognitive_complexity_threshold, "cognitive_complexity_threshold": u64, 25),
++ (blacklisted_names: Vec<String> = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()),
+ /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
- (cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold": Option<u64>, None),
++ (cognitive_complexity_threshold: u64 = 25),
+ /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead.
- (doc_valid_idents, "doc_valid_idents": Vec<String>, [
++ #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")]
++ (cyclomatic_complexity_threshold: Option<u64> = None),
+ /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
- (too_many_arguments_threshold, "too_many_arguments_threshold": u64, 7),
++ (doc_valid_idents: Vec<String> = [
+ "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",
+ "TeX", "LaTeX", "BibTeX", "BibLaTeX",
+ "MinGW",
+ "CamelCase",
+ ].iter().map(ToString::to_string).collect()),
+ /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
- (type_complexity_threshold, "type_complexity_threshold": u64, 250),
++ (too_many_arguments_threshold: u64 = 7),
+ /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
- (single_char_binding_names_threshold, "single_char_binding_names_threshold": u64, 4),
++ (type_complexity_threshold: u64 = 250),
+ /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
- (too_large_for_stack, "too_large_for_stack": u64, 200),
++ (single_char_binding_names_threshold: u64 = 4),
+ /// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
- (enum_variant_name_threshold, "enum_variant_name_threshold": u64, 3),
++ (too_large_for_stack: u64 = 200),
+ /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
- (enum_variant_size_threshold, "enum_variant_size_threshold": u64, 200),
++ (enum_variant_name_threshold: u64 = 3),
+ /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion
- (verbose_bit_mask_threshold, "verbose_bit_mask_threshold": u64, 1),
++ (enum_variant_size_threshold: u64 = 200),
+ /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
- (literal_representation_threshold, "literal_representation_threshold": u64, 16384),
++ (verbose_bit_mask_threshold: u64 = 1),
+ /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
- (trivial_copy_size_limit, "trivial_copy_size_limit": Option<u64>, None),
++ (literal_representation_threshold: u64 = 16384),
+ /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
- (pass_by_value_size_limit, "pass_by_value_size_limit": u64, 256),
++ (trivial_copy_size_limit: Option<u64> = None),
+ /// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value.
- (too_many_lines_threshold, "too_many_lines_threshold": u64, 100),
++ (pass_by_value_size_limit: u64 = 256),
+ /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have
- (array_size_threshold, "array_size_threshold": u64, 512_000),
++ (too_many_lines_threshold: u64 = 100),
+ /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack
- (vec_box_size_threshold, "vec_box_size_threshold": u64, 4096),
++ (array_size_threshold: u64 = 512_000),
+ /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed
- (max_trait_bounds, "max_trait_bounds": u64, 3),
++ (vec_box_size_threshold: u64 = 4096),
+ /// Lint: TYPE_REPETITION_IN_BOUNDS. The maximum number of bounds a trait can have to be linted
- (max_struct_bools, "max_struct_bools": u64, 3),
++ (max_trait_bounds: u64 = 3),
+ /// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have
- (max_fn_params_bools, "max_fn_params_bools": u64, 3),
++ (max_struct_bools: u64 = 3),
+ /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have
- (warn_on_all_wildcard_imports, "warn_on_all_wildcard_imports": bool, false),
++ (max_fn_params_bools: u64 = 3),
+ /// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests).
- (disallowed_methods, "disallowed_methods": Vec<String>, Vec::<String>::new()),
++ (warn_on_all_wildcard_imports: bool = false),
+ /// Lint: DISALLOWED_METHOD. The list of disallowed methods, written as fully qualified paths.
- (unreadable_literal_lint_fractions, "unreadable_literal_lint_fractions": bool, true),
++ (disallowed_methods: Vec<String> = Vec::new()),
+ /// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators.
- (upper_case_acronyms_aggressive, "upper_case_acronyms_aggressive": bool, false),
++ (unreadable_literal_lint_fractions: bool = true),
+ /// Lint: UPPER_CASE_ACRONYMS. Enables verbose mode. Triggers if there is more than one uppercase char next to each other
- (cargo_ignore_publish, "cargo_ignore_publish": bool, false),
- }
-
- impl Default for Conf {
- #[must_use]
- fn default() -> Self {
- toml::from_str("").expect("we never error on empty config files")
- }
++ (upper_case_acronyms_aggressive: bool = false),
+ /// Lint: _CARGO_COMMON_METADATA. For internal testing only, ignores the current `publish` settings in the Cargo manifest.
- /// Produces a `Conf` filled with the default values and forwards the errors
- ///
- /// Used internally for convenience
- fn default(errors: Vec<Error>) -> (Conf, Vec<Error>) {
- (Conf::default(), errors)
- }
-
++ (cargo_ignore_publish: bool = false),
+}
+
+/// Search for the configuration file.
+pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
+ /// Possible filename to search for.
+ const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
+
+ // Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
+ // If neither of those exist, use ".".
+ let mut current = env::var_os("CLIPPY_CONF_DIR")
+ .or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
+ .map_or_else(|| PathBuf::from("."), PathBuf::from);
+ loop {
+ for config_file_name in &CONFIG_FILE_NAMES {
+ let config_file = current.join(config_file_name);
+ match fs::metadata(&config_file) {
+ // Only return if it's a file to handle the unlikely situation of a directory named
+ // `clippy.toml`.
+ Ok(ref md) if !md.is_dir() => return Ok(Some(config_file)),
+ // Return the error if it's something other than `NotFound`; otherwise we didn't
+ // find the project file yet, and continue searching.
+ Err(e) if e.kind() != io::ErrorKind::NotFound => return Err(e),
+ _ => {},
+ }
+ }
+
+ // If the current directory has no parent, we're done searching.
+ if !current.pop() {
+ return Ok(None);
+ }
+ }
+}
+
- pub fn read(path: &Path) -> (Conf, Vec<Error>) {
+/// Read the `toml` configuration file.
+///
+/// In case of error, the function tries to continue as much as possible.
- Err(err) => return default(vec![err.into()]),
++pub fn read(path: &Path) -> TryConf {
+ let content = match fs::read_to_string(path) {
++ Err(e) => return TryConf::from_error(e),
+ Ok(content) => content,
-
- assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty());
- match toml::from_str(&content) {
- Ok(toml) => {
- let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
-
- let toml_ref: &Conf = &toml;
-
- let cyc_field: Option<u64> = toml_ref.cyclomatic_complexity_threshold;
-
- if cyc_field.is_some() {
- let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string();
- errors.push(Error::Toml(cyc_err));
- }
-
- (toml, errors)
- },
- Err(e) => {
- let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
- errors.push(Error::Toml(e.to_string()));
-
- default(errors)
- },
- }
+ };
++ toml::from_str(&content).unwrap_or_else(TryConf::from_error)
+}
--- /dev/null
+use crate::consts::{constant_simple, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet;
+use clippy_utils::ty::match_type;
+use clippy_utils::{
+ is_else_clause, is_expn_of, is_expr_path_def_path, match_def_path, method_calls, path_to_res, paths, run_lints,
+ SpanlessEq,
+};
+use if_chain::if_chain;
+use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, ModKind, NodeId};
+use rustc_ast::visit::FnKind;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::hir_id::CRATE_HIR_ID;
+use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
+use rustc_hir::{
+ BinOpKind, Block, Crate, Expr, ExprKind, HirId, Item, Local, MatchSource, MutTy, Mutability, Node, Path, Stmt,
+ StmtKind, Ty, TyKind, UnOp,
+};
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_middle::mir::interpret::ConstValue;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Spanned;
+use rustc_span::symbol::{Symbol, SymbolStr};
+use rustc_span::{BytePos, Span};
+use rustc_typeck::hir_ty_to_ty;
+
+use std::borrow::{Borrow, Cow};
+
++#[cfg(feature = "metadata-collector-lint")]
++pub mod metadata_collector;
++
+declare_clippy_lint! {
+ /// **What it does:** Checks for various things we like to keep tidy in clippy.
+ ///
+ /// **Why is this bad?** We like to pretend we're an example of tidy code.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:** Wrong ordering of the util::paths constants.
+ pub CLIPPY_LINTS_INTERNAL,
+ internal,
+ "various things that will negatively affect your clippy experience"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Ensures every lint is associated to a `LintPass`.
+ ///
+ /// **Why is this bad?** The compiler only knows lints via a `LintPass`. Without
+ /// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not
+ /// know the name of the lint.
+ ///
+ /// **Known problems:** Only checks for lints associated using the
+ /// `declare_lint_pass!`, `impl_lint_pass!`, and `lint_array!` macros.
+ ///
+ /// **Example:**
+ /// ```rust,ignore
+ /// declare_lint! { pub LINT_1, ... }
+ /// declare_lint! { pub LINT_2, ... }
+ /// declare_lint! { pub FORGOTTEN_LINT, ... }
+ /// // ...
+ /// declare_lint_pass!(Pass => [LINT_1, LINT_2]);
+ /// // missing FORGOTTEN_LINT
+ /// ```
+ pub LINT_WITHOUT_LINT_PASS,
+ internal,
+ "declaring a lint without associating it in a LintPass"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for calls to `cx.span_lint*` and suggests to use the `utils::*`
+ /// variant of the function.
+ ///
+ /// **Why is this bad?** The `utils::*` variants also add a link to the Clippy documentation to the
+ /// warning/error messages.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// Bad:
+ /// ```rust,ignore
+ /// cx.span_lint(LINT_NAME, "message");
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// utils::span_lint(cx, LINT_NAME, "message");
+ /// ```
+ pub COMPILER_LINT_FUNCTIONS,
+ internal,
+ "usage of the lint functions of the compiler instead of the utils::* variant"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for calls to `cx.outer().expn_data()` and suggests to use
+ /// the `cx.outer_expn_data()`
+ ///
+ /// **Why is this bad?** `cx.outer_expn_data()` is faster and more concise.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// Bad:
+ /// ```rust,ignore
+ /// expr.span.ctxt().outer().expn_data()
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// expr.span.ctxt().outer_expn_data()
+ /// ```
+ pub OUTER_EXPN_EXPN_DATA,
+ internal,
+ "using `cx.outer_expn().expn_data()` instead of `cx.outer_expn_data()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Not an actual lint. This lint is only meant for testing our customized internal compiler
+ /// error message by calling `panic`.
+ ///
+ /// **Why is this bad?** ICE in large quantities can damage your teeth
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// Bad:
+ /// ```rust,ignore
+ /// 🍦🍦🍦🍦🍦
+ /// ```
+ pub PRODUCE_ICE,
+ internal,
+ "this message should not appear anywhere as we ICE before and don't emit the lint"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for cases of an auto-generated lint without an updated description,
+ /// i.e. `default lint description`.
+ ///
+ /// **Why is this bad?** Indicates that the lint is not finished.
+ ///
+ /// **Known problems:** None
+ ///
+ /// **Example:**
+ /// Bad:
+ /// ```rust,ignore
+ /// declare_lint! { pub COOL_LINT, nursery, "default lint description" }
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" }
+ /// ```
+ pub DEFAULT_LINT,
+ internal,
+ "found 'default lint description' in a lint declaration"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Lints `span_lint_and_then` function calls, where the
+ /// closure argument has only one statement and that statement is a method
+ /// call to `span_suggestion`, `span_help`, `span_note` (using the same
+ /// span), `help` or `note`.
+ ///
+ /// These usages of `span_lint_and_then` should be replaced with one of the
+ /// wrapper functions `span_lint_and_sugg`, span_lint_and_help`, or
+ /// `span_lint_and_note`.
+ ///
+ /// **Why is this bad?** Using the wrapper `span_lint_and_*` functions, is more
+ /// convenient, readable and less error prone.
+ ///
+ /// **Known problems:** None
+ ///
+ /// *Example:**
+ /// Bad:
+ /// ```rust,ignore
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.span_suggestion(
+ /// expr.span,
+ /// help_msg,
+ /// sugg.to_string(),
+ /// Applicability::MachineApplicable,
+ /// );
+ /// });
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.span_help(expr.span, help_msg);
+ /// });
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.help(help_msg);
+ /// });
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.span_note(expr.span, note_msg);
+ /// });
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.note(note_msg);
+ /// });
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// span_lint_and_sugg(
+ /// cx,
+ /// TEST_LINT,
+ /// expr.span,
+ /// lint_msg,
+ /// help_msg,
+ /// sugg.to_string(),
+ /// Applicability::MachineApplicable,
+ /// );
+ /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg);
+ /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg);
+ /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg);
+ /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg);
+ /// ```
+ pub COLLAPSIBLE_SPAN_LINT_CALLS,
+ internal,
+ "found collapsible `span_lint_and_then` calls"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for calls to `utils::match_type()` on a type diagnostic item
+ /// and suggests to use `utils::is_type_diagnostic_item()` instead.
+ ///
+ /// **Why is this bad?** `utils::is_type_diagnostic_item()` does not require hardcoded paths.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// Bad:
+ /// ```rust,ignore
+ /// utils::match_type(cx, ty, &paths::VEC)
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// utils::is_type_diagnostic_item(cx, ty, sym::vec_type)
+ /// ```
+ pub MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
+ internal,
+ "using `utils::match_type()` instead of `utils::is_type_diagnostic_item()`"
+}
+
+declare_clippy_lint! {
+ /// **What it does:**
+ /// Checks the paths module for invalid paths.
+ ///
+ /// **Why is this bad?**
+ /// It indicates a bug in the code.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:** None.
+ pub INVALID_PATHS,
+ internal,
+ "invalid path"
+}
+
+declare_clippy_lint! {
+ /// **What it does:**
+ /// Checks for interning symbols that have already been pre-interned and defined as constants.
+ ///
+ /// **Why is this bad?**
+ /// It's faster and easier to use the symbol constant.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// Bad:
+ /// ```rust,ignore
+ /// let _ = sym!(f32);
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// let _ = sym::f32;
+ /// ```
+ pub INTERNING_DEFINED_SYMBOL,
+ internal,
+ "interning a symbol that is pre-interned and defined as a constant"
+}
+
+declare_clippy_lint! {
+ /// **What it does:** Checks for unnecessary conversion from Symbol to a string.
+ ///
+ /// **Why is this bad?** It's faster use symbols directly intead of strings.
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ /// Bad:
+ /// ```rust,ignore
+ /// symbol.as_str() == "clippy";
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// symbol == sym::clippy;
+ /// ```
+ pub UNNECESSARY_SYMBOL_STR,
+ internal,
+ "unnecessary conversion between Symbol and string"
+}
+
+declare_clippy_lint! {
+ /// Finds unidiomatic usage of `if_chain!`
+ pub IF_CHAIN_STYLE,
+ internal,
+ "non-idiomatic `if_chain!` usage"
+}
+
+declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
+
+impl EarlyLintPass for ClippyLintsInternal {
+ fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &AstCrate) {
+ if let Some(utils) = krate.items.iter().find(|item| item.ident.name.as_str() == "utils") {
+ if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = utils.kind {
+ if let Some(paths) = items.iter().find(|item| item.ident.name.as_str() == "paths") {
+ if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = paths.kind {
+ let mut last_name: Option<SymbolStr> = None;
+ for item in items {
+ let name = item.ident.as_str();
+ if let Some(ref last_name) = last_name {
+ if **last_name > *name {
+ span_lint(
+ cx,
+ CLIPPY_LINTS_INTERNAL,
+ item.span,
+ "this constant should be before the previous constant due to lexical \
+ ordering",
+ );
+ }
+ }
+ last_name = Some(name);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct LintWithoutLintPass {
+ declared_lints: FxHashMap<Symbol, Span>,
+ registered_lints: FxHashSet<Symbol>,
+}
+
+impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS]);
+
+impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if !run_lints(cx, &[DEFAULT_LINT], item.hir_id()) {
+ return;
+ }
+
+ if let hir::ItemKind::Static(ty, Mutability::Not, body_id) = item.kind {
+ if is_lint_ref_type(cx, ty) {
+ let expr = &cx.tcx.hir().body(body_id).value;
+ if_chain! {
+ if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind;
+ if let ExprKind::Struct(_, fields, _) = inner_exp.kind;
+ let field = fields
+ .iter()
+ .find(|f| f.ident.as_str() == "desc")
+ .expect("lints must have a description field");
+ if let ExprKind::Lit(Spanned {
+ node: LitKind::Str(ref sym, _),
+ ..
+ }) = field.expr.kind;
+ if sym.as_str() == "default lint description";
+
+ then {
+ span_lint(
+ cx,
+ DEFAULT_LINT,
+ item.span,
+ &format!("the lint `{}` has the default lint description", item.ident.name),
+ );
+ }
+ }
+ self.declared_lints.insert(item.ident.name, item.span);
+ }
+ } else if is_expn_of(item.span, "impl_lint_pass").is_some()
+ || is_expn_of(item.span, "declare_lint_pass").is_some()
+ {
+ if let hir::ItemKind::Impl(hir::Impl {
+ of_trait: None,
+ items: impl_item_refs,
+ ..
+ }) = item.kind
+ {
+ let mut collector = LintCollector {
+ output: &mut self.registered_lints,
+ cx,
+ };
+ let body_id = cx.tcx.hir().body_owned_by(
+ impl_item_refs
+ .iter()
+ .find(|iiref| iiref.ident.as_str() == "get_lints")
+ .expect("LintPass needs to implement get_lints")
+ .id
+ .hir_id(),
+ );
+ collector.visit_expr(&cx.tcx.hir().body(body_id).value);
+ }
+ }
+ }
+
+ fn check_crate_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx Crate<'_>) {
+ if !run_lints(cx, &[LINT_WITHOUT_LINT_PASS], CRATE_HIR_ID) {
+ return;
+ }
+
+ for (lint_name, &lint_span) in &self.declared_lints {
+ // When using the `declare_tool_lint!` macro, the original `lint_span`'s
+ // file points to "<rustc macros>".
+ // `compiletest-rs` thinks that's an error in a different file and
+ // just ignores it. This causes the test in compile-fail/lint_pass
+ // not able to capture the error.
+ // Therefore, we need to climb the macro expansion tree and find the
+ // actual span that invoked `declare_tool_lint!`:
+ let lint_span = lint_span.ctxt().outer_expn_data().call_site;
+
+ if !self.registered_lints.contains(lint_name) {
+ span_lint(
+ cx,
+ LINT_WITHOUT_LINT_PASS,
+ lint_span,
+ &format!("the lint `{}` is not added to any `LintPass`", lint_name),
+ );
+ }
+ }
+ }
+}
+
+fn is_lint_ref_type<'tcx>(cx: &LateContext<'tcx>, ty: &Ty<'_>) -> bool {
+ if let TyKind::Rptr(
+ _,
+ MutTy {
+ ty: inner,
+ mutbl: Mutability::Not,
+ },
+ ) = ty.kind
+ {
+ if let TyKind::Path(ref path) = inner.kind {
+ if let Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, inner.hir_id) {
+ return match_def_path(cx, def_id, &paths::LINT);
+ }
+ }
+ }
+
+ false
+}
+
+struct LintCollector<'a, 'tcx> {
+ output: &'a mut FxHashSet<Symbol>,
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for LintCollector<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_path(&mut self, path: &'tcx Path<'_>, _: HirId) {
+ if path.segments.len() == 1 {
+ self.output.insert(path.segments[0].ident.name);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::All(self.cx.tcx.hir())
+ }
+}
+
+#[derive(Clone, Default)]
+pub struct CompilerLintFunctions {
+ map: FxHashMap<&'static str, &'static str>,
+}
+
+impl CompilerLintFunctions {
+ #[must_use]
+ pub fn new() -> Self {
+ let mut map = FxHashMap::default();
+ map.insert("span_lint", "utils::span_lint");
+ map.insert("struct_span_lint", "utils::span_lint");
+ map.insert("lint", "utils::span_lint");
+ map.insert("span_lint_note", "utils::span_lint_and_note");
+ map.insert("span_lint_help", "utils::span_lint_and_help");
+ Self { map }
+ }
+}
+
+impl_lint_pass!(CompilerLintFunctions => [COMPILER_LINT_FUNCTIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for CompilerLintFunctions {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if !run_lints(cx, &[COMPILER_LINT_FUNCTIONS], expr.hir_id) {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::MethodCall(path, _, args, _) = expr.kind;
+ let fn_name = path.ident;
+ if let Some(sugg) = self.map.get(&*fn_name.as_str());
+ let ty = cx.typeck_results().expr_ty(&args[0]).peel_refs();
+ if match_type(cx, ty, &paths::EARLY_CONTEXT)
+ || match_type(cx, ty, &paths::LATE_CONTEXT);
+ then {
+ span_lint_and_help(
+ cx,
+ COMPILER_LINT_FUNCTIONS,
+ path.ident.span,
+ "usage of a compiler lint function",
+ None,
+ &format!("please use the Clippy variant of this function: `{}`", sugg),
+ );
+ }
+ }
+ }
+}
+
+declare_lint_pass!(OuterExpnDataPass => [OUTER_EXPN_EXPN_DATA]);
+
+impl<'tcx> LateLintPass<'tcx> for OuterExpnDataPass {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if !run_lints(cx, &[OUTER_EXPN_EXPN_DATA], expr.hir_id) {
+ return;
+ }
+
+ let (method_names, arg_lists, spans) = method_calls(expr, 2);
+ let method_names: Vec<SymbolStr> = method_names.iter().map(|s| s.as_str()).collect();
+ let method_names: Vec<&str> = method_names.iter().map(|s| &**s).collect();
+ if_chain! {
+ if let ["expn_data", "outer_expn"] = method_names.as_slice();
+ let args = arg_lists[1];
+ if args.len() == 1;
+ let self_arg = &args[0];
+ let self_ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
+ if match_type(cx, self_ty, &paths::SYNTAX_CONTEXT);
+ then {
+ span_lint_and_sugg(
+ cx,
+ OUTER_EXPN_EXPN_DATA,
+ spans[1].with_hi(expr.span.hi()),
+ "usage of `outer_expn().expn_data()`",
+ "try",
+ "outer_expn_data()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
+
+declare_lint_pass!(ProduceIce => [PRODUCE_ICE]);
+
+impl EarlyLintPass for ProduceIce {
+ fn check_fn(&mut self, _: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
+ if is_trigger_fn(fn_kind) {
+ panic!("Would you like some help with that?");
+ }
+ }
+}
+
+fn is_trigger_fn(fn_kind: FnKind<'_>) -> bool {
+ match fn_kind {
+ FnKind::Fn(_, ident, ..) => ident.name.as_str() == "it_looks_like_you_are_trying_to_kill_clippy",
+ FnKind::Closure(..) => false,
+ }
+}
+
+declare_lint_pass!(CollapsibleCalls => [COLLAPSIBLE_SPAN_LINT_CALLS]);
+
+impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if !run_lints(cx, &[COLLAPSIBLE_SPAN_LINT_CALLS], expr.hir_id) {
+ return;
+ }
+
+ if_chain! {
+ 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::Block(block, _) = &body.value.kind;
+ let stmts = &block.stmts;
+ if stmts.len() == 1 && block.expr.is_none();
+ if let StmtKind::Semi(only_expr) = &stmts[0].kind;
+ if let ExprKind::MethodCall(ps, _, span_call_args, _) = &only_expr.kind;
+ then {
+ let and_then_snippets = get_and_then_snippets(cx, and_then_args);
+ let mut sle = SpanlessEq::new(cx).deny_side_effects();
+ match &*ps.ident.as_str() {
+ "span_suggestion" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
+ suggest_suggestion(cx, expr, &and_then_snippets, &span_suggestion_snippets(cx, span_call_args));
+ },
+ "span_help" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
+ let help_snippet = snippet(cx, span_call_args[2].span, r#""...""#);
+ suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true);
+ },
+ "span_note" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
+ let note_snippet = snippet(cx, span_call_args[2].span, r#""...""#);
+ suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true);
+ },
+ "help" => {
+ let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
+ suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false);
+ }
+ "note" => {
+ let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
+ suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false);
+ }
+ _ => (),
+ }
+ }
+ }
+ }
+}
+
+struct AndThenSnippets<'a> {
+ cx: Cow<'a, str>,
+ lint: Cow<'a, str>,
+ span: Cow<'a, str>,
+ msg: Cow<'a, str>,
+}
+
+fn get_and_then_snippets<'a, 'hir>(cx: &LateContext<'_>, and_then_snippets: &'hir [Expr<'hir>]) -> AndThenSnippets<'a> {
+ let cx_snippet = snippet(cx, and_then_snippets[0].span, "cx");
+ let lint_snippet = snippet(cx, and_then_snippets[1].span, "..");
+ let span_snippet = snippet(cx, and_then_snippets[2].span, "span");
+ let msg_snippet = snippet(cx, and_then_snippets[3].span, r#""...""#);
+
+ AndThenSnippets {
+ cx: cx_snippet,
+ lint: lint_snippet,
+ span: span_snippet,
+ msg: msg_snippet,
+ }
+}
+
+struct SpanSuggestionSnippets<'a> {
+ help: Cow<'a, str>,
+ sugg: Cow<'a, str>,
+ applicability: Cow<'a, str>,
+}
+
+fn span_suggestion_snippets<'a, 'hir>(
+ cx: &LateContext<'_>,
+ span_call_args: &'hir [Expr<'hir>],
+) -> SpanSuggestionSnippets<'a> {
+ let help_snippet = snippet(cx, span_call_args[2].span, r#""...""#);
+ let sugg_snippet = snippet(cx, span_call_args[3].span, "..");
+ let applicability_snippet = snippet(cx, span_call_args[4].span, "Applicability::MachineApplicable");
+
+ SpanSuggestionSnippets {
+ help: help_snippet,
+ sugg: sugg_snippet,
+ applicability: applicability_snippet,
+ }
+}
+
+fn suggest_suggestion(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ and_then_snippets: &AndThenSnippets<'_>,
+ span_suggestion_snippets: &SpanSuggestionSnippets<'_>,
+) {
+ span_lint_and_sugg(
+ cx,
+ COLLAPSIBLE_SPAN_LINT_CALLS,
+ expr.span,
+ "this call is collapsible",
+ "collapse into",
+ format!(
+ "span_lint_and_sugg({}, {}, {}, {}, {}, {}, {})",
+ and_then_snippets.cx,
+ and_then_snippets.lint,
+ and_then_snippets.span,
+ and_then_snippets.msg,
+ span_suggestion_snippets.help,
+ span_suggestion_snippets.sugg,
+ span_suggestion_snippets.applicability
+ ),
+ Applicability::MachineApplicable,
+ );
+}
+
+fn suggest_help(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ and_then_snippets: &AndThenSnippets<'_>,
+ help: &str,
+ with_span: bool,
+) {
+ let option_span = if with_span {
+ format!("Some({})", and_then_snippets.span)
+ } else {
+ "None".to_string()
+ };
+
+ span_lint_and_sugg(
+ cx,
+ COLLAPSIBLE_SPAN_LINT_CALLS,
+ expr.span,
+ "this call is collapsible",
+ "collapse into",
+ format!(
+ "span_lint_and_help({}, {}, {}, {}, {}, {})",
+ and_then_snippets.cx,
+ and_then_snippets.lint,
+ and_then_snippets.span,
+ and_then_snippets.msg,
+ &option_span,
+ help
+ ),
+ Applicability::MachineApplicable,
+ );
+}
+
+fn suggest_note(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ and_then_snippets: &AndThenSnippets<'_>,
+ note: &str,
+ with_span: bool,
+) {
+ let note_span = if with_span {
+ format!("Some({})", and_then_snippets.span)
+ } else {
+ "None".to_string()
+ };
+
+ span_lint_and_sugg(
+ cx,
+ COLLAPSIBLE_SPAN_LINT_CALLS,
+ expr.span,
+ "this call is collspible",
+ "collapse into",
+ format!(
+ "span_lint_and_note({}, {}, {}, {}, {}, {})",
+ and_then_snippets.cx,
+ and_then_snippets.lint,
+ and_then_snippets.span,
+ and_then_snippets.msg,
+ note_span,
+ note
+ ),
+ Applicability::MachineApplicable,
+ );
+}
+
+declare_lint_pass!(MatchTypeOnDiagItem => [MATCH_TYPE_ON_DIAGNOSTIC_ITEM]);
+
+impl<'tcx> LateLintPass<'tcx> for MatchTypeOnDiagItem {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if !run_lints(cx, &[MATCH_TYPE_ON_DIAGNOSTIC_ITEM], expr.hir_id) {
+ return;
+ }
+
+ if_chain! {
+ // Check if this is a call to utils::match_type()
+ if let ExprKind::Call(fn_path, [context, ty, ty_path]) = expr.kind;
+ if is_expr_path_def_path(cx, fn_path, &["clippy_utils", "ty", "match_type"]);
+ // Extract the path to the matched type
+ if let Some(segments) = path_to_matched_type(cx, ty_path);
+ let segments: Vec<&str> = segments.iter().map(|sym| &**sym).collect();
+ if let Some(ty_did) = path_to_res(cx, &segments[..]).opt_def_id();
+ // Check if the matched type is a diagnostic item
+ let diag_items = cx.tcx.diagnostic_items(ty_did.krate);
+ if let Some(item_name) = diag_items.iter().find_map(|(k, v)| if *v == ty_did { Some(k) } else { None });
+ then {
+ // TODO: check paths constants from external crates.
+ let cx_snippet = snippet(cx, context.span, "_");
+ let ty_snippet = snippet(cx, ty.span, "_");
+
+ span_lint_and_sugg(
+ cx,
+ MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
+ expr.span,
+ "usage of `clippy_utils::ty::match_type()` on a type diagnostic item",
+ "try",
+ format!("clippy_utils::ty::is_type_diagnostic_item({}, {}, sym::{})", cx_snippet, ty_snippet, item_name),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+}
+
+fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Vec<SymbolStr>> {
+ use rustc_hir::ItemKind;
+
+ match &expr.kind {
+ ExprKind::AddrOf(.., expr) => return path_to_matched_type(cx, expr),
+ ExprKind::Path(qpath) => match cx.qpath_res(qpath, expr.hir_id) {
+ Res::Local(hir_id) => {
+ let parent_id = cx.tcx.hir().get_parent_node(hir_id);
+ if let Some(Node::Local(local)) = cx.tcx.hir().find(parent_id) {
+ if let Some(init) = local.init {
+ return path_to_matched_type(cx, init);
+ }
+ }
+ },
+ Res::Def(DefKind::Const | DefKind::Static, def_id) => {
+ if let Some(Node::Item(item)) = cx.tcx.hir().get_if_local(def_id) {
+ if let ItemKind::Const(.., body_id) | ItemKind::Static(.., body_id) = item.kind {
+ let body = cx.tcx.hir().body(body_id);
+ return path_to_matched_type(cx, &body.value);
+ }
+ }
+ },
+ _ => {},
+ },
+ ExprKind::Array(exprs) => {
+ let segments: Vec<SymbolStr> = exprs
+ .iter()
+ .filter_map(|expr| {
+ if let ExprKind::Lit(lit) = &expr.kind {
+ if let LitKind::Str(sym, _) = lit.node {
+ return Some(sym.as_str());
+ }
+ }
+
+ None
+ })
+ .collect();
+
+ if segments.len() == exprs.len() {
+ return Some(segments);
+ }
+ },
+ _ => {},
+ }
+
+ None
+}
+
+// This is not a complete resolver for paths. It works on all the paths currently used in the paths
+// module. That's all it does and all it needs to do.
+pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
+ if path_to_res(cx, path) != Res::Err {
+ return true;
+ }
+
+ // Some implementations can't be found by `path_to_res`, particularly inherent
+ // implementations of native types. Check lang items.
+ let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect();
+ let lang_items = cx.tcx.lang_items();
+ for item_def_id in lang_items.items().iter().flatten() {
+ let lang_item_path = cx.get_def_path(*item_def_id);
+ if path_syms.starts_with(&lang_item_path) {
+ if let [item] = &path_syms[lang_item_path.len()..] {
+ for child in cx.tcx.item_children(*item_def_id) {
+ if child.ident.name == *item {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ false
+}
+
+declare_lint_pass!(InvalidPaths => [INVALID_PATHS]);
+
+impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ let local_def_id = &cx.tcx.parent_module(item.hir_id());
+ let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
+ if_chain! {
+ if mod_name.as_str() == "paths";
+ if let hir::ItemKind::Const(ty, body_id) = item.kind;
+ let ty = hir_ty_to_ty(cx.tcx, ty);
+ if let ty::Array(el_ty, _) = &ty.kind();
+ if let ty::Ref(_, el_ty, _) = &el_ty.kind();
+ if el_ty.is_str();
+ let body = cx.tcx.hir().body(body_id);
+ let typeck_results = cx.tcx.typeck_body(body_id);
+ if let Some(Constant::Vec(path)) = constant_simple(cx, typeck_results, &body.value);
+ let path: Vec<&str> = path.iter().map(|x| {
+ if let Constant::Str(s) = x {
+ s.as_str()
+ } else {
+ // We checked the type of the constant above
+ unreachable!()
+ }
+ }).collect();
+ if !check_path(cx, &path[..]);
+ then {
+ span_lint(cx, CLIPPY_LINTS_INTERNAL, item.span, "invalid path");
+ }
+ }
+ }
+}
+
+#[derive(Default)]
+pub struct InterningDefinedSymbol {
+ // Maps the symbol value to the constant DefId.
+ symbol_map: FxHashMap<u32, DefId>,
+}
+
+impl_lint_pass!(InterningDefinedSymbol => [INTERNING_DEFINED_SYMBOL, UNNECESSARY_SYMBOL_STR]);
+
+impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
+ fn check_crate(&mut self, cx: &LateContext<'_>, _: &Crate<'_>) {
+ if !self.symbol_map.is_empty() {
+ return;
+ }
+
+ for &module in &[&paths::KW_MODULE, &paths::SYM_MODULE] {
+ if let Some(def_id) = path_to_res(cx, module).opt_def_id() {
+ for item in cx.tcx.item_children(def_id).iter() {
+ if_chain! {
+ if let Res::Def(DefKind::Const, item_def_id) = item.res;
+ let ty = cx.tcx.type_of(item_def_id);
+ if match_type(cx, ty, &paths::SYMBOL);
+ if let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(item_def_id);
+ if let Ok(value) = value.to_u32();
+ then {
+ self.symbol_map.insert(value, item_def_id);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(func, [arg]) = &expr.kind;
+ if let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(func).kind();
+ if match_def_path(cx, *def_id, &paths::SYMBOL_INTERN);
+ if let Some(Constant::Str(arg)) = constant_simple(cx, cx.typeck_results(), arg);
+ let value = Symbol::intern(&arg).as_u32();
+ if let Some(&def_id) = self.symbol_map.get(&value);
+ then {
+ span_lint_and_sugg(
+ cx,
+ INTERNING_DEFINED_SYMBOL,
+ is_expn_of(expr.span, "sym").unwrap_or(expr.span),
+ "interning a defined symbol",
+ "try",
+ cx.tcx.def_path_str(def_id),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ if let ExprKind::Binary(op, left, right) = expr.kind {
+ if matches!(op.node, BinOpKind::Eq | BinOpKind::Ne) {
+ let data = [
+ (left, self.symbol_str_expr(left, cx)),
+ (right, self.symbol_str_expr(right, cx)),
+ ];
+ match data {
+ // both operands are a symbol string
+ [(_, Some(left)), (_, Some(right))] => {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_SYMBOL_STR,
+ expr.span,
+ "unnecessary `Symbol` to string conversion",
+ "try",
+ format!(
+ "{} {} {}",
+ left.as_symbol_snippet(cx),
+ op.node.as_str(),
+ right.as_symbol_snippet(cx),
+ ),
+ Applicability::MachineApplicable,
+ );
+ },
+ // one of the operands is a symbol string
+ [(expr, Some(symbol)), _] | [_, (expr, Some(symbol))] => {
+ // creating an owned string for comparison
+ if matches!(symbol, SymbolStrExpr::Expr { is_to_owned: true, .. }) {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_SYMBOL_STR,
+ expr.span,
+ "unnecessary string allocation",
+ "try",
+ format!("{}.as_str()", symbol.as_symbol_snippet(cx)),
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ // nothing found
+ [(_, None), (_, None)] => {},
+ }
+ }
+ }
+ }
+}
+
+impl InterningDefinedSymbol {
+ fn symbol_str_expr<'tcx>(&self, expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> Option<SymbolStrExpr<'tcx>> {
+ static IDENT_STR_PATHS: &[&[&str]] = &[&paths::IDENT_AS_STR, &paths::TO_STRING_METHOD];
+ static SYMBOL_STR_PATHS: &[&[&str]] = &[
+ &paths::SYMBOL_AS_STR,
+ &paths::SYMBOL_TO_IDENT_STRING,
+ &paths::TO_STRING_METHOD,
+ ];
+ // SymbolStr might be de-referenced: `&*symbol.as_str()`
+ let call = if_chain! {
+ if let ExprKind::AddrOf(_, _, e) = expr.kind;
+ if let ExprKind::Unary(UnOp::Deref, e) = e.kind;
+ then { e } else { expr }
+ };
+ if_chain! {
+ // is a method call
+ if let ExprKind::MethodCall(_, _, [item], _) = call.kind;
+ if let Some(did) = cx.typeck_results().type_dependent_def_id(call.hir_id);
+ let ty = cx.typeck_results().expr_ty(item);
+ // ...on either an Ident or a Symbol
+ if let Some(is_ident) = if match_type(cx, ty, &paths::SYMBOL) {
+ Some(false)
+ } else if match_type(cx, ty, &paths::IDENT) {
+ Some(true)
+ } else {
+ None
+ };
+ // ...which converts it to a string
+ let paths = if is_ident { IDENT_STR_PATHS } else { SYMBOL_STR_PATHS };
+ if let Some(path) = paths.iter().find(|path| match_def_path(cx, did, path));
+ then {
+ let is_to_owned = path.last().unwrap().ends_with("string");
+ return Some(SymbolStrExpr::Expr {
+ item,
+ is_ident,
+ is_to_owned,
+ });
+ }
+ }
+ // is a string constant
+ if let Some(Constant::Str(s)) = constant_simple(cx, cx.typeck_results(), expr) {
+ let value = Symbol::intern(&s).as_u32();
+ // ...which matches a symbol constant
+ if let Some(&def_id) = self.symbol_map.get(&value) {
+ return Some(SymbolStrExpr::Const(def_id));
+ }
+ }
+ None
+ }
+}
+
+enum SymbolStrExpr<'tcx> {
+ /// a string constant with a corresponding symbol constant
+ Const(DefId),
+ /// a "symbol to string" expression like `symbol.as_str()`
+ Expr {
+ /// part that evaluates to `Symbol` or `Ident`
+ item: &'tcx Expr<'tcx>,
+ is_ident: bool,
+ /// whether an owned `String` is created like `to_ident_string()`
+ is_to_owned: bool,
+ },
+}
+
+impl<'tcx> SymbolStrExpr<'tcx> {
+ /// Returns a snippet that evaluates to a `Symbol` and is const if possible
+ fn as_symbol_snippet(&self, cx: &LateContext<'_>) -> Cow<'tcx, str> {
+ match *self {
+ Self::Const(def_id) => cx.tcx.def_path_str(def_id).into(),
+ Self::Expr { item, is_ident, .. } => {
+ let mut snip = snippet(cx, item.span.source_callsite(), "..");
+ if is_ident {
+ // get `Ident.name`
+ snip.to_mut().push_str(".name");
+ }
+ snip
+ },
+ }
+ }
+}
+
+declare_lint_pass!(IfChainStyle => [IF_CHAIN_STYLE]);
+
+impl<'tcx> LateLintPass<'tcx> for IfChainStyle {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
+ let (local, after, if_chain_span) = if_chain! {
+ if let [Stmt { kind: StmtKind::Local(local), .. }, after @ ..] = block.stmts;
+ if let Some(if_chain_span) = is_expn_of(block.span, "if_chain");
+ then { (local, after, if_chain_span) } else { return }
+ };
+ if is_first_if_chain_expr(cx, block.hir_id, if_chain_span) {
+ span_lint(
+ cx,
+ IF_CHAIN_STYLE,
+ if_chain_local_span(cx, local, if_chain_span),
+ "`let` expression should be above the `if_chain!`",
+ );
+ } else if local.span.ctxt() == block.span.ctxt() && is_if_chain_then(after, block.expr, if_chain_span) {
+ span_lint(
+ cx,
+ IF_CHAIN_STYLE,
+ if_chain_local_span(cx, local, if_chain_span),
+ "`let` expression should be inside `then { .. }`",
+ )
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ let (cond, then, els) = match expr.kind {
+ ExprKind::If(cond, then, els) => (Some(cond), then, els.is_some()),
+ ExprKind::Match(
+ _,
+ [arm, ..],
+ MatchSource::IfLetDesugar {
+ contains_else_clause: els,
+ },
+ ) => (None, arm.body, els),
+ _ => return,
+ };
+ let then_block = match then.kind {
+ ExprKind::Block(block, _) => block,
+ _ => return,
+ };
+ let if_chain_span = is_expn_of(expr.span, "if_chain");
+ if !els {
+ check_nested_if_chains(cx, expr, then_block, if_chain_span);
+ }
+ let if_chain_span = match if_chain_span {
+ None => return,
+ Some(span) => span,
+ };
+ // check for `if a && b;`
+ if_chain! {
+ if let Some(cond) = cond;
+ if let ExprKind::Binary(op, _, _) = cond.kind;
+ if op.node == BinOpKind::And;
+ if cx.sess().source_map().is_multiline(cond.span);
+ then {
+ span_lint(cx, IF_CHAIN_STYLE, cond.span, "`if a && b;` should be `if a; if b;`");
+ }
+ }
+ if is_first_if_chain_expr(cx, expr.hir_id, if_chain_span)
+ && is_if_chain_then(then_block.stmts, then_block.expr, if_chain_span)
+ {
+ span_lint(cx, IF_CHAIN_STYLE, expr.span, "`if_chain!` only has one `if`")
+ }
+ }
+}
+
+fn check_nested_if_chains(
+ cx: &LateContext<'_>,
+ if_expr: &Expr<'_>,
+ then_block: &Block<'_>,
+ if_chain_span: Option<Span>,
+) {
+ #[rustfmt::skip]
+ let (head, tail) = match *then_block {
+ Block { stmts, expr: Some(tail), .. } => (stmts, tail),
+ Block {
+ stmts: &[
+ ref head @ ..,
+ Stmt { kind: StmtKind::Expr(tail) | StmtKind::Semi(tail), .. }
+ ],
+ ..
+ } => (head, tail),
+ _ => return,
+ };
+ if_chain! {
+ if matches!(tail.kind,
+ ExprKind::If(_, _, None)
+ | ExprKind::Match(.., MatchSource::IfLetDesugar { contains_else_clause: false }));
+ let sm = cx.sess().source_map();
+ if head
+ .iter()
+ .all(|stmt| matches!(stmt.kind, StmtKind::Local(..)) && !sm.is_multiline(stmt.span));
+ if if_chain_span.is_some() || !is_else_clause(cx.tcx, if_expr);
+ then {} else { return }
+ }
+ let (span, msg) = match (if_chain_span, is_expn_of(tail.span, "if_chain")) {
+ (None, Some(_)) => (if_expr.span, "this `if` can be part of the inner `if_chain!`"),
+ (Some(_), None) => (tail.span, "this `if` can be part of the outer `if_chain!`"),
+ (Some(a), Some(b)) if a != b => (b, "this `if_chain!` can be merged with the outer `if_chain!`"),
+ _ => return,
+ };
+ span_lint_and_then(cx, IF_CHAIN_STYLE, span, msg, |diag| {
+ let (span, msg) = match head {
+ [] => return,
+ [stmt] => (stmt.span, "this `let` statement can also be in the `if_chain!`"),
+ [a, .., b] => (
+ a.span.to(b.span),
+ "these `let` statements can also be in the `if_chain!`",
+ ),
+ };
+ diag.span_help(span, msg);
+ });
+}
+
+fn is_first_if_chain_expr(cx: &LateContext<'_>, hir_id: HirId, if_chain_span: Span) -> bool {
+ cx.tcx
+ .hir()
+ .parent_iter(hir_id)
+ .find(|(_, node)| {
+ #[rustfmt::skip]
+ !matches!(node, Node::Expr(Expr { kind: ExprKind::Block(..), .. }) | Node::Stmt(_))
+ })
+ .map_or(false, |(id, _)| {
+ is_expn_of(cx.tcx.hir().span(id), "if_chain") != Some(if_chain_span)
+ })
+}
+
+/// Checks a trailing slice of statements and expression of a `Block` to see if they are part
+/// of the `then {..}` portion of an `if_chain!`
+fn is_if_chain_then(stmts: &[Stmt<'_>], expr: Option<&Expr<'_>>, if_chain_span: Span) -> bool {
+ let span = if let [stmt, ..] = stmts {
+ stmt.span
+ } else if let Some(expr) = expr {
+ expr.span
+ } else {
+ // empty `then {}`
+ return true;
+ };
+ is_expn_of(span, "if_chain").map_or(true, |span| span != if_chain_span)
+}
+
+/// Creates a `Span` for `let x = ..;` in an `if_chain!` call.
+fn if_chain_local_span(cx: &LateContext<'_>, local: &Local<'_>, if_chain_span: Span) -> Span {
+ let mut span = local.pat.span;
+ if let Some(init) = local.init {
+ span = span.to(init.span);
+ }
+ span.adjust(if_chain_span.ctxt().outer_expn());
+ let sm = cx.sess().source_map();
+ let span = sm.span_extend_to_prev_str(span, "let", false);
+ let span = sm.span_extend_to_next_char(span, ';', false);
+ Span::new(span.lo() - BytePos(3), span.hi() + BytePos(1), span.ctxt())
+}
--- /dev/null
--- /dev/null
++//! This lint is used to collect metadata about clippy lints. This metadata is exported as a json
++//! file and then used to generate the [clippy lint list](https://rust-lang.github.io/rust-clippy/master/index.html)
++//!
++//! This module and therefor the entire lint is guarded by a feature flag called
++//! `metadata-collector-lint`
++//!
++//! The module transforms all lint names to ascii lowercase to ensure that we don't have mismatches
++//! during any comparison or mapping. (Please take care of this, it's not fun to spend time on such
++//! a simple mistake)
++
++// # NITs
++// - TODO xFrednet 2021-02-13: Collect depreciations and maybe renames
++
++use if_chain::if_chain;
++use rustc_data_structures::fx::FxHashMap;
++use rustc_hir::{
++ self as hir, def::DefKind, intravisit, intravisit::Visitor, ExprKind, Item, ItemKind, Mutability, QPath,
++};
++use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
++use rustc_middle::hir::map::Map;
++use rustc_session::{declare_tool_lint, impl_lint_pass};
++use rustc_span::{sym, Loc, Span, Symbol};
++use serde::{ser::SerializeStruct, Serialize, Serializer};
++use std::collections::BinaryHeap;
++use std::fs::{self, OpenOptions};
++use std::io::prelude::*;
++use std::path::Path;
++
++use crate::utils::internal_lints::is_lint_ref_type;
++use clippy_utils::{
++ diagnostics::span_lint, last_path_segment, match_function_call, match_path, paths, ty::match_type,
++ ty::walk_ptrs_ty_depth,
++};
++
++/// This is the output file of the lint collector.
++const OUTPUT_FILE: &str = "../util/gh-pages/metadata_collection.json";
++/// These lints are excluded from the export.
++const BLACK_LISTED_LINTS: [&str; 3] = ["lint_author", "deep_code_inspection", "internal_metadata_collector"];
++/// These groups will be ignored by the lint group matcher. This is useful for collections like
++/// `clippy::all`
++const IGNORED_LINT_GROUPS: [&str; 1] = ["clippy::all"];
++/// Lints within this group will be excluded from the collection
++const EXCLUDED_LINT_GROUPS: [&str; 1] = ["clippy::internal"];
++
++const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [
++ &["clippy_utils", "diagnostics", "span_lint"],
++ &["clippy_utils", "diagnostics", "span_lint_and_help"],
++ &["clippy_utils", "diagnostics", "span_lint_and_note"],
++ &["clippy_utils", "diagnostics", "span_lint_hir"],
++ &["clippy_utils", "diagnostics", "span_lint_and_sugg"],
++ &["clippy_utils", "diagnostics", "span_lint_and_then"],
++ &["clippy_utils", "diagnostics", "span_lint_hir_and_then"],
++];
++const SUGGESTION_DIAGNOSTIC_BUILDER_METHODS: [(&str, bool); 9] = [
++ ("span_suggestion", false),
++ ("span_suggestion_short", false),
++ ("span_suggestion_verbose", false),
++ ("span_suggestion_hidden", false),
++ ("tool_only_span_suggestion", false),
++ ("multipart_suggestion", true),
++ ("multipart_suggestions", true),
++ ("tool_only_multipart_suggestion", true),
++ ("span_suggestions", true),
++];
++const SUGGESTION_FUNCTIONS: [&[&str]; 2] = [
++ &["clippy_utils", "diagnostics", "multispan_sugg"],
++ &["clippy_utils", "diagnostics", "multispan_sugg_with_applicability"],
++];
++
++/// The index of the applicability name of `paths::APPLICABILITY_VALUES`
++const APPLICABILITY_NAME_INDEX: usize = 2;
++
++declare_clippy_lint! {
++ /// **What it does:** Collects metadata about clippy lints for the website.
++ ///
++ /// This lint will be used to report problems of syntax parsing. You should hopefully never
++ /// see this but never say never I guess ^^
++ ///
++ /// **Why is this bad?** This is not a bad thing but definitely a hacky way to do it. See
++ /// issue [#4310](https://github.com/rust-lang/rust-clippy/issues/4310) for a discussion
++ /// about the implementation.
++ ///
++ /// **Known problems:** Hopefully none. It would be pretty uncool to have a problem here :)
++ ///
++ /// **Example output:**
++ /// ```json,ignore
++ /// {
++ /// "id": "internal_metadata_collector",
++ /// "id_span": {
++ /// "path": "clippy_lints/src/utils/internal_lints/metadata_collector.rs",
++ /// "line": 1
++ /// },
++ /// "group": "clippy::internal",
++ /// "docs": " **What it does:** Collects metadata about clippy lints for the website. [...] "
++ /// }
++ /// ```
++ pub INTERNAL_METADATA_COLLECTOR,
++ internal_warn,
++ "A busy bee collection metadata about lints"
++}
++
++impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]);
++
++#[allow(clippy::module_name_repetitions)]
++#[derive(Debug, Clone, Default)]
++pub struct MetadataCollector {
++ /// All collected lints
++ ///
++ /// We use a Heap here to have the lints added in alphabetic order in the export
++ lints: BinaryHeap<LintMetadata>,
++ applicability_info: FxHashMap<String, ApplicabilityInfo>,
++}
++
++impl Drop for MetadataCollector {
++ /// You might ask: How hacky is this?
++ /// My answer: YES
++ fn drop(&mut self) {
++ // The metadata collector gets dropped twice, this makes sure that we only write
++ // when the list is full
++ if self.lints.is_empty() {
++ return;
++ }
++
++ let mut applicability_info = std::mem::take(&mut self.applicability_info);
++
++ // Mapping the final data
++ let mut lints = std::mem::take(&mut self.lints).into_sorted_vec();
++ lints
++ .iter_mut()
++ .for_each(|x| x.applicability = applicability_info.remove(&x.id));
++
++ // Outputting
++ if Path::new(OUTPUT_FILE).exists() {
++ fs::remove_file(OUTPUT_FILE).unwrap();
++ }
++ let mut file = OpenOptions::new().write(true).create(true).open(OUTPUT_FILE).unwrap();
++ writeln!(file, "{}", serde_json::to_string_pretty(&lints).unwrap()).unwrap();
++ }
++}
++
++#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)]
++struct LintMetadata {
++ id: String,
++ id_span: SerializableSpan,
++ group: String,
++ docs: String,
++ /// This field is only used in the output and will only be
++ /// mapped shortly before the actual output.
++ applicability: Option<ApplicabilityInfo>,
++}
++
++impl LintMetadata {
++ fn new(id: String, id_span: SerializableSpan, group: String, docs: String) -> Self {
++ Self {
++ id,
++ id_span,
++ group,
++ docs,
++ applicability: None,
++ }
++ }
++}
++
++#[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 {
++ write!(f, "{}:{}", self.path.rsplit('/').next().unwrap_or_default(), self.line)
++ }
++}
++
++impl SerializableSpan {
++ fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Self {
++ Self::from_span(cx, item.ident.span)
++ }
++
++ fn from_span(cx: &LateContext<'_>, span: Span) -> Self {
++ let loc: Loc = cx.sess().source_map().lookup_char_pos(span.lo());
++
++ Self {
++ path: format!("{}", loc.file.name),
++ line: loc.line,
++ }
++ }
++}
++
++#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
++struct ApplicabilityInfo {
++ /// Indicates if any of the lint emissions uses multiple spans. This is related to
++ /// [rustfix#141](https://github.com/rust-lang/rustfix/issues/141) as such suggestions can
++ /// currently not be applied automatically.
++ is_multi_part_suggestion: bool,
++ applicability: Option<usize>,
++}
++
++impl Serialize for ApplicabilityInfo {
++ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
++ where
++ S: Serializer,
++ {
++ let index = self.applicability.unwrap_or_default();
++
++ let mut s = serializer.serialize_struct("ApplicabilityInfo", 2)?;
++ s.serialize_field("is_multi_part_suggestion", &self.is_multi_part_suggestion)?;
++ s.serialize_field(
++ "applicability",
++ &paths::APPLICABILITY_VALUES[index][APPLICABILITY_NAME_INDEX],
++ )?;
++ s.end()
++ }
++}
++
++impl<'hir> LateLintPass<'hir> for MetadataCollector {
++ /// Collecting lint declarations like:
++ /// ```rust, ignore
++ /// declare_clippy_lint! {
++ /// /// **What it does:** Something IDK.
++ /// pub SOME_LINT,
++ /// internal,
++ /// "Who am I?"
++ /// }
++ /// ```
++ fn check_item(&mut self, cx: &LateContext<'hir>, item: &'hir Item<'_>) {
++ if_chain! {
++ // item validation
++ if let ItemKind::Static(ref ty, Mutability::Not, _) = item.kind;
++ if is_lint_ref_type(cx, ty);
++ // blacklist check
++ let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase();
++ if !BLACK_LISTED_LINTS.contains(&lint_name.as_str());
++ // metadata extraction
++ if let Some(group) = get_lint_group_or_lint(cx, &lint_name, item);
++ if let Some(docs) = extract_attr_docs_or_lint(cx, item);
++ then {
++ self.lints.push(LintMetadata::new(
++ lint_name,
++ SerializableSpan::from_item(cx, item),
++ group,
++ docs,
++ ));
++ }
++ }
++ }
++
++ /// Collecting constant applicability from the actual lint emissions
++ ///
++ /// Example:
++ /// ```rust, ignore
++ /// span_lint_and_sugg(
++ /// cx,
++ /// SOME_LINT,
++ /// item.span,
++ /// "Le lint message",
++ /// "Here comes help:",
++ /// "#![allow(clippy::all)]",
++ /// Applicability::MachineApplicable, // <-- Extracts this constant value
++ /// );
++ /// ```
++ fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) {
++ if let Some(args) = match_lint_emission(cx, expr) {
++ let mut emission_info = extract_emission_info(cx, args);
++ if emission_info.is_empty() {
++ // See:
++ // - src/misc.rs:734:9
++ // - src/methods/mod.rs:3545:13
++ // - src/methods/mod.rs:3496:13
++ // We are basically unable to resolve the lint name it self.
++ return;
++ }
++
++ for (lint_name, applicability, is_multi_part) in emission_info.drain(..) {
++ let app_info = self.applicability_info.entry(lint_name).or_default();
++ app_info.applicability = applicability;
++ app_info.is_multi_part_suggestion = is_multi_part;
++ }
++ }
++ }
++}
++
++// ==================================================================
++// Lint definition extraction
++// ==================================================================
++fn sym_to_string(sym: Symbol) -> String {
++ sym.as_str().to_string()
++}
++
++fn extract_attr_docs_or_lint(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
++ extract_attr_docs(cx, item).or_else(|| {
++ lint_collection_error_item(cx, item, "could not collect the lint documentation");
++ None
++ })
++}
++
++/// This function collects all documentation that has been added to an item using
++/// `#[doc = r""]` attributes. Several attributes are aggravated using line breaks
++///
++/// ```ignore
++/// #[doc = r"Hello world!"]
++/// #[doc = r"=^.^="]
++/// struct SomeItem {}
++/// ```
++///
++/// Would result in `Hello world!\n=^.^=\n`
++fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
++ cx.tcx
++ .hir()
++ .attrs(item.hir_id())
++ .iter()
++ .filter_map(|ref x| x.doc_str().map(|sym| sym.as_str().to_string()))
++ .reduce(|mut acc, sym| {
++ acc.push_str(&sym);
++ acc.push('\n');
++ acc
++ })
++}
++
++fn get_lint_group_or_lint(cx: &LateContext<'_>, lint_name: &str, item: &'hir Item<'_>) -> Option<String> {
++ let result = cx.lint_store.check_lint_name(lint_name, Some(sym::clippy));
++ if let CheckLintNameResult::Tool(Ok(lint_lst)) = result {
++ get_lint_group(cx, lint_lst[0])
++ .or_else(|| {
++ lint_collection_error_item(cx, item, "Unable to determine lint group");
++ None
++ })
++ .filter(|group| !EXCLUDED_LINT_GROUPS.contains(&group.as_str()))
++ } else {
++ lint_collection_error_item(cx, item, "Unable to find lint in lint_store");
++ None
++ }
++}
++
++fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
++ for (group_name, lints, _) in &cx.lint_store.get_lint_groups() {
++ if IGNORED_LINT_GROUPS.contains(group_name) {
++ continue;
++ }
++
++ if lints.iter().any(|x| *x == lint_id) {
++ return Some((*group_name).to_string());
++ }
++ }
++
++ None
++}
++
++// ==================================================================
++// Lint emission
++// ==================================================================
++fn lint_collection_error_item(cx: &LateContext<'_>, item: &Item<'_>, message: &str) {
++ span_lint(
++ cx,
++ INTERNAL_METADATA_COLLECTOR,
++ item.ident.span,
++ &format!("metadata collection error for `{}`: {}", item.ident.name, message),
++ );
++}
++
++// ==================================================================
++// Applicability
++// ==================================================================
++/// This function checks if a given expression is equal to a simple lint emission function call.
++/// It will return the function arguments if the emission matched any function.
++fn match_lint_emission<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) -> Option<&'hir [hir::Expr<'hir>]> {
++ LINT_EMISSION_FUNCTIONS
++ .iter()
++ .find_map(|emission_fn| match_function_call(cx, expr, emission_fn))
++}
++
++fn take_higher_applicability(a: Option<usize>, b: Option<usize>) -> Option<usize> {
++ a.map_or(b, |a| a.max(b.unwrap_or_default()).into())
++}
++
++fn extract_emission_info<'hir>(
++ cx: &LateContext<'hir>,
++ args: &'hir [hir::Expr<'hir>],
++) -> Vec<(String, Option<usize>, bool)> {
++ let mut lints = Vec::new();
++ let mut applicability = None;
++ let mut multi_part = false;
++
++ for arg in args {
++ let (arg_ty, _) = walk_ptrs_ty_depth(cx.typeck_results().expr_ty(&arg));
++
++ if match_type(cx, arg_ty, &paths::LINT) {
++ // If we found the lint arg, extract the lint name
++ let mut resolved_lints = resolve_lints(cx, arg);
++ lints.append(&mut resolved_lints);
++ } else if match_type(cx, arg_ty, &paths::APPLICABILITY) {
++ applicability = resolve_applicability(cx, arg);
++ } else if arg_ty.is_closure() {
++ multi_part |= check_is_multi_part(cx, arg);
++ // TODO xFrednet 2021-03-01: don't use or_else but rather a comparison
++ applicability = applicability.or_else(|| resolve_applicability(cx, arg));
++ }
++ }
++
++ lints
++ .drain(..)
++ .map(|lint_name| (lint_name, applicability, multi_part))
++ .collect()
++}
++
++/// Resolves the possible lints that this expression could reference
++fn resolve_lints(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Vec<String> {
++ let mut resolver = LintResolver::new(cx);
++ resolver.visit_expr(expr);
++ resolver.lints
++}
++
++/// This function tries to resolve the linked applicability to the given expression.
++fn resolve_applicability(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<usize> {
++ let mut resolver = ApplicabilityResolver::new(cx);
++ resolver.visit_expr(expr);
++ resolver.complete()
++}
++
++fn check_is_multi_part(cx: &LateContext<'hir>, closure_expr: &'hir hir::Expr<'hir>) -> bool {
++ if let ExprKind::Closure(_, _, body_id, _, _) = closure_expr.kind {
++ let mut scanner = IsMultiSpanScanner::new(cx);
++ intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body_id));
++ return scanner.is_multi_part();
++ } else if let Some(local) = get_parent_local(cx, closure_expr) {
++ if let Some(local_init) = local.init {
++ return check_is_multi_part(cx, local_init);
++ }
++ }
++
++ false
++}
++
++struct LintResolver<'a, 'hir> {
++ cx: &'a LateContext<'hir>,
++ lints: Vec<String>,
++}
++
++impl<'a, 'hir> LintResolver<'a, 'hir> {
++ fn new(cx: &'a LateContext<'hir>) -> Self {
++ Self {
++ cx,
++ lints: Vec::<String>::default(),
++ }
++ }
++}
++
++impl<'a, 'hir> intravisit::Visitor<'hir> for LintResolver<'a, 'hir> {
++ type Map = Map<'hir>;
++
++ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
++ intravisit::NestedVisitorMap::All(self.cx.tcx.hir())
++ }
++
++ fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
++ if_chain! {
++ if let ExprKind::Path(qpath) = &expr.kind;
++ if let QPath::Resolved(_, path) = qpath;
++
++ let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(&expr));
++ if match_type(self.cx, expr_ty, &paths::LINT);
++ then {
++ if let hir::def::Res::Def(DefKind::Static, _) = path.res {
++ let lint_name = last_path_segment(qpath).ident.name;
++ self.lints.push(sym_to_string(lint_name).to_ascii_lowercase());
++ } else if let Some(local) = get_parent_local(self.cx, expr) {
++ if let Some(local_init) = local.init {
++ intravisit::walk_expr(self, local_init);
++ }
++ }
++ }
++ }
++
++ intravisit::walk_expr(self, expr);
++ }
++}
++
++/// This visitor finds the highest applicability value in the visited expressions
++struct ApplicabilityResolver<'a, 'hir> {
++ cx: &'a LateContext<'hir>,
++ /// This is the index of hightest `Applicability` for `paths::APPLICABILITY_VALUES`
++ applicability_index: Option<usize>,
++}
++
++impl<'a, 'hir> ApplicabilityResolver<'a, 'hir> {
++ fn new(cx: &'a LateContext<'hir>) -> Self {
++ Self {
++ cx,
++ applicability_index: None,
++ }
++ }
++
++ fn add_new_index(&mut self, new_index: usize) {
++ self.applicability_index = take_higher_applicability(self.applicability_index, Some(new_index));
++ }
++
++ fn complete(self) -> Option<usize> {
++ self.applicability_index
++ }
++}
++
++impl<'a, 'hir> intravisit::Visitor<'hir> for ApplicabilityResolver<'a, 'hir> {
++ type Map = Map<'hir>;
++
++ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
++ intravisit::NestedVisitorMap::All(self.cx.tcx.hir())
++ }
++
++ fn visit_path(&mut self, path: &'hir hir::Path<'hir>, _id: hir::HirId) {
++ for (index, enum_value) in paths::APPLICABILITY_VALUES.iter().enumerate() {
++ if match_path(path, enum_value) {
++ self.add_new_index(index);
++ return;
++ }
++ }
++ }
++
++ fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
++ let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(&expr));
++
++ if_chain! {
++ if match_type(self.cx, expr_ty, &paths::APPLICABILITY);
++ if let Some(local) = get_parent_local(self.cx, expr);
++ if let Some(local_init) = local.init;
++ then {
++ intravisit::walk_expr(self, local_init);
++ }
++ };
++
++ // TODO xFrednet 2021-03-01: support function arguments?
++
++ intravisit::walk_expr(self, expr);
++ }
++}
++
++/// This returns the parent local node if the expression is a reference one
++fn get_parent_local(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Local<'hir>> {
++ if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind {
++ if let hir::def::Res::Local(local_hir) = path.res {
++ return get_parent_local_hir_id(cx, local_hir);
++ }
++ }
++
++ None
++}
++
++fn get_parent_local_hir_id(cx: &LateContext<'hir>, hir_id: hir::HirId) -> Option<&'hir hir::Local<'hir>> {
++ let map = cx.tcx.hir();
++
++ match map.find(map.get_parent_node(hir_id)) {
++ Some(hir::Node::Local(local)) => Some(local),
++ Some(hir::Node::Pat(pattern)) => get_parent_local_hir_id(cx, pattern.hir_id),
++ _ => None,
++ }
++}
++
++/// This visitor finds the highest applicability value in the visited expressions
++struct IsMultiSpanScanner<'a, 'hir> {
++ cx: &'a LateContext<'hir>,
++ suggestion_count: usize,
++}
++
++impl<'a, 'hir> IsMultiSpanScanner<'a, 'hir> {
++ fn new(cx: &'a LateContext<'hir>) -> Self {
++ Self {
++ cx,
++ suggestion_count: 0,
++ }
++ }
++
++ /// Add a new single expression suggestion to the counter
++ fn add_single_span_suggestion(&mut self) {
++ self.suggestion_count += 1;
++ }
++
++ /// Signals that a suggestion with possible multiple spans was found
++ fn add_multi_part_suggestion(&mut self) {
++ self.suggestion_count += 2;
++ }
++
++ /// Checks if the suggestions include multiple spanns
++ fn is_multi_part(&self) -> bool {
++ self.suggestion_count > 1
++ }
++}
++
++impl<'a, 'hir> intravisit::Visitor<'hir> for IsMultiSpanScanner<'a, 'hir> {
++ type Map = Map<'hir>;
++
++ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
++ intravisit::NestedVisitorMap::All(self.cx.tcx.hir())
++ }
++
++ fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
++ // Early return if the lint is already multi span
++ if self.is_multi_part() {
++ return;
++ }
++
++ match &expr.kind {
++ ExprKind::Call(fn_expr, _args) => {
++ let found_function = SUGGESTION_FUNCTIONS
++ .iter()
++ .any(|func_path| match_function_call(self.cx, fn_expr, func_path).is_some());
++ if found_function {
++ // These functions are all multi part suggestions
++ self.add_single_span_suggestion()
++ }
++ },
++ ExprKind::MethodCall(path, _path_span, arg, _arg_span) => {
++ let (self_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(&arg[0]));
++ if match_type(self.cx, self_ty, &paths::DIAGNOSTIC_BUILDER) {
++ let called_method = path.ident.name.as_str().to_string();
++ for (method_name, is_multi_part) in &SUGGESTION_DIAGNOSTIC_BUILDER_METHODS {
++ if *method_name == called_method {
++ if *is_multi_part {
++ self.add_multi_part_suggestion();
++ } else {
++ self.add_single_span_suggestion();
++ }
++ break;
++ }
++ }
++ }
++ },
++ _ => {},
++ }
++
++ intravisit::walk_expr(self, expr);
++ }
++}
--- /dev/null
- #[cfg(feature = "internal-lints")]
+pub mod author;
+pub mod conf;
+pub mod inspector;
++#[cfg(any(feature = "internal-lints", feature = "metadata-collector-lint"))]
+pub mod internal_lints;
--- /dev/null
- version = "0.1.53"
+[package]
+name = "clippy_utils"
++version = "0.1.54"
+authors = ["The Rust Clippy Developers"]
+edition = "2018"
+publish = false
+
+[dependencies]
+if_chain = "1.0.0"
+itertools = "0.9"
+regex-syntax = "0.6"
+serde = { version = "1.0", features = ["derive"] }
+unicode-normalization = "0.1"
+rustc-semver="1.1.0"
+
+[features]
+internal-lints = []
++metadata-collector-lint = []
+
+[package.metadata.rust-analyzer]
+# This crate uses #[feature(rustc_private)]
+rustc_private = true
--- /dev/null
+//! Clippy wrappers around rustc's diagnostic functions.
++//!
++//! These functions are used by the `INTERNAL_METADATA_COLLECTOR` lint to collect the corresponding
++//! lint applicability. Please make sure that you update the `LINT_EMISSION_FUNCTIONS` variable in
++//! `clippy_lints::utils::internal_lints::metadata_collector` when a new function is added
++//! or renamed.
++//!
++//! Thank you!
++//! ~The `INTERNAL_METADATA_COLLECTOR` lint
+
+use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_hir::HirId;
+use rustc_lint::{LateContext, Lint, LintContext};
+use rustc_span::source_map::{MultiSpan, Span};
+use std::env;
+
+fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) {
+ if env::var("CLIPPY_DISABLE_DOCS_LINKS").is_err() {
+ if let Some(lint) = lint.name_lower().strip_prefix("clippy::") {
+ diag.help(&format!(
+ "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}",
+ &option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
+ // extract just major + minor version and ignore patch versions
+ format!("rust-{}", n.rsplitn(2, '.').nth(1).unwrap())
+ }),
+ lint
+ ));
+ }
+ }
+}
+
+/// Emit a basic lint message with a `msg` and a `span`.
+///
+/// This is the most primitive of our lint emission methods and can
+/// be a good way to get a new lint started.
+///
+/// Usually it's nicer to provide more context for lint messages.
+/// Be sure the output is understandable when you use this method.
+///
+/// # Example
+///
+/// ```ignore
+/// error: usage of mem::forget on Drop type
+/// --> $DIR/mem_forget.rs:17:5
+/// |
+/// 17 | std::mem::forget(seven);
+/// | ^^^^^^^^^^^^^^^^^^^^^^^
+/// ```
+pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<MultiSpan>, msg: &str) {
+ cx.struct_span_lint(lint, sp, |diag| {
+ let mut diag = diag.build(msg);
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+/// Same as `span_lint` but with an extra `help` message.
+///
+/// Use this if you want to provide some general help but
+/// can't provide a specific machine applicable suggestion.
+///
+/// The `help` message can be optionally attached to a `Span`.
+///
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+///
+/// # Example
+///
+/// ```ignore
+/// error: constant division of 0.0 with 0.0 will always result in NaN
+/// --> $DIR/zero_div_zero.rs:6:25
+/// |
+/// 6 | let other_f64_nan = 0.0f64 / 0.0;
+/// | ^^^^^^^^^^^^
+/// |
+/// = help: Consider using `f64::NAN` if you would like a constant representing NaN
+/// ```
+pub fn span_lint_and_help<'a, T: LintContext>(
+ cx: &'a T,
+ lint: &'static Lint,
+ span: Span,
+ msg: &str,
+ help_span: Option<Span>,
+ help: &str,
+) {
+ cx.struct_span_lint(lint, span, |diag| {
+ let mut diag = diag.build(msg);
+ if let Some(help_span) = help_span {
+ diag.span_help(help_span, help);
+ } else {
+ diag.help(help);
+ }
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+/// Like `span_lint` but with a `note` section instead of a `help` message.
+///
+/// The `note` message is presented separately from the main lint message
+/// and is attached to a specific span:
+///
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+///
+/// # Example
+///
+/// ```ignore
+/// error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing.
+/// --> $DIR/drop_forget_ref.rs:10:5
+/// |
+/// 10 | forget(&SomeStruct);
+/// | ^^^^^^^^^^^^^^^^^^^
+/// |
+/// = note: `-D clippy::forget-ref` implied by `-D warnings`
+/// note: argument has type &SomeStruct
+/// --> $DIR/drop_forget_ref.rs:10:12
+/// |
+/// 10 | forget(&SomeStruct);
+/// | ^^^^^^^^^^^
+/// ```
+pub fn span_lint_and_note<'a, T: LintContext>(
+ cx: &'a T,
+ lint: &'static Lint,
+ span: impl Into<MultiSpan>,
+ msg: &str,
+ note_span: Option<Span>,
+ note: &str,
+) {
+ cx.struct_span_lint(lint, span, |diag| {
+ let mut diag = diag.build(msg);
+ if let Some(note_span) = note_span {
+ diag.span_note(note_span, note);
+ } else {
+ diag.note(note);
+ }
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+/// Like `span_lint` but allows to add notes, help and suggestions using a closure.
+///
+/// If you need to customize your lint output a lot, use this function.
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+pub fn span_lint_and_then<C, S, F>(cx: &C, lint: &'static Lint, sp: S, msg: &str, f: F)
+where
+ C: LintContext,
+ S: Into<MultiSpan>,
+ F: FnOnce(&mut DiagnosticBuilder<'_>),
+{
+ cx.struct_span_lint(lint, sp, |diag| {
+ let mut diag = diag.build(msg);
+ f(&mut diag);
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) {
+ cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
+ let mut diag = diag.build(msg);
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+pub fn span_lint_hir_and_then(
+ cx: &LateContext<'_>,
+ lint: &'static Lint,
+ hir_id: HirId,
+ sp: Span,
+ msg: &str,
+ f: impl FnOnce(&mut DiagnosticBuilder<'_>),
+) {
+ cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
+ let mut diag = diag.build(msg);
+ f(&mut diag);
+ docs_link(&mut diag, lint);
+ diag.emit();
+ });
+}
+
+/// Add a span lint with a suggestion on how to fix it.
+///
+/// These suggestions can be parsed by rustfix to allow it to automatically fix your code.
+/// In the example below, `help` is `"try"` and `sugg` is the suggested replacement `".any(|x| x >
+/// 2)"`.
+///
+/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
+///
+/// # Example
+///
+/// ```ignore
+/// error: This `.fold` can be more succinctly expressed as `.any`
+/// --> $DIR/methods.rs:390:13
+/// |
+/// 390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2);
+/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`
+/// |
+/// = note: `-D fold-any` implied by `-D warnings`
+/// ```
+#[cfg_attr(feature = "internal-lints", allow(clippy::collapsible_span_lint_calls))]
+pub fn span_lint_and_sugg<'a, T: LintContext>(
+ cx: &'a T,
+ lint: &'static Lint,
+ sp: Span,
+ msg: &str,
+ help: &str,
+ sugg: String,
+ applicability: Applicability,
+) {
+ span_lint_and_then(cx, lint, sp, msg, |diag| {
+ diag.span_suggestion(sp, help, sugg, applicability);
+ });
+}
+
+/// Create a suggestion made from several `span → replacement`.
+///
+/// Note: in the JSON format (used by `compiletest_rs`), the help message will
+/// appear once per
+/// replacement. In human-readable format though, it only appears once before
+/// the whole suggestion.
+pub fn multispan_sugg<I>(diag: &mut DiagnosticBuilder<'_>, help_msg: &str, sugg: I)
+where
+ I: IntoIterator<Item = (Span, String)>,
+{
+ multispan_sugg_with_applicability(diag, help_msg, Applicability::Unspecified, sugg)
+}
+
+/// Create a suggestion made from several `span → replacement`.
+///
+/// rustfix currently doesn't support the automatic application of suggestions with
+/// multiple spans. This is tracked in issue [rustfix#141](https://github.com/rust-lang/rustfix/issues/141).
+/// Suggestions with multiple spans will be silently ignored.
+pub fn multispan_sugg_with_applicability<I>(
+ diag: &mut DiagnosticBuilder<'_>,
+ help_msg: &str,
+ applicability: Applicability,
+ sugg: I,
+) where
+ I: IntoIterator<Item = (Span, String)>,
+{
+ diag.multipart_suggestion(help_msg, sugg.into_iter().collect(), applicability);
+}
--- /dev/null
- // TODO: arm.pat?
+use crate::consts::{constant_context, constant_simple};
+use crate::differing_macro_contexts;
+use crate::source::snippet_opt;
+use rustc_ast::ast::InlineAsmTemplatePiece;
+use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
+use rustc_hir::def::Res;
+use rustc_hir::HirIdMap;
+use rustc_hir::{
+ BinOpKind, Block, BlockCheckMode, BodyId, BorrowKind, CaptureBy, Expr, ExprField, ExprKind, FnRetTy, GenericArg,
+ GenericArgs, Guard, HirId, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatField, PatKind, Path,
+ PathSegment, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding,
+};
+use rustc_lexer::{tokenize, TokenKind};
+use rustc_lint::LateContext;
+use rustc_middle::ich::StableHashingContextProvider;
+use rustc_middle::ty::TypeckResults;
+use rustc_span::Symbol;
+use std::hash::Hash;
+
+/// Type used to check whether two ast are the same. This is different from the
+/// operator
+/// `==` on ast types as this operator would compare true equality with ID and
+/// span.
+///
+/// Note that some expressions kinds are not considered but could be added.
+pub struct SpanlessEq<'a, 'tcx> {
+ /// Context used to evaluate constant expressions.
+ cx: &'a LateContext<'tcx>,
+ maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
+ allow_side_effects: bool,
+ expr_fallback: Option<Box<dyn FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a>>,
+}
+
+impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
+ pub fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ maybe_typeck_results: cx.maybe_typeck_results(),
+ allow_side_effects: true,
+ expr_fallback: None,
+ }
+ }
+
+ /// Consider expressions containing potential side effects as not equal.
+ pub fn deny_side_effects(self) -> Self {
+ Self {
+ allow_side_effects: false,
+ ..self
+ }
+ }
+
+ pub fn expr_fallback(self, expr_fallback: impl FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a) -> Self {
+ Self {
+ expr_fallback: Some(Box::new(expr_fallback)),
+ ..self
+ }
+ }
+
+ /// Use this method to wrap comparisons that may involve inter-expression context.
+ /// See `self.locals`.
+ pub fn inter_expr(&mut self) -> HirEqInterExpr<'_, 'a, 'tcx> {
+ HirEqInterExpr {
+ inner: self,
+ locals: HirIdMap::default(),
+ }
+ }
+
+ #[allow(dead_code)]
+ pub fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
+ self.inter_expr().eq_block(left, right)
+ }
+
+ pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
+ self.inter_expr().eq_expr(left, right)
+ }
+
+ pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
+ self.inter_expr().eq_path_segment(left, right)
+ }
+
+ pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
+ self.inter_expr().eq_path_segments(left, right)
+ }
+}
+
+pub struct HirEqInterExpr<'a, 'b, 'tcx> {
+ inner: &'a mut SpanlessEq<'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<HirId>,
+}
+
+impl HirEqInterExpr<'_, '_, '_> {
+ pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (&StmtKind::Local(ref l), &StmtKind::Local(ref r)) => {
+ // This additional check ensures that the type of the locals are equivalent even if the init
+ // expression or type have some inferred parts.
+ if let Some(typeck) = self.inner.maybe_typeck_results {
+ let l_ty = typeck.pat_ty(&l.pat);
+ let r_ty = typeck.pat_ty(&r.pat);
+ if !rustc_middle::ty::TyS::same_type(l_ty, r_ty) {
+ return false;
+ }
+ }
+
+ // eq_pat adds the HirIds to the locals map. We therefor call it last to make sure that
+ // these only get added if the init and type is equal.
+ both(&l.init, &r.init, |l, r| self.eq_expr(l, r))
+ && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r))
+ && self.eq_pat(&l.pat, &r.pat)
+ },
+ (&StmtKind::Expr(ref l), &StmtKind::Expr(ref r)) | (&StmtKind::Semi(ref l), &StmtKind::Semi(ref r)) => {
+ self.eq_expr(l, r)
+ },
+ _ => false,
+ }
+ }
+
+ /// Checks whether two blocks are the same.
+ fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
+ match (left.stmts, left.expr, right.stmts, right.expr) {
+ ([], None, [], None) => {
+ // For empty blocks, check to see if the tokens are equal. This will catch the case where a macro
+ // expanded to nothing, or the cfg attribute was used.
+ let (left, right) = match (
+ snippet_opt(self.inner.cx, left.span),
+ snippet_opt(self.inner.cx, right.span),
+ ) {
+ (Some(left), Some(right)) => (left, right),
+ _ => return true,
+ };
+ let mut left_pos = 0;
+ let left = tokenize(&left)
+ .map(|t| {
+ let end = left_pos + t.len;
+ let s = &left[left_pos..end];
+ left_pos = end;
+ (t, s)
+ })
+ .filter(|(t, _)| {
+ !matches!(
+ t.kind,
+ TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
+ )
+ })
+ .map(|(_, s)| s);
+ let mut right_pos = 0;
+ let right = tokenize(&right)
+ .map(|t| {
+ let end = right_pos + t.len;
+ let s = &right[right_pos..end];
+ right_pos = end;
+ (t, s)
+ })
+ .filter(|(t, _)| {
+ !matches!(
+ t.kind,
+ TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
+ )
+ })
+ .map(|(_, s)| s);
+ left.eq(right)
+ },
+ _ => {
+ over(&left.stmts, &right.stmts, |l, r| self.eq_stmt(l, r))
+ && both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
+ },
+ }
+ }
+
+ #[allow(clippy::similar_names)]
+ pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
+ if !self.inner.allow_side_effects && differing_macro_contexts(left.span, right.span) {
+ return false;
+ }
+
+ if let Some(typeck_results) = self.inner.maybe_typeck_results {
+ if let (Some(l), Some(r)) = (
+ constant_simple(self.inner.cx, typeck_results, left),
+ constant_simple(self.inner.cx, typeck_results, right),
+ ) {
+ if l == r {
+ return true;
+ }
+ }
+ }
+
+ let is_eq = match (
+ &reduce_exprkind(self.inner.cx, &left.kind),
+ &reduce_exprkind(self.inner.cx, &right.kind),
+ ) {
+ (&ExprKind::AddrOf(lb, l_mut, ref le), &ExprKind::AddrOf(rb, r_mut, ref re)) => {
+ lb == rb && l_mut == r_mut && self.eq_expr(le, re)
+ },
+ (&ExprKind::Continue(li), &ExprKind::Continue(ri)) => {
+ both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
+ },
+ (&ExprKind::Assign(ref ll, ref lr, _), &ExprKind::Assign(ref rl, ref rr, _)) => {
+ self.inner.allow_side_effects && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ },
+ (&ExprKind::AssignOp(ref lo, ref ll, ref lr), &ExprKind::AssignOp(ref ro, ref rl, ref rr)) => {
+ self.inner.allow_side_effects && lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ },
+ (&ExprKind::Block(ref l, _), &ExprKind::Block(ref r, _)) => self.eq_block(l, r),
+ (&ExprKind::Binary(l_op, ref ll, ref lr), &ExprKind::Binary(r_op, ref rl, ref rr)) => {
+ l_op.node == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ || swap_binop(l_op.node, ll, lr).map_or(false, |(l_op, ll, lr)| {
+ l_op == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
+ })
+ },
+ (&ExprKind::Break(li, ref le), &ExprKind::Break(ri, ref re)) => {
+ both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
+ && both(le, re, |l, r| self.eq_expr(l, r))
+ },
+ (&ExprKind::Box(ref l), &ExprKind::Box(ref r)) => self.eq_expr(l, r),
+ (&ExprKind::Call(l_fun, l_args), &ExprKind::Call(r_fun, r_args)) => {
+ self.inner.allow_side_effects && self.eq_expr(l_fun, r_fun) && self.eq_exprs(l_args, r_args)
+ },
+ (&ExprKind::Cast(ref lx, ref lt), &ExprKind::Cast(ref rx, ref rt))
+ | (&ExprKind::Type(ref lx, ref lt), &ExprKind::Type(ref rx, ref rt)) => {
+ self.eq_expr(lx, rx) && self.eq_ty(lt, rt)
+ },
+ (&ExprKind::Field(ref l_f_exp, ref l_f_ident), &ExprKind::Field(ref r_f_exp, ref r_f_ident)) => {
+ l_f_ident.name == r_f_ident.name && self.eq_expr(l_f_exp, r_f_exp)
+ },
+ (&ExprKind::Index(ref la, ref li), &ExprKind::Index(ref ra, ref ri)) => {
+ self.eq_expr(la, ra) && self.eq_expr(li, ri)
+ },
+ (&ExprKind::If(ref lc, ref lt, ref le), &ExprKind::If(ref rc, ref rt, ref re)) => {
+ self.eq_expr(lc, rc) && self.eq_expr(&**lt, &**rt) && both(le, re, |l, r| self.eq_expr(l, r))
+ },
+ (&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node,
+ (&ExprKind::Loop(ref lb, ref ll, ref lls, _), &ExprKind::Loop(ref rb, ref rl, ref rls, _)) => {
+ lls == rls && self.eq_block(lb, rb) && both(ll, rl, |l, r| l.ident.name == r.ident.name)
+ },
+ (&ExprKind::Match(ref le, ref la, ref ls), &ExprKind::Match(ref re, ref ra, ref rs)) => {
+ ls == rs
+ && self.eq_expr(le, re)
+ && over(la, ra, |l, r| {
+ self.eq_pat(&l.pat, &r.pat)
+ && both(&l.guard, &r.guard, |l, r| self.eq_guard(l, r))
+ && self.eq_expr(&l.body, &r.body)
+ })
+ },
+ (&ExprKind::MethodCall(l_path, _, l_args, _), &ExprKind::MethodCall(r_path, _, r_args, _)) => {
+ self.inner.allow_side_effects && self.eq_path_segment(l_path, r_path) && self.eq_exprs(l_args, r_args)
+ },
+ (&ExprKind::Repeat(ref le, ref ll_id), &ExprKind::Repeat(ref re, ref rl_id)) => {
+ let mut celcx = constant_context(self.inner.cx, self.inner.cx.tcx.typeck_body(ll_id.body));
+ let ll = celcx.expr(&self.inner.cx.tcx.hir().body(ll_id.body).value);
+ let mut celcx = constant_context(self.inner.cx, self.inner.cx.tcx.typeck_body(rl_id.body));
+ let rl = celcx.expr(&self.inner.cx.tcx.hir().body(rl_id.body).value);
+
+ self.eq_expr(le, re) && ll == rl
+ },
+ (&ExprKind::Ret(ref l), &ExprKind::Ret(ref r)) => both(l, r, |l, r| self.eq_expr(l, r)),
+ (&ExprKind::Path(ref l), &ExprKind::Path(ref r)) => self.eq_qpath(l, r),
+ (&ExprKind::Struct(ref l_path, ref lf, ref lo), &ExprKind::Struct(ref r_path, ref rf, ref ro)) => {
+ self.eq_qpath(l_path, r_path)
+ && both(lo, ro, |l, r| self.eq_expr(l, r))
+ && over(lf, rf, |l, r| self.eq_expr_field(l, r))
+ },
+ (&ExprKind::Tup(l_tup), &ExprKind::Tup(r_tup)) => self.eq_exprs(l_tup, r_tup),
+ (&ExprKind::Unary(l_op, ref le), &ExprKind::Unary(r_op, ref re)) => l_op == r_op && self.eq_expr(le, re),
+ (&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r),
+ (&ExprKind::DropTemps(ref le), &ExprKind::DropTemps(ref re)) => self.eq_expr(le, re),
+ _ => false,
+ };
+ is_eq || self.inner.expr_fallback.as_mut().map_or(false, |f| f(left, right))
+ }
+
+ fn eq_exprs(&mut self, left: &[Expr<'_>], right: &[Expr<'_>]) -> bool {
+ over(left, right, |l, r| self.eq_expr(l, r))
+ }
+
+ fn eq_expr_field(&mut self, left: &ExprField<'_>, right: &ExprField<'_>) -> bool {
+ left.ident.name == right.ident.name && self.eq_expr(&left.expr, &right.expr)
+ }
+
+ fn eq_guard(&mut self, left: &Guard<'_>, right: &Guard<'_>) -> bool {
+ match (left, right) {
+ (Guard::If(l), Guard::If(r)) => self.eq_expr(l, r),
+ (Guard::IfLet(lp, le), Guard::IfLet(rp, re)) => self.eq_pat(lp, rp) && self.eq_expr(le, re),
+ _ => false,
+ }
+ }
+
+ fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> bool {
+ match (left, right) {
+ (GenericArg::Lifetime(l_lt), GenericArg::Lifetime(r_lt)) => Self::eq_lifetime(l_lt, r_lt),
+ (GenericArg::Type(l_ty), GenericArg::Type(r_ty)) => self.eq_ty(l_ty, r_ty),
+ _ => false,
+ }
+ }
+
+ fn eq_lifetime(left: &Lifetime, right: &Lifetime) -> bool {
+ left.name == right.name
+ }
+
+ fn eq_pat_field(&mut self, left: &PatField<'_>, right: &PatField<'_>) -> bool {
+ let (PatField { ident: li, pat: lp, .. }, PatField { ident: ri, pat: rp, .. }) = (&left, &right);
+ li.name == ri.name && self.eq_pat(lp, rp)
+ }
+
+ /// Checks whether two patterns are the same.
+ fn eq_pat(&mut self, left: &Pat<'_>, right: &Pat<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (&PatKind::Box(ref l), &PatKind::Box(ref r)) => self.eq_pat(l, r),
+ (&PatKind::Struct(ref lp, ref la, ..), &PatKind::Struct(ref rp, ref ra, ..)) => {
+ self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat_field(l, r))
+ },
+ (&PatKind::TupleStruct(ref lp, ref la, ls), &PatKind::TupleStruct(ref rp, ref ra, rs)) => {
+ self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat(l, r)) && ls == rs
+ },
+ (&PatKind::Binding(lb, li, _, ref lp), &PatKind::Binding(rb, ri, _, ref rp)) => {
+ let eq = lb == rb && both(lp, rp, |l, r| self.eq_pat(l, r));
+ if eq {
+ self.locals.insert(li, ri);
+ }
+ eq
+ },
+ (&PatKind::Path(ref l), &PatKind::Path(ref r)) => self.eq_qpath(l, r),
+ (&PatKind::Lit(ref l), &PatKind::Lit(ref r)) => self.eq_expr(l, r),
+ (&PatKind::Tuple(ref l, ls), &PatKind::Tuple(ref r, rs)) => {
+ ls == rs && over(l, r, |l, r| self.eq_pat(l, r))
+ },
+ (&PatKind::Range(ref ls, ref le, li), &PatKind::Range(ref rs, ref re, ri)) => {
+ both(ls, rs, |a, b| self.eq_expr(a, b)) && both(le, re, |a, b| self.eq_expr(a, b)) && (li == ri)
+ },
+ (&PatKind::Ref(ref le, ref lm), &PatKind::Ref(ref re, ref rm)) => lm == rm && self.eq_pat(le, re),
+ (&PatKind::Slice(ref ls, ref li, ref le), &PatKind::Slice(ref rs, ref ri, ref re)) => {
+ over(ls, rs, |l, r| self.eq_pat(l, r))
+ && over(le, re, |l, r| self.eq_pat(l, r))
+ && both(li, ri, |l, r| self.eq_pat(l, r))
+ },
+ (&PatKind::Wild, &PatKind::Wild) => true,
+ _ => false,
+ }
+ }
+
+ #[allow(clippy::similar_names)]
+ fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool {
+ match (left, right) {
+ (&QPath::Resolved(ref lty, ref lpath), &QPath::Resolved(ref rty, ref rpath)) => {
+ both(lty, rty, |l, r| self.eq_ty(l, r)) && self.eq_path(lpath, rpath)
+ },
+ (&QPath::TypeRelative(ref lty, ref lseg), &QPath::TypeRelative(ref rty, ref rseg)) => {
+ self.eq_ty(lty, rty) && self.eq_path_segment(lseg, rseg)
+ },
+ (&QPath::LangItem(llang_item, _), &QPath::LangItem(rlang_item, _)) => llang_item == rlang_item,
+ _ => false,
+ }
+ }
+
+ fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
+ match (left.res, right.res) {
+ (Res::Local(l), Res::Local(r)) => l == r || self.locals.get(&l) == Some(&r),
+ (Res::Local(_), _) | (_, Res::Local(_)) => false,
+ _ => over(&left.segments, &right.segments, |l, r| self.eq_path_segment(l, r)),
+ }
+ }
+
+ fn eq_path_parameters(&mut self, left: &GenericArgs<'_>, right: &GenericArgs<'_>) -> bool {
+ if !(left.parenthesized || right.parenthesized) {
+ over(&left.args, &right.args, |l, r| self.eq_generic_arg(l, r)) // FIXME(flip1995): may not work
+ && over(&left.bindings, &right.bindings, |l, r| self.eq_type_binding(l, r))
+ } else if left.parenthesized && right.parenthesized {
+ over(left.inputs(), right.inputs(), |l, r| self.eq_ty(l, r))
+ && both(&Some(&left.bindings[0].ty()), &Some(&right.bindings[0].ty()), |l, r| {
+ self.eq_ty(l, r)
+ })
+ } else {
+ false
+ }
+ }
+
+ pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
+ left.len() == right.len() && left.iter().zip(right).all(|(l, r)| self.eq_path_segment(l, r))
+ }
+
+ pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
+ // The == of idents doesn't work with different contexts,
+ // we have to be explicit about hygiene
+ left.ident.name == right.ident.name && both(&left.args, &right.args, |l, r| self.eq_path_parameters(l, r))
+ }
+
+ #[allow(clippy::similar_names)]
+ fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool {
+ match (&left.kind, &right.kind) {
+ (&TyKind::Slice(ref l_vec), &TyKind::Slice(ref r_vec)) => self.eq_ty(l_vec, r_vec),
+ (&TyKind::Array(ref lt, ref ll_id), &TyKind::Array(ref rt, ref rl_id)) => {
+ let cx = self.inner.cx;
+ let eval_const =
+ |body| constant_context(cx, cx.tcx.typeck_body(body)).expr(&cx.tcx.hir().body(body).value);
+ self.eq_ty(lt, rt) && eval_const(ll_id.body) == eval_const(rl_id.body)
+ },
+ (&TyKind::Ptr(ref l_mut), &TyKind::Ptr(ref r_mut)) => {
+ l_mut.mutbl == r_mut.mutbl && self.eq_ty(&*l_mut.ty, &*r_mut.ty)
+ },
+ (&TyKind::Rptr(_, ref l_rmut), &TyKind::Rptr(_, ref r_rmut)) => {
+ l_rmut.mutbl == r_rmut.mutbl && self.eq_ty(&*l_rmut.ty, &*r_rmut.ty)
+ },
+ (&TyKind::Path(ref l), &TyKind::Path(ref r)) => self.eq_qpath(l, r),
+ (&TyKind::Tup(ref l), &TyKind::Tup(ref r)) => over(l, r, |l, r| self.eq_ty(l, r)),
+ (&TyKind::Infer, &TyKind::Infer) => true,
+ _ => false,
+ }
+ }
+
+ fn eq_type_binding(&mut self, left: &TypeBinding<'_>, right: &TypeBinding<'_>) -> bool {
+ left.ident.name == right.ident.name && self.eq_ty(&left.ty(), &right.ty())
+ }
+}
+
+/// Some simple reductions like `{ return }` => `return`
+fn reduce_exprkind<'hir>(cx: &LateContext<'_>, kind: &'hir ExprKind<'hir>) -> &'hir ExprKind<'hir> {
+ if let ExprKind::Block(block, _) = kind {
+ match (block.stmts, block.expr) {
+ // From an `if let` expression without an `else` block. The arm for the implicit wild pattern is an empty
+ // block with an empty span.
+ ([], None) if block.span.is_empty() => &ExprKind::Tup(&[]),
+ // `{}` => `()`
+ ([], None) => match snippet_opt(cx, block.span) {
+ // Don't reduce if there are any tokens contained in the braces
+ Some(snip)
+ if tokenize(&snip)
+ .map(|t| t.kind)
+ .filter(|t| {
+ !matches!(
+ t,
+ TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
+ )
+ })
+ .ne([TokenKind::OpenBrace, TokenKind::CloseBrace].iter().copied()) =>
+ {
+ kind
+ },
+ _ => &ExprKind::Tup(&[]),
+ },
+ ([], Some(expr)) => match expr.kind {
+ // `{ return .. }` => `return ..`
+ ExprKind::Ret(..) => &expr.kind,
+ _ => kind,
+ },
+ ([stmt], None) => match stmt.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
+ // `{ return ..; }` => `return ..`
+ ExprKind::Ret(..) => &expr.kind,
+ _ => kind,
+ },
+ _ => kind,
+ },
+ _ => kind,
+ }
+ } else {
+ kind
+ }
+}
+
+fn swap_binop<'a>(
+ binop: BinOpKind,
+ lhs: &'a Expr<'a>,
+ rhs: &'a Expr<'a>,
+) -> Option<(BinOpKind, &'a Expr<'a>, &'a Expr<'a>)> {
+ match binop {
+ BinOpKind::Add | BinOpKind::Eq | BinOpKind::Ne | BinOpKind::BitAnd | BinOpKind::BitXor | BinOpKind::BitOr => {
+ Some((binop, rhs, lhs))
+ },
+ BinOpKind::Lt => Some((BinOpKind::Gt, rhs, lhs)),
+ BinOpKind::Le => Some((BinOpKind::Ge, rhs, lhs)),
+ BinOpKind::Ge => Some((BinOpKind::Le, rhs, lhs)),
+ BinOpKind::Gt => Some((BinOpKind::Lt, rhs, lhs)),
+ BinOpKind::Mul // Not always commutative, e.g. with matrices. See issue #5698
+ | BinOpKind::Shl
+ | BinOpKind::Shr
+ | BinOpKind::Rem
+ | BinOpKind::Sub
+ | BinOpKind::Div
+ | BinOpKind::And
+ | BinOpKind::Or => None,
+ }
+}
+
+/// Checks if the two `Option`s are both `None` or some equal values as per
+/// `eq_fn`.
+pub fn both<X>(l: &Option<X>, r: &Option<X>, mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
+ l.as_ref()
+ .map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y)))
+}
+
+/// Checks if two slices are equal as per `eq_fn`.
+pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
+ left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
+}
+
+/// Counts how many elements of the slices are equal as per `eq_fn`.
+pub fn count_eq<X: Sized>(
+ left: &mut dyn Iterator<Item = X>,
+ right: &mut dyn Iterator<Item = X>,
+ mut eq_fn: impl FnMut(&X, &X) -> bool,
+) -> usize {
+ left.zip(right).take_while(|(l, r)| eq_fn(l, r)).count()
+}
+
+/// Checks if two expressions evaluate to the same value, and don't contain any side effects.
+pub fn eq_expr_value(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>) -> bool {
+ SpanlessEq::new(cx).deny_side_effects().eq_expr(left, right)
+}
+
+/// Type used to hash an ast element. This is different from the `Hash` trait
+/// on ast types as this
+/// trait would consider IDs and spans.
+///
+/// All expressions kind are hashed, but some might have a weaker hash.
+pub struct SpanlessHash<'a, 'tcx> {
+ /// Context used to evaluate constant expressions.
+ cx: &'a LateContext<'tcx>,
+ maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
+ s: StableHasher,
+}
+
+impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
+ pub fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ maybe_typeck_results: cx.maybe_typeck_results(),
+ s: StableHasher::new(),
+ }
+ }
+
+ pub fn finish(self) -> u64 {
+ self.s.finish()
+ }
+
+ pub fn hash_block(&mut self, b: &Block<'_>) {
+ for s in b.stmts {
+ self.hash_stmt(s);
+ }
+
+ if let Some(ref e) = b.expr {
+ self.hash_expr(e);
+ }
+
+ match b.rules {
+ BlockCheckMode::DefaultBlock => 0,
+ BlockCheckMode::UnsafeBlock(_) => 1,
+ BlockCheckMode::PushUnsafeBlock(_) => 2,
+ BlockCheckMode::PopUnsafeBlock(_) => 3,
+ }
+ .hash(&mut self.s);
+ }
+
+ #[allow(clippy::many_single_char_names, clippy::too_many_lines)]
+ pub fn hash_expr(&mut self, e: &Expr<'_>) {
+ let simple_const = self
+ .maybe_typeck_results
+ .and_then(|typeck_results| constant_simple(self.cx, typeck_results, e));
+
+ // const hashing may result in the same hash as some unrelated node, so add a sort of
+ // discriminant depending on which path we're choosing next
+ simple_const.is_some().hash(&mut self.s);
+
+ if let Some(e) = simple_const {
+ return e.hash(&mut self.s);
+ }
+
+ std::mem::discriminant(&e.kind).hash(&mut self.s);
+
+ match e.kind {
+ ExprKind::AddrOf(kind, m, ref e) => {
+ match kind {
+ BorrowKind::Ref => 0,
+ BorrowKind::Raw => 1,
+ }
+ .hash(&mut self.s);
+ m.hash(&mut self.s);
+ self.hash_expr(e);
+ },
+ ExprKind::Continue(i) => {
+ if let Some(i) = i.label {
+ self.hash_name(i.ident.name);
+ }
+ },
+ ExprKind::Assign(ref l, ref r, _) => {
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::AssignOp(ref o, ref l, ref r) => {
+ o.node
+ .hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::Block(ref b, _) => {
+ self.hash_block(b);
+ },
+ ExprKind::Binary(op, ref l, ref r) => {
+ op.node
+ .hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
+ self.hash_expr(l);
+ self.hash_expr(r);
+ },
+ ExprKind::Break(i, ref j) => {
+ if let Some(i) = i.label {
+ self.hash_name(i.ident.name);
+ }
+ if let Some(ref j) = *j {
+ self.hash_expr(&*j);
+ }
+ },
+ ExprKind::Box(ref e) | ExprKind::DropTemps(ref e) | ExprKind::Yield(ref e, _) => {
+ self.hash_expr(e);
+ },
+ ExprKind::Call(ref fun, args) => {
+ self.hash_expr(fun);
+ self.hash_exprs(args);
+ },
+ ExprKind::Cast(ref e, ref ty) | ExprKind::Type(ref e, ref ty) => {
+ self.hash_expr(e);
+ self.hash_ty(ty);
+ },
+ ExprKind::Closure(cap, _, eid, _, _) => {
+ match cap {
+ CaptureBy::Value => 0,
+ CaptureBy::Ref => 1,
+ }
+ .hash(&mut self.s);
+ // closures inherit TypeckResults
+ self.hash_expr(&self.cx.tcx.hir().body(eid).value);
+ },
+ ExprKind::Field(ref e, ref f) => {
+ self.hash_expr(e);
+ self.hash_name(f.name);
+ },
+ ExprKind::Index(ref a, ref i) => {
+ self.hash_expr(a);
+ self.hash_expr(i);
+ },
+ ExprKind::InlineAsm(ref asm) => {
+ for piece in asm.template {
+ match piece {
+ InlineAsmTemplatePiece::String(s) => s.hash(&mut self.s),
+ InlineAsmTemplatePiece::Placeholder {
+ operand_idx,
+ modifier,
+ span: _,
+ } => {
+ operand_idx.hash(&mut self.s);
+ modifier.hash(&mut self.s);
+ },
+ }
+ }
+ asm.options.hash(&mut self.s);
+ for (op, _op_sp) in asm.operands {
+ match op {
+ InlineAsmOperand::In { reg, expr } => {
+ reg.hash(&mut self.s);
+ self.hash_expr(expr);
+ },
+ InlineAsmOperand::Out { reg, late, expr } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ if let Some(expr) = expr {
+ self.hash_expr(expr);
+ }
+ },
+ InlineAsmOperand::InOut { reg, late, expr } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ self.hash_expr(expr);
+ },
+ InlineAsmOperand::SplitInOut {
+ reg,
+ late,
+ in_expr,
+ out_expr,
+ } => {
+ reg.hash(&mut self.s);
+ late.hash(&mut self.s);
+ self.hash_expr(in_expr);
+ if let Some(out_expr) = out_expr {
+ self.hash_expr(out_expr);
+ }
+ },
+ InlineAsmOperand::Const { anon_const } => self.hash_body(anon_const.body),
+ InlineAsmOperand::Sym { expr } => self.hash_expr(expr),
+ }
+ }
+ },
+ ExprKind::LlvmInlineAsm(..) | ExprKind::Err => {},
+ ExprKind::Lit(ref l) => {
+ l.node.hash(&mut self.s);
+ },
+ ExprKind::Loop(ref b, ref i, ..) => {
+ self.hash_block(b);
+ if let Some(i) = *i {
+ self.hash_name(i.ident.name);
+ }
+ },
+ ExprKind::If(ref cond, ref then, ref else_opt) => {
+ let c: fn(_, _, _) -> _ = ExprKind::If;
+ c.hash(&mut self.s);
+ self.hash_expr(cond);
+ self.hash_expr(&**then);
+ if let Some(ref e) = *else_opt {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Match(ref e, arms, ref s) => {
+ self.hash_expr(e);
+
+ for arm in arms {
- pub fn hash_lifetime(&mut self, lifetime: &Lifetime) {
++ self.hash_pat(arm.pat);
+ if let Some(ref e) = arm.guard {
+ self.hash_guard(e);
+ }
+ self.hash_expr(&arm.body);
+ }
+
+ s.hash(&mut self.s);
+ },
+ ExprKind::MethodCall(ref path, ref _tys, args, ref _fn_span) => {
+ self.hash_name(path.ident.name);
+ self.hash_exprs(args);
+ },
+ ExprKind::ConstBlock(ref l_id) => {
+ self.hash_body(l_id.body);
+ },
+ ExprKind::Repeat(ref e, ref l_id) => {
+ self.hash_expr(e);
+ self.hash_body(l_id.body);
+ },
+ ExprKind::Ret(ref e) => {
+ if let Some(ref e) = *e {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Path(ref qpath) => {
+ self.hash_qpath(qpath);
+ },
+ ExprKind::Struct(ref path, fields, ref expr) => {
+ self.hash_qpath(path);
+
+ for f in fields {
+ self.hash_name(f.ident.name);
+ self.hash_expr(&f.expr);
+ }
+
+ if let Some(ref e) = *expr {
+ self.hash_expr(e);
+ }
+ },
+ ExprKind::Tup(tup) => {
+ self.hash_exprs(tup);
+ },
+ ExprKind::Array(v) => {
+ self.hash_exprs(v);
+ },
+ ExprKind::Unary(lop, ref le) => {
+ lop.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
+ self.hash_expr(le);
+ },
+ }
+ }
+
+ pub fn hash_exprs(&mut self, e: &[Expr<'_>]) {
+ for e in e {
+ self.hash_expr(e);
+ }
+ }
+
+ pub fn hash_name(&mut self, n: Symbol) {
+ n.as_str().hash(&mut self.s);
+ }
+
+ pub fn hash_qpath(&mut self, p: &QPath<'_>) {
+ match *p {
+ QPath::Resolved(_, ref path) => {
+ self.hash_path(path);
+ },
+ QPath::TypeRelative(_, ref path) => {
+ self.hash_name(path.ident.name);
+ },
+ QPath::LangItem(lang_item, ..) => {
+ lang_item.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
+ },
+ }
+ // self.maybe_typeck_results.unwrap().qpath_res(p, id).hash(&mut self.s);
+ }
+
++ pub fn hash_pat(&mut self, pat: &Pat<'_>) {
++ std::mem::discriminant(&pat.kind).hash(&mut self.s);
++ match pat.kind {
++ PatKind::Binding(ann, _, _, pat) => {
++ ann.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
++ if let Some(pat) = pat {
++ self.hash_pat(pat);
++ }
++ },
++ PatKind::Box(pat) => self.hash_pat(pat),
++ PatKind::Lit(expr) => self.hash_expr(expr),
++ PatKind::Or(pats) => {
++ for pat in pats {
++ self.hash_pat(pat);
++ }
++ },
++ PatKind::Path(ref qpath) => self.hash_qpath(qpath),
++ PatKind::Range(s, e, i) => {
++ if let Some(s) = s {
++ self.hash_expr(s);
++ }
++ if let Some(e) = e {
++ self.hash_expr(e);
++ }
++ i.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
++ },
++ PatKind::Ref(pat, m) => {
++ self.hash_pat(pat);
++ m.hash(&mut self.s);
++ },
++ PatKind::Slice(l, m, r) => {
++ for pat in l {
++ self.hash_pat(pat);
++ }
++ if let Some(pat) = m {
++ self.hash_pat(pat);
++ }
++ for pat in r {
++ self.hash_pat(pat);
++ }
++ },
++ PatKind::Struct(ref qpath, fields, e) => {
++ self.hash_qpath(qpath);
++ for f in fields {
++ self.hash_name(f.ident.name);
++ self.hash_pat(f.pat);
++ }
++ e.hash(&mut self.s)
++ },
++ PatKind::Tuple(pats, e) => {
++ for pat in pats {
++ self.hash_pat(pat);
++ }
++ e.hash(&mut self.s);
++ },
++ PatKind::TupleStruct(ref qpath, pats, e) => {
++ self.hash_qpath(qpath);
++ for pat in pats {
++ self.hash_pat(pat);
++ }
++ e.hash(&mut self.s);
++ },
++ PatKind::Wild => {},
++ }
++ }
++
+ pub fn hash_path(&mut self, path: &Path<'_>) {
+ match path.res {
+ // constant hash since equality is dependant on inter-expression context
+ Res::Local(_) => 1_usize.hash(&mut self.s),
+ _ => {
+ for seg in path.segments {
+ self.hash_name(seg.ident.name);
+ }
+ },
+ }
+ }
+
+ pub fn hash_stmt(&mut self, b: &Stmt<'_>) {
+ std::mem::discriminant(&b.kind).hash(&mut self.s);
+
+ match &b.kind {
+ StmtKind::Local(local) => {
++ self.hash_pat(local.pat);
+ if let Some(ref init) = local.init {
+ self.hash_expr(init);
+ }
+ },
+ StmtKind::Item(..) => {},
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
+ self.hash_expr(expr);
+ },
+ }
+ }
+
+ pub fn hash_guard(&mut self, g: &Guard<'_>) {
+ match g {
+ Guard::If(ref expr) | Guard::IfLet(_, ref expr) => {
+ self.hash_expr(expr);
+ },
+ }
+ }
+
- self.hash_tykind(&ty.kind);
- }
-
- pub fn hash_tykind(&mut self, ty: &TyKind<'_>) {
- std::mem::discriminant(ty).hash(&mut self.s);
- match ty {
++ pub fn hash_lifetime(&mut self, lifetime: Lifetime) {
+ std::mem::discriminant(&lifetime.name).hash(&mut self.s);
+ if let LifetimeName::Param(ref name) = lifetime.name {
+ std::mem::discriminant(name).hash(&mut self.s);
+ match name {
+ ParamName::Plain(ref ident) => {
+ ident.name.hash(&mut self.s);
+ },
+ ParamName::Fresh(ref size) => {
+ size.hash(&mut self.s);
+ },
+ ParamName::Error => {},
+ }
+ }
+ }
+
+ pub fn hash_ty(&mut self, ty: &Ty<'_>) {
- TyKind::Ptr(mut_ty) => {
++ std::mem::discriminant(&ty.kind).hash(&mut self.s);
++ match ty.kind {
+ TyKind::Slice(ty) => {
+ self.hash_ty(ty);
+ },
+ TyKind::Array(ty, anon_const) => {
+ self.hash_ty(ty);
+ self.hash_body(anon_const.body);
+ },
- TyKind::Rptr(lifetime, mut_ty) => {
++ TyKind::Ptr(ref mut_ty) => {
+ self.hash_ty(&mut_ty.ty);
+ mut_ty.mutbl.hash(&mut self.s);
+ },
- for ty in *ty_list {
++ TyKind::Rptr(lifetime, ref mut_ty) => {
+ self.hash_lifetime(lifetime);
+ self.hash_ty(&mut_ty.ty);
+ mut_ty.mutbl.hash(&mut self.s);
+ },
+ TyKind::BareFn(bfn) => {
+ bfn.unsafety.hash(&mut self.s);
+ bfn.abi.hash(&mut self.s);
+ for arg in bfn.decl.inputs {
+ self.hash_ty(&arg);
+ }
+ match bfn.decl.output {
+ FnRetTy::DefaultReturn(_) => {
+ ().hash(&mut self.s);
+ },
+ FnRetTy::Return(ref ty) => {
+ self.hash_ty(ty);
+ },
+ }
+ bfn.decl.c_variadic.hash(&mut self.s);
+ },
+ TyKind::Tup(ty_list) => {
- TyKind::Path(qpath) => match qpath {
++ for ty in ty_list {
+ self.hash_ty(ty);
+ }
+ },
- match arg {
- GenericArg::Lifetime(ref l) => self.hash_lifetime(l),
- GenericArg::Type(ref ty) => self.hash_ty(&ty),
++ TyKind::Path(ref qpath) => match qpath {
+ QPath::Resolved(ref maybe_ty, ref path) => {
+ if let Some(ref ty) = maybe_ty {
+ self.hash_ty(ty);
+ }
+ for segment in path.segments {
+ segment.ident.name.hash(&mut self.s);
+ self.hash_generic_args(segment.args().args);
+ }
+ },
+ QPath::TypeRelative(ref ty, ref segment) => {
+ self.hash_ty(ty);
+ segment.ident.name.hash(&mut self.s);
+ },
+ QPath::LangItem(lang_item, ..) => {
+ lang_item.hash(&mut self.s);
+ },
+ },
+ TyKind::OpaqueDef(_, arg_list) => {
+ self.hash_generic_args(arg_list);
+ },
+ TyKind::TraitObject(_, lifetime, _) => {
+ self.hash_lifetime(lifetime);
+ },
+ TyKind::Typeof(anon_const) => {
+ self.hash_body(anon_const.body);
+ },
+ TyKind::Err | TyKind::Infer | TyKind::Never => {},
+ }
+ }
+
+ pub fn hash_body(&mut self, body_id: BodyId) {
+ // swap out TypeckResults when hashing a body
+ let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body_id));
+ self.hash_expr(&self.cx.tcx.hir().body(body_id).value);
+ self.maybe_typeck_results = old_maybe_typeck_results;
+ }
+
+ fn hash_generic_args(&mut self, arg_list: &[GenericArg<'_>]) {
+ for arg in arg_list {
++ match *arg {
++ GenericArg::Lifetime(l) => self.hash_lifetime(l),
++ GenericArg::Type(ref ty) => self.hash_ty(ty),
+ GenericArg::Const(ref ca) => self.hash_body(ca.value.body),
+ }
+ }
+ }
+}
--- /dev/null
- use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor};
+#![feature(box_patterns)]
+#![feature(in_band_lifetimes)]
+#![feature(iter_zip)]
+#![cfg_attr(bootstrap, feature(or_patterns))]
+#![feature(rustc_private)]
+#![recursion_limit = "512"]
+#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
+
+// FIXME: switch to something more ergonomic here, once available.
+// (Currently there is no way to opt into sysroot crates without `extern crate`.)
+extern crate rustc_ast;
+extern crate rustc_ast_pretty;
+extern crate rustc_attr;
+extern crate rustc_data_structures;
+extern crate rustc_errors;
+extern crate rustc_hir;
+extern crate rustc_infer;
+extern crate rustc_lexer;
+extern crate rustc_lint;
+extern crate rustc_middle;
+extern crate rustc_mir;
+extern crate rustc_session;
+extern crate rustc_span;
+extern crate rustc_target;
+extern crate rustc_trait_selection;
+extern crate rustc_typeck;
+
+#[macro_use]
+pub mod sym_helper;
+
+#[allow(clippy::module_name_repetitions)]
+pub mod ast_utils;
+pub mod attrs;
+pub mod camel_case;
+pub mod comparisons;
+pub mod consts;
+pub mod diagnostics;
+pub mod eager_or_lazy;
+pub mod higher;
+mod hir_utils;
+pub mod msrvs;
+pub mod numeric_literal;
+pub mod paths;
+pub mod ptr;
+pub mod qualify_min_const_fn;
+pub mod source;
+pub mod sugg;
+pub mod ty;
+pub mod usage;
+pub mod visitors;
+
+pub use self::attrs::*;
+pub use self::hir_utils::{both, count_eq, eq_expr_value, over, SpanlessEq, SpanlessHash};
+
+use std::collections::hash_map::Entry;
+use std::hash::BuildHasherDefault;
+
+use if_chain::if_chain;
+use rustc_ast::ast::{self, Attribute, BorrowKind, LitKind};
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::{DefId, LOCAL_CRATE};
- ImplItem, ImplItemKind, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment,
- QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
++use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor};
+use rustc_hir::LangItem::{ResultErr, ResultOk};
+use rustc_hir::{
+ def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl,
- match get_parent_node(cx.tcx, e.hir_id) {
++ ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path,
++ PathSegment, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
+};
+use rustc_lint::{LateContext, Level, Lint, LintContext};
+use rustc_middle::hir::exports::Export;
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty as rustc_ty;
+use rustc_middle::ty::{layout::IntegerExt, DefIdTree, Ty, TyCtxt, TypeFoldable};
+use rustc_semver::RustcVersion;
+use rustc_session::Session;
+use rustc_span::hygiene::{ExpnKind, MacroKind};
+use rustc_span::source_map::original_sp;
+use rustc_span::sym;
+use rustc_span::symbol::{kw, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+use rustc_target::abi::Integer;
+
+use crate::consts::{constant, Constant};
+use crate::ty::{can_partially_move_ty, is_recursively_primitive_type};
+
+pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
+ if let Ok(version) = RustcVersion::parse(msrv) {
+ return Some(version);
+ } else if let Some(sess) = sess {
+ if let Some(span) = span {
+ sess.span_err(span, &format!("`{}` is not a valid Rust version", msrv));
+ }
+ }
+ None
+}
+
+pub fn meets_msrv(msrv: Option<&RustcVersion>, lint_msrv: &RustcVersion) -> bool {
+ msrv.map_or(true, |msrv| msrv.meets(*lint_msrv))
+}
+
+#[macro_export]
+macro_rules! extract_msrv_attr {
+ (LateContext) => {
+ extract_msrv_attr!(@LateContext, ());
+ };
+ (EarlyContext) => {
+ extract_msrv_attr!(@EarlyContext);
+ };
+ (@$context:ident$(, $call:tt)?) => {
+ fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'tcx>, attrs: &'tcx [rustc_ast::ast::Attribute]) {
+ use $crate::get_unique_inner_attr;
+ match get_unique_inner_attr(cx.sess$($call)?, attrs, "msrv") {
+ Some(msrv_attr) => {
+ if let Some(msrv) = msrv_attr.value_str() {
+ self.msrv = $crate::parse_msrv(
+ &msrv.to_string(),
+ Some(cx.sess$($call)?),
+ Some(msrv_attr.span),
+ );
+ } else {
+ cx.sess$($call)?.span_err(msrv_attr.span, "bad clippy attribute");
+ }
+ },
+ _ => (),
+ }
+ }
+ };
+}
+
+/// Returns `true` if the two spans come from differing expansions (i.e., one is
+/// from a macro and one isn't).
+#[must_use]
+pub fn differing_macro_contexts(lhs: Span, rhs: Span) -> bool {
+ rhs.ctxt() != lhs.ctxt()
+}
+
+/// If the given expression is a local binding, find the initializer expression.
+/// If that initializer expression is another local binding, find its initializer again.
+/// This process repeats as long as possible (but usually no more than once). Initializer
+/// expressions with adjustments are ignored. If this is not desired, use [`find_binding_init`]
+/// instead.
+///
+/// Examples:
+/// ```ignore
+/// let abc = 1;
+/// // ^ output
+/// let def = abc;
+/// dbg!(def)
+/// // ^^^ input
+///
+/// // or...
+/// let abc = 1;
+/// let def = abc + 2;
+/// // ^^^^^^^ output
+/// dbg!(def)
+/// // ^^^ input
+/// ```
+pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> {
+ while let Some(init) = path_to_local(expr)
+ .and_then(|id| find_binding_init(cx, id))
+ .filter(|init| cx.typeck_results().expr_adjustments(init).is_empty())
+ {
+ expr = init;
+ }
+ expr
+}
+
+/// Finds the initializer expression for a local binding. Returns `None` if the binding is mutable.
+/// By only considering immutable bindings, we guarantee that the returned expression represents the
+/// value of the binding wherever it is referenced.
+///
+/// Example: For `let x = 1`, if the `HirId` of `x` is provided, the `Expr` `1` is returned.
+/// Note: If you have an expression that references a binding `x`, use `path_to_local` to get the
+/// canonical binding `HirId`.
+pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
+ let hir = cx.tcx.hir();
+ if_chain! {
+ if let Some(Node::Binding(pat)) = hir.find(hir_id);
+ if matches!(pat.kind, PatKind::Binding(BindingAnnotation::Unannotated, ..));
+ let parent = hir.get_parent_node(hir_id);
+ if let Some(Node::Local(local)) = hir.find(parent);
+ then {
+ return local.init;
+ }
+ }
+ None
+}
+
+/// Returns `true` if the given `NodeId` is inside a constant context
+///
+/// # Example
+///
+/// ```rust,ignore
+/// if in_constant(cx, expr.hir_id) {
+/// // Do something
+/// }
+/// ```
+pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool {
+ let parent_id = cx.tcx.hir().get_parent_item(id);
+ match cx.tcx.hir().get(parent_id) {
+ Node::Item(&Item {
+ kind: ItemKind::Const(..) | ItemKind::Static(..),
+ ..
+ })
+ | Node::TraitItem(&TraitItem {
+ kind: TraitItemKind::Const(..),
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Const(..),
+ ..
+ })
+ | Node::AnonConst(_) => true,
+ Node::Item(&Item {
+ kind: ItemKind::Fn(ref sig, ..),
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Fn(ref sig, _),
+ ..
+ }) => sig.header.constness == Constness::Const,
+ _ => false,
+ }
+}
+
+/// Checks if a `QPath` resolves to a constructor of a `LangItem`.
+/// For example, use this to check whether a function call or a pattern is `Some(..)`.
+pub fn is_lang_ctor(cx: &LateContext<'_>, qpath: &QPath<'_>, lang_item: LangItem) -> bool {
+ if let QPath::Resolved(_, path) = qpath {
+ if let Res::Def(DefKind::Ctor(..), ctor_id) = path.res {
+ if let Ok(item_id) = cx.tcx.lang_items().require(lang_item) {
+ return cx.tcx.parent(ctor_id) == Some(item_id);
+ }
+ }
+ }
+ false
+}
+
+/// Returns `true` if this `span` was expanded by any macro.
+#[must_use]
+pub fn in_macro(span: Span) -> bool {
+ if span.from_expansion() {
+ !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
+ } else {
+ false
+ }
+}
+
+/// Checks if given pattern is a wildcard (`_`)
+pub fn is_wild<'tcx>(pat: &impl std::ops::Deref<Target = Pat<'tcx>>) -> bool {
+ matches!(pat.kind, PatKind::Wild)
+}
+
+/// Checks if the first type parameter is a lang item.
+pub fn is_ty_param_lang_item(cx: &LateContext<'_>, qpath: &QPath<'tcx>, item: LangItem) -> Option<&'tcx hir::Ty<'tcx>> {
+ let ty = get_qpath_generic_tys(qpath).next()?;
+
+ if let TyKind::Path(qpath) = &ty.kind {
+ cx.qpath_res(qpath, ty.hir_id)
+ .opt_def_id()
+ .map_or(false, |id| {
+ cx.tcx.lang_items().require(item).map_or(false, |lang_id| id == lang_id)
+ })
+ .then(|| ty)
+ } else {
+ None
+ }
+}
+
+/// Checks if the first type parameter is a diagnostic item.
+pub fn is_ty_param_diagnostic_item(
+ cx: &LateContext<'_>,
+ qpath: &QPath<'tcx>,
+ item: Symbol,
+) -> Option<&'tcx hir::Ty<'tcx>> {
+ let ty = get_qpath_generic_tys(qpath).next()?;
+
+ if let TyKind::Path(qpath) = &ty.kind {
+ cx.qpath_res(qpath, ty.hir_id)
+ .opt_def_id()
+ .map_or(false, |id| cx.tcx.is_diagnostic_item(item, id))
+ .then(|| ty)
+ } else {
+ None
+ }
+}
+
+/// Checks if the method call given in `expr` belongs to the given trait.
+/// This is a deprecated function, consider using [`is_trait_method`].
+pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool {
+ let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
+ let trt_id = cx.tcx.trait_of_item(def_id);
+ trt_id.map_or(false, |trt_id| match_def_path(cx, trt_id, path))
+}
+
+/// Checks if a method is defined in an impl of a diagnostic item
+pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
+ if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
+ if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
+ return cx.tcx.is_diagnostic_item(diag_item, adt.did);
+ }
+ }
+ false
+}
+
+/// Checks if a method is in a diagnostic item trait
+pub fn is_diag_trait_item(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
+ if let Some(trait_did) = cx.tcx.trait_of_item(def_id) {
+ return cx.tcx.is_diagnostic_item(diag_item, trait_did);
+ }
+ false
+}
+
+/// Checks if the method call given in `expr` belongs to the given trait.
+pub fn is_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
+ cx.typeck_results()
+ .type_dependent_def_id(expr.hir_id)
+ .map_or(false, |did| is_diag_trait_item(cx, did, diag_item))
+}
+
+/// Checks if an expression references a variable of the given name.
+pub fn match_var(expr: &Expr<'_>, var: Symbol) -> bool {
+ if let ExprKind::Path(QPath::Resolved(None, ref path)) = expr.kind {
+ if let [p] = path.segments {
+ return p.ident.name == var;
+ }
+ }
+ false
+}
+
+pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
+ match *path {
+ QPath::Resolved(_, ref path) => path.segments.last().expect("A path must have at least one segment"),
+ QPath::TypeRelative(_, ref seg) => seg,
+ QPath::LangItem(..) => panic!("last_path_segment: lang item has no path segments"),
+ }
+}
+
+pub fn get_qpath_generics(path: &QPath<'tcx>) -> Option<&'tcx GenericArgs<'tcx>> {
+ match path {
+ QPath::Resolved(_, p) => p.segments.last().and_then(|s| s.args),
+ QPath::TypeRelative(_, s) => s.args,
+ QPath::LangItem(..) => None,
+ }
+}
+
+pub fn get_qpath_generic_tys(path: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> {
+ get_qpath_generics(path)
+ .map_or([].as_ref(), |a| a.args)
+ .iter()
+ .filter_map(|a| {
+ if let hir::GenericArg::Type(ty) = a {
+ Some(ty)
+ } else {
+ None
+ }
+ })
+}
+
+pub fn single_segment_path<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
+ match *path {
+ QPath::Resolved(_, ref path) => path.segments.get(0),
+ QPath::TypeRelative(_, ref seg) => Some(seg),
+ QPath::LangItem(..) => None,
+ }
+}
+
+/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the
+/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from
+/// `QPath::Resolved.1.res.opt_def_id()`.
+///
+/// Matches a `QPath` against a slice of segment string literals.
+///
+/// There is also `match_path` if you are dealing with a `rustc_hir::Path` instead of a
+/// `rustc_hir::QPath`.
+///
+/// # Examples
+/// ```rust,ignore
+/// match_qpath(path, &["std", "rt", "begin_unwind"])
+/// ```
+pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool {
+ match *path {
+ QPath::Resolved(_, ref path) => match_path(path, segments),
+ QPath::TypeRelative(ref ty, ref segment) => match ty.kind {
+ TyKind::Path(ref inner_path) => {
+ if let [prefix @ .., end] = segments {
+ if match_qpath(inner_path, prefix) {
+ return segment.ident.name.as_str() == *end;
+ }
+ }
+ false
+ },
+ _ => false,
+ },
+ QPath::LangItem(..) => false,
+ }
+}
+
+/// If the expression is a path, resolve it. Otherwise, return `Res::Err`.
+pub fn expr_path_res(cx: &LateContext<'_>, expr: &Expr<'_>) -> Res {
+ if let ExprKind::Path(p) = &expr.kind {
+ cx.qpath_res(p, expr.hir_id)
+ } else {
+ Res::Err
+ }
+}
+
+/// Resolves the path to a `DefId` and checks if it matches the given path.
+pub fn is_qpath_def_path(cx: &LateContext<'_>, path: &QPath<'_>, hir_id: HirId, segments: &[&str]) -> bool {
+ cx.qpath_res(path, hir_id)
+ .opt_def_id()
+ .map_or(false, |id| match_def_path(cx, id, segments))
+}
+
+/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given path.
+pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool {
+ expr_path_res(cx, expr)
+ .opt_def_id()
+ .map_or(false, |id| match_def_path(cx, id, segments))
+}
+
+/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the
+/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from
+/// `QPath::Resolved.1.res.opt_def_id()`.
+///
+/// Matches a `Path` against a slice of segment string literals.
+///
+/// There is also `match_qpath` if you are dealing with a `rustc_hir::QPath` instead of a
+/// `rustc_hir::Path`.
+///
+/// # Examples
+///
+/// ```rust,ignore
+/// if match_path(&trait_ref.path, &paths::HASH) {
+/// // This is the `std::hash::Hash` trait.
+/// }
+///
+/// if match_path(ty_path, &["rustc", "lint", "Lint"]) {
+/// // This is a `rustc_middle::lint::Lint`.
+/// }
+/// ```
+pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool {
+ path.segments
+ .iter()
+ .rev()
+ .zip(segments.iter().rev())
+ .all(|(a, b)| a.ident.name.as_str() == *b)
+}
+
+/// If the expression is a path to a local, returns the canonical `HirId` of the local.
+pub fn path_to_local(expr: &Expr<'_>) -> Option<HirId> {
+ if let ExprKind::Path(QPath::Resolved(None, ref path)) = expr.kind {
+ if let Res::Local(id) = path.res {
+ return Some(id);
+ }
+ }
+ None
+}
+
+/// Returns true if the expression is a path to a local with the specified `HirId`.
+/// Use this function to see if an expression matches a function argument or a match binding.
+pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool {
+ path_to_local(expr) == Some(id)
+}
+
+/// Gets the definition associated to a path.
+#[allow(clippy::shadow_unrelated)] // false positive #6563
+pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
+ macro_rules! try_res {
+ ($e:expr) => {
+ match $e {
+ Some(e) => e,
+ None => return Res::Err,
+ }
+ };
+ }
+ fn item_child_by_name<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, name: &str) -> Option<&'tcx Export<HirId>> {
+ tcx.item_children(def_id)
+ .iter()
+ .find(|item| item.ident.name.as_str() == name)
+ }
+
+ let (krate, first, path) = match *path {
+ [krate, first, ref path @ ..] => (krate, first, path),
+ _ => return Res::Err,
+ };
+ let tcx = cx.tcx;
+ let crates = tcx.crates();
+ let krate = try_res!(crates.iter().find(|&&num| tcx.crate_name(num).as_str() == krate));
+ let first = try_res!(item_child_by_name(tcx, krate.as_def_id(), first));
+ let last = path
+ .iter()
+ .copied()
+ // `get_def_path` seems to generate these empty segments for extern blocks.
+ // We can just ignore them.
+ .filter(|segment| !segment.is_empty())
+ // for each segment, find the child item
+ .try_fold(first, |item, segment| {
+ let def_id = item.res.def_id();
+ if let Some(item) = item_child_by_name(tcx, def_id, segment) {
+ Some(item)
+ } else if matches!(item.res, Res::Def(DefKind::Enum | DefKind::Struct, _)) {
+ // it is not a child item so check inherent impl items
+ tcx.inherent_impls(def_id)
+ .iter()
+ .find_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, segment))
+ } else {
+ None
+ }
+ });
+ try_res!(last).res
+}
+
+/// Convenience function to get the `DefId` of a trait by path.
+/// It could be a trait or trait alias.
+pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option<DefId> {
+ match path_to_res(cx, path) {
+ Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
+ _ => None,
+ }
+}
+
+/// Gets the `hir::TraitRef` of the trait the given method is implemented for.
+///
+/// Use this if you want to find the `TraitRef` of the `Add` trait in this example:
+///
+/// ```rust
+/// struct Point(isize, isize);
+///
+/// impl std::ops::Add for Point {
+/// type Output = Self;
+///
+/// fn add(self, other: Self) -> Self {
+/// Point(0, 0)
+/// }
+/// }
+/// ```
+pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx TraitRef<'tcx>> {
+ // Get the implemented trait for the current function
+ let parent_impl = cx.tcx.hir().get_parent_item(hir_id);
+ if_chain! {
+ if parent_impl != hir::CRATE_HIR_ID;
+ if let hir::Node::Item(item) = cx.tcx.hir().get(parent_impl);
+ if let hir::ItemKind::Impl(impl_) = &item.kind;
+ then { return impl_.of_trait.as_ref(); }
+ }
+ None
+}
+
+/// Checks if the top level expression can be moved into a closure as is.
+pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, jump_targets: &[HirId]) -> bool {
+ match expr.kind {
+ ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
+ | ExprKind::Continue(Destination { target_id: Ok(id), .. })
+ if jump_targets.contains(&id) =>
+ {
+ true
+ },
+ ExprKind::Break(..)
+ | ExprKind::Continue(_)
+ | ExprKind::Ret(_)
+ | ExprKind::Yield(..)
+ | ExprKind::InlineAsm(_)
+ | ExprKind::LlvmInlineAsm(_) => false,
+ // Accessing a field of a local value can only be done if the type isn't
+ // partially moved.
+ ExprKind::Field(base_expr, _)
+ if matches!(
+ base_expr.kind,
+ ExprKind::Path(QPath::Resolved(_, Path { res: Res::Local(_), .. }))
+ ) && can_partially_move_ty(cx, cx.typeck_results().expr_ty(base_expr)) =>
+ {
+ // TODO: check if the local has been partially moved. Assume it has for now.
+ false
+ }
+ _ => true,
+ }
+}
+
+/// Checks if the expression can be moved into a closure as is.
+pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ struct V<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ loops: Vec<HirId>,
+ allow_closure: bool,
+ }
+ impl Visitor<'tcx> for V<'_, 'tcx> {
+ type Map = ErasedMap<'tcx>;
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if !self.allow_closure {
+ return;
+ }
+ if let ExprKind::Loop(b, ..) = e.kind {
+ self.loops.push(e.hir_id);
+ self.visit_block(b);
+ self.loops.pop();
+ } else {
+ self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops);
+ walk_expr(self, e);
+ }
+ }
+ }
+
+ let mut v = V {
+ cx,
+ allow_closure: true,
+ loops: Vec::new(),
+ };
+ v.visit_expr(expr);
+ v.allow_closure
+}
+
+/// Returns the method names and argument list of nested method call expressions that make up
+/// `expr`. method/span lists are sorted with the most recent call first.
+pub fn method_calls<'tcx>(
+ expr: &'tcx Expr<'tcx>,
+ max_depth: usize,
+) -> (Vec<Symbol>, Vec<&'tcx [Expr<'tcx>]>, Vec<Span>) {
+ let mut method_names = Vec::with_capacity(max_depth);
+ let mut arg_lists = Vec::with_capacity(max_depth);
+ let mut spans = Vec::with_capacity(max_depth);
+
+ let mut current = expr;
+ for _ in 0..max_depth {
+ if let ExprKind::MethodCall(path, span, args, _) = ¤t.kind {
+ if args.iter().any(|e| e.span.from_expansion()) {
+ break;
+ }
+ method_names.push(path.ident.name);
+ arg_lists.push(&**args);
+ spans.push(*span);
+ current = &args[0];
+ } else {
+ break;
+ }
+ }
+
+ (method_names, arg_lists, spans)
+}
+
+/// Matches an `Expr` against a chain of methods, and return the matched `Expr`s.
+///
+/// For example, if `expr` represents the `.baz()` in `foo.bar().baz()`,
+/// `method_chain_args(expr, &["bar", "baz"])` will return a `Vec`
+/// containing the `Expr`s for
+/// `.bar()` and `.baz()`
+pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option<Vec<&'a [Expr<'a>]>> {
+ let mut current = expr;
+ let mut matched = Vec::with_capacity(methods.len());
+ for method_name in methods.iter().rev() {
+ // method chains are stored last -> first
+ if let ExprKind::MethodCall(ref path, _, ref args, _) = current.kind {
+ if path.ident.name.as_str() == *method_name {
+ if args.iter().any(|e| e.span.from_expansion()) {
+ return None;
+ }
+ matched.push(&**args); // build up `matched` backwards
+ current = &args[0] // go to parent expression
+ } else {
+ return None;
+ }
+ } else {
+ return None;
+ }
+ }
+ // Reverse `matched` so that it is in the same order as `methods`.
+ matched.reverse();
+ Some(matched)
+}
+
+/// Returns `true` if the provided `def_id` is an entrypoint to a program.
+pub fn is_entrypoint_fn(cx: &LateContext<'_>, def_id: DefId) -> bool {
+ cx.tcx
+ .entry_fn(LOCAL_CRATE)
+ .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id)
+}
+
+/// Returns `true` if the expression is in the program's `#[panic_handler]`.
+pub fn is_in_panic_handler(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ let parent = cx.tcx.hir().get_parent_item(e.hir_id);
+ let def_id = cx.tcx.hir().local_def_id(parent).to_def_id();
+ Some(def_id) == cx.tcx.lang_items().panic_impl()
+}
+
+/// Gets the name of the item the expression is in, if available.
+pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
+ let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id);
+ match cx.tcx.hir().find(parent_id) {
+ Some(
+ Node::Item(Item { ident, .. })
+ | Node::TraitItem(TraitItem { ident, .. })
+ | Node::ImplItem(ImplItem { ident, .. }),
+ ) => Some(ident.name),
+ _ => None,
+ }
+}
+
+/// Gets the name of a `Pat`, if any.
+pub fn get_pat_name(pat: &Pat<'_>) -> Option<Symbol> {
+ match pat.kind {
+ PatKind::Binding(.., ref spname, _) => Some(spname.name),
+ PatKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.ident.name),
+ PatKind::Box(ref p) | PatKind::Ref(ref p, _) => get_pat_name(&*p),
+ _ => None,
+ }
+}
+
+pub struct ContainsName {
+ pub name: Symbol,
+ pub result: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for ContainsName {
+ type Map = Map<'tcx>;
+
+ fn visit_name(&mut self, _: Span, name: Symbol) {
+ if self.name == name {
+ self.result = true;
+ }
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// Checks if an `Expr` contains a certain name.
+pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool {
+ let mut cn = ContainsName { name, result: false };
+ cn.visit_expr(expr);
+ cn.result
+}
+
+/// Returns `true` if `expr` contains a return expression
+pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
+ struct RetCallFinder {
+ found: bool,
+ }
+
+ impl<'tcx> hir::intravisit::Visitor<'tcx> for RetCallFinder {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+ if self.found {
+ return;
+ }
+ if let hir::ExprKind::Ret(..) = &expr.kind {
+ self.found = true;
+ } else {
+ hir::intravisit::walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap<Self::Map> {
+ hir::intravisit::NestedVisitorMap::None
+ }
+ }
+
+ let mut visitor = RetCallFinder { found: false };
+ visitor.visit_expr(expr);
+ visitor.found
+}
+
+struct FindMacroCalls<'a, 'b> {
+ names: &'a [&'b str],
+ result: Vec<Span>,
+}
+
+impl<'a, 'b, 'tcx> Visitor<'tcx> for FindMacroCalls<'a, 'b> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) {
+ self.result.push(expr.span);
+ }
+ // and check sub-expressions
+ intravisit::walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// Finds calls of the specified macros in a function body.
+pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec<Span> {
+ let mut fmc = FindMacroCalls {
+ names,
+ result: Vec::new(),
+ };
+ fmc.visit_expr(&body.value);
+ fmc.result
+}
+
+/// Extends the span to the beginning of the spans line, incl. whitespaces.
+///
+/// ```rust,ignore
+/// let x = ();
+/// // ^^
+/// // will be converted to
+/// let x = ();
+/// // ^^^^^^^^^^^^^^
+/// ```
+fn line_span<T: LintContext>(cx: &T, span: Span) -> Span {
+ let span = original_sp(span, DUMMY_SP);
+ let source_map_and_line = cx.sess().source_map().lookup_line(span.lo()).unwrap();
+ let line_no = source_map_and_line.line;
+ let line_start = source_map_and_line.sf.lines[line_no];
+ Span::new(line_start, span.hi(), span.ctxt())
+}
+
+/// Gets the parent node, if any.
+pub fn get_parent_node(tcx: TyCtxt<'_>, id: HirId) -> Option<Node<'_>> {
+ tcx.hir().parent_iter(id).next().map(|(_, node)| node)
+}
+
+/// Gets the parent expression, if any –- this is useful to constrain a lint.
+pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
++ get_parent_expr_for_hir(cx, e.hir_id)
++}
++
++/// This retrieves the parent for the given `HirId` if it's an expression. This is useful for
++/// constraint lints
++pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: hir::HirId) -> Option<&'tcx Expr<'tcx>> {
++ match get_parent_node(cx.tcx, hir_id) {
+ Some(Node::Expr(parent)) => Some(parent),
+ _ => None,
+ }
+}
+
+pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Block<'tcx>> {
+ let map = &cx.tcx.hir();
+ let enclosing_node = map
+ .get_enclosing_scope(hir_id)
+ .and_then(|enclosing_id| map.find(enclosing_id));
+ enclosing_node.and_then(|node| match node {
+ Node::Block(block) => Some(block),
+ Node::Item(&Item {
+ kind: ItemKind::Fn(_, _, eid),
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Fn(_, eid),
+ ..
+ }) => match cx.tcx.hir().body(eid).value.kind {
+ ExprKind::Block(ref block, _) => Some(block),
+ _ => None,
+ },
+ _ => None,
+ })
+}
+
+/// Gets the parent node if it's an impl block.
+pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> {
+ let map = tcx.hir();
+ match map.parent_iter(id).next() {
+ Some((
+ _,
+ Node::Item(Item {
+ kind: ItemKind::Impl(imp),
+ ..
+ }),
+ )) => Some(imp),
+ _ => None,
+ }
+}
+
+/// Checks if the given expression is the else clause of either an `if` or `if let` expression.
+pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ let map = tcx.hir();
+ let mut iter = map.parent_iter(expr.hir_id);
+ match iter.next() {
+ Some((arm_id, Node::Arm(..))) => matches!(
+ iter.next(),
+ Some((
+ _,
+ Node::Expr(Expr {
+ kind: ExprKind::Match(_, [_, else_arm], MatchSource::IfLetDesugar { .. }),
+ ..
+ })
+ ))
+ if else_arm.hir_id == arm_id
+ ),
+ Some((
+ _,
+ Node::Expr(Expr {
+ kind: ExprKind::If(_, _, Some(else_expr)),
+ ..
+ }),
+ )) => else_expr.hir_id == expr.hir_id,
+ _ => false,
+ }
+}
+
+/// Checks whether the given expression is a constant integer of the given value.
+/// unlike `is_integer_literal`, this version does const folding
+pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool {
+ if is_integer_literal(e, value) {
+ return true;
+ }
+ let map = cx.tcx.hir();
+ let parent_item = map.get_parent_item(e.hir_id);
+ if let Some((Constant::Int(v), _)) = map
+ .maybe_body_owned_by(parent_item)
+ .and_then(|body_id| constant(cx, cx.tcx.typeck_body(body_id), e))
+ {
+ value == v
+ } else {
+ false
+ }
+}
+
+/// Checks whether the given expression is a constant literal of the given value.
+pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool {
+ // FIXME: use constant folding
+ if let ExprKind::Lit(ref spanned) = expr.kind {
+ if let LitKind::Int(v, _) = spanned.node {
+ return v == value;
+ }
+ }
+ false
+}
+
+/// Returns `true` if the given `Expr` has been coerced before.
+///
+/// Examples of coercions can be found in the Nomicon at
+/// <https://doc.rust-lang.org/nomicon/coercions.html>.
+///
+/// See `rustc_middle::ty::adjustment::Adjustment` and `rustc_typeck::check::coercion` for more
+/// information on adjustments and coercions.
+pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ cx.typeck_results().adjustments().get(e.hir_id).is_some()
+}
+
+/// Returns the pre-expansion span if is this comes from an expansion of the
+/// macro `name`.
+/// See also `is_direct_expn_of`.
+#[must_use]
+pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> {
+ loop {
+ if span.from_expansion() {
+ let data = span.ctxt().outer_expn_data();
+ let new_span = data.call_site;
+
+ if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
+ if mac_name.as_str() == name {
+ return Some(new_span);
+ }
+ }
+
+ span = new_span;
+ } else {
+ return None;
+ }
+ }
+}
+
+/// Returns the pre-expansion span if the span directly comes from an expansion
+/// of the macro `name`.
+/// The difference with `is_expn_of` is that in
+/// ```rust,ignore
+/// foo!(bar!(42));
+/// ```
+/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only
+/// `bar!` by
+/// `is_direct_expn_of`.
+#[must_use]
+pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> {
+ if span.from_expansion() {
+ let data = span.ctxt().outer_expn_data();
+ let new_span = data.call_site;
+
+ if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
+ if mac_name.as_str() == name {
+ return Some(new_span);
+ }
+ }
+ }
+
+ None
+}
+
+/// Convenience function to get the return type of a function.
+pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_item: hir::HirId) -> Ty<'tcx> {
+ let fn_def_id = cx.tcx.hir().local_def_id(fn_item);
+ let ret_ty = cx.tcx.fn_sig(fn_def_id).output();
+ cx.tcx.erase_late_bound_regions(ret_ty)
+}
+
+/// Checks if an expression is constructing a tuple-like enum variant or struct
+pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ if let ExprKind::Call(ref fun, _) = expr.kind {
+ if let ExprKind::Path(ref qp) = fun.kind {
+ let res = cx.qpath_res(qp, fun.hir_id);
+ return match res {
+ def::Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true,
+ def::Res::Def(_, def_id) => cx.tcx.is_promotable_const_fn(def_id),
+ _ => false,
+ };
+ }
+ }
+ false
+}
+
+/// Returns `true` if a pattern is refutable.
+// TODO: should be implemented using rustc/mir_build/thir machinery
+pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
+ fn is_enum_variant(cx: &LateContext<'_>, qpath: &QPath<'_>, id: HirId) -> bool {
+ matches!(
+ cx.qpath_res(qpath, id),
+ def::Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), _)
+ )
+ }
+
+ fn are_refutable<'a, I: Iterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, mut i: I) -> bool {
+ i.any(|pat| is_refutable(cx, pat))
+ }
+
+ match pat.kind {
+ PatKind::Wild => false,
+ PatKind::Binding(_, _, _, pat) => pat.map_or(false, |pat| is_refutable(cx, pat)),
+ PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => is_refutable(cx, pat),
+ PatKind::Lit(..) | PatKind::Range(..) => true,
+ PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id),
+ PatKind::Or(ref pats) => {
+ // TODO: should be the honest check, that pats is exhaustive set
+ are_refutable(cx, pats.iter().map(|pat| &**pat))
+ },
+ PatKind::Tuple(ref pats, _) => are_refutable(cx, pats.iter().map(|pat| &**pat)),
+ PatKind::Struct(ref qpath, ref fields, _) => {
+ is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| &*field.pat))
+ },
+ PatKind::TupleStruct(ref qpath, ref pats, _) => {
+ is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats.iter().map(|pat| &**pat))
+ },
+ PatKind::Slice(ref head, ref middle, ref tail) => {
+ match &cx.typeck_results().node_type(pat.hir_id).kind() {
+ rustc_ty::Slice(..) => {
+ // [..] is the only irrefutable slice pattern.
+ !head.is_empty() || middle.is_none() || !tail.is_empty()
+ },
+ rustc_ty::Array(..) => {
+ are_refutable(cx, head.iter().chain(middle).chain(tail.iter()).map(|pat| &**pat))
+ },
+ _ => {
+ // unreachable!()
+ true
+ },
+ }
+ },
+ }
+}
+
+/// If the pattern is an `or` pattern, call the function once for each sub pattern. Otherwise, call
+/// the function once on the given pattern.
+pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>, mut f: F) {
+ if let PatKind::Or(pats) = pat.kind {
+ pats.iter().copied().for_each(f)
+ } else {
+ f(pat)
+ }
+}
+
+/// Checks for the `#[automatically_derived]` attribute all `#[derive]`d
+/// implementations have.
+pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool {
+ attrs.iter().any(|attr| attr.has_name(sym::automatically_derived))
+}
+
+/// Remove blocks around an expression.
+///
+/// Ie. `x`, `{ x }` and `{{{{ x }}}}` all give `x`. `{ x; y }` and `{}` return
+/// themselves.
+pub fn remove_blocks<'tcx>(mut expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
+ while let ExprKind::Block(ref block, ..) = expr.kind {
+ match (block.stmts.is_empty(), block.expr.as_ref()) {
+ (true, Some(e)) => expr = e,
+ _ => break,
+ }
+ }
+ expr
+}
+
+pub fn is_self(slf: &Param<'_>) -> bool {
+ if let PatKind::Binding(.., name, _) = slf.pat.kind {
+ name.name == kw::SelfLower
+ } else {
+ false
+ }
+}
+
+pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool {
+ if_chain! {
+ if let TyKind::Path(QPath::Resolved(None, ref path)) = slf.kind;
+ if let Res::SelfTy(..) = path.res;
+ then {
+ return true
+ }
+ }
+ false
+}
+
+pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator<Item = &'tcx Param<'tcx>> {
+ (0..decl.inputs.len()).map(move |i| &body.params[i])
+}
+
+/// Checks if a given expression is a match expression expanded from the `?`
+/// operator or the `try` macro.
+pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ if_chain! {
+ if let PatKind::TupleStruct(ref path, ref pat, None) = arm.pat.kind;
+ if is_lang_ctor(cx, path, ResultOk);
+ if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind;
+ if path_to_local_id(arm.body, hir_id);
+ then {
+ return true;
+ }
+ }
+ false
+ }
+
+ fn is_err(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind {
+ is_lang_ctor(cx, path, ResultErr)
+ } else {
+ false
+ }
+ }
+
+ if let ExprKind::Match(_, ref arms, ref source) = expr.kind {
+ // desugared from a `?` operator
+ if let MatchSource::TryDesugar = *source {
+ return Some(expr);
+ }
+
+ if_chain! {
+ if arms.len() == 2;
+ if arms[0].guard.is_none();
+ if arms[1].guard.is_none();
+ if (is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) ||
+ (is_ok(cx, &arms[1]) && is_err(cx, &arms[0]));
+ then {
+ return Some(expr);
+ }
+ }
+ }
+
+ None
+}
+
+/// Returns `true` if the lint is allowed in the current context
+///
+/// Useful for skipping long running code when it's unnecessary
+pub fn is_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool {
+ cx.tcx.lint_level_at_node(lint, id).0 == Level::Allow
+}
+
+pub fn strip_pat_refs<'hir>(mut pat: &'hir Pat<'hir>) -> &'hir Pat<'hir> {
+ while let PatKind::Ref(subpat, _) = pat.kind {
+ pat = subpat;
+ }
+ pat
+}
+
+pub fn int_bits(tcx: TyCtxt<'_>, ity: rustc_ty::IntTy) -> u64 {
+ Integer::from_int_ty(&tcx, ity).size().bits()
+}
+
+#[allow(clippy::cast_possible_wrap)]
+/// Turn a constant int byte representation into an i128
+pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: rustc_ty::IntTy) -> i128 {
+ let amt = 128 - int_bits(tcx, ity);
+ ((u as i128) << amt) >> amt
+}
+
+#[allow(clippy::cast_sign_loss)]
+/// clip unused bytes
+pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: rustc_ty::IntTy) -> u128 {
+ let amt = 128 - int_bits(tcx, ity);
+ ((u as u128) << amt) >> amt
+}
+
+/// clip unused bytes
+pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: rustc_ty::UintTy) -> u128 {
+ let bits = Integer::from_uint_ty(&tcx, ity).size().bits();
+ let amt = 128 - bits;
+ (u << amt) >> amt
+}
+
+pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool {
+ let map = &tcx.hir();
+ let mut prev_enclosing_node = None;
+ let mut enclosing_node = node;
+ while Some(enclosing_node) != prev_enclosing_node {
+ if is_automatically_derived(map.attrs(enclosing_node)) {
+ return true;
+ }
+ prev_enclosing_node = Some(enclosing_node);
+ enclosing_node = map.get_parent_item(enclosing_node);
+ }
+ false
+}
+
+/// Matches a function call with the given path and returns the arguments.
+///
+/// Usage:
+///
+/// ```rust,ignore
+/// if let Some(args) = match_function_call(cx, cmp_max_call, &paths::CMP_MAX);
+/// ```
+pub fn match_function_call<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ path: &[&str],
+) -> Option<&'tcx [Expr<'tcx>]> {
+ if_chain! {
+ if let ExprKind::Call(ref fun, ref args) = expr.kind;
+ if let ExprKind::Path(ref qpath) = fun.kind;
+ if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
+ if match_def_path(cx, fun_def_id, path);
+ then {
+ return Some(&args)
+ }
+ };
+ None
+}
+
+/// Checks if the given `DefId` matches any of the paths. Returns the index of matching path, if
+/// any.
+pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) -> Option<usize> {
+ let search_path = cx.get_def_path(did);
+ paths
+ .iter()
+ .position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied()))
+}
+
+/// Checks if the given `DefId` matches the path.
+pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool {
+ // We should probably move to Symbols in Clippy as well rather than interning every time.
+ let path = cx.get_def_path(did);
+ syms.iter().map(|x| Symbol::intern(x)).eq(path.iter().copied())
+}
+
+pub fn match_panic_call(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if let ExprKind::Call(func, [arg]) = expr.kind {
+ expr_path_res(cx, func)
+ .opt_def_id()
+ .map_or(false, |id| match_panic_def_id(cx, id))
+ .then(|| arg)
+ } else {
+ None
+ }
+}
+
+pub fn match_panic_def_id(cx: &LateContext<'_>, did: DefId) -> bool {
+ match_any_def_paths(
+ cx,
+ did,
+ &[
+ &paths::BEGIN_PANIC,
+ &paths::BEGIN_PANIC_FMT,
+ &paths::PANIC_ANY,
+ &paths::PANICKING_PANIC,
+ &paths::PANICKING_PANIC_FMT,
+ &paths::PANICKING_PANIC_STR,
+ ],
+ )
+ .is_some()
+}
+
+/// Returns the list of condition expressions and the list of blocks in a
+/// sequence of `if/else`.
+/// E.g., this returns `([a, b], [c, d, e])` for the expression
+/// `if a { c } else if b { d } else { e }`.
+pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, Vec<&'tcx Block<'tcx>>) {
+ let mut conds = Vec::new();
+ let mut blocks: Vec<&Block<'_>> = Vec::new();
+
+ while let ExprKind::If(ref cond, ref then_expr, ref else_expr) = expr.kind {
+ conds.push(&**cond);
+ if let ExprKind::Block(ref block, _) = then_expr.kind {
+ blocks.push(block);
+ } else {
+ panic!("ExprKind::If node is not an ExprKind::Block");
+ }
+
+ if let Some(ref else_expr) = *else_expr {
+ expr = else_expr;
+ } else {
+ break;
+ }
+ }
+
+ // final `else {..}`
+ if !blocks.is_empty() {
+ if let ExprKind::Block(ref block, _) = expr.kind {
+ blocks.push(&**block);
+ }
+ }
+
+ (conds, blocks)
+}
+
++/// Checks if the given function kind is an async function.
++pub fn is_async_fn(kind: FnKind<'_>) -> bool {
++ matches!(kind, FnKind::ItemFn(_, _, header, _) if header.asyncness == IsAsync::Async)
++}
++
++/// Peels away all the compiler generated code surrounding the body of an async function,
++pub fn get_async_fn_body(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> {
++ if let ExprKind::Call(
++ _,
++ &[Expr {
++ kind: ExprKind::Closure(_, _, body, _, _),
++ ..
++ }],
++ ) = body.value.kind
++ {
++ if let ExprKind::Block(
++ Block {
++ stmts: [],
++ expr:
++ Some(Expr {
++ kind: ExprKind::DropTemps(expr),
++ ..
++ }),
++ ..
++ },
++ _,
++ ) = tcx.hir().body(body).value.kind
++ {
++ return Some(expr);
++ }
++ };
++ None
++}
++
+// Finds the `#[must_use]` attribute, if any
+pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> {
+ attrs.iter().find(|a| a.has_name(sym::must_use))
+}
+
+// check if expr is calling method or function with #[must_use] attribute
+pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let did = match expr.kind {
+ ExprKind::Call(ref path, _) => if_chain! {
+ if let ExprKind::Path(ref qpath) = path.kind;
+ if let def::Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id);
+ then {
+ Some(did)
+ } else {
+ None
+ }
+ },
+ ExprKind::MethodCall(_, _, _, _) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
+ _ => None,
+ };
+
+ did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some())
+}
+
+/// Gets the node where an expression is either used, or it's type is unified with another branch.
+pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
+ let map = tcx.hir();
+ let mut child_id = expr.hir_id;
+ let mut iter = map.parent_iter(child_id);
+ loop {
+ match iter.next() {
+ None => break None,
+ Some((id, Node::Block(_))) => child_id = id,
+ Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
+ Some((_, Node::Expr(expr))) => match expr.kind {
+ ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id,
+ ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id,
+ ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None,
+ _ => break Some(Node::Expr(expr)),
+ },
+ Some((_, node)) => break Some(node),
+ }
+ }
+}
+
+/// Checks if the result of an expression is used, or it's type is unified with another branch.
+pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ !matches!(
+ get_expr_use_or_unification_node(tcx, expr),
+ None | Some(Node::Stmt(Stmt {
+ kind: StmtKind::Expr(_)
+ | StmtKind::Semi(_)
+ | StmtKind::Local(Local {
+ pat: Pat {
+ kind: PatKind::Wild,
+ ..
+ },
+ ..
+ }),
+ ..
+ }))
+ )
+}
+
+/// Checks if the expression is the final expression returned from a block.
+pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..)))
+}
+
+pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
+ cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
+ if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
+ attr.path == sym::no_std
+ } else {
+ false
+ }
+ })
+}
+
+/// Check if parent of a hir node is a trait implementation block.
+/// For example, `f` in
+/// ```rust,ignore
+/// impl Trait for S {
+/// fn f() {}
+/// }
+/// ```
+pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool {
+ if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
+ matches!(item.kind, ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }))
+ } else {
+ false
+ }
+}
+
+/// Check if it's even possible to satisfy the `where` clause for the item.
+///
+/// `trivial_bounds` feature allows functions with unsatisfiable bounds, for example:
+///
+/// ```ignore
+/// fn foo() where i32: Iterator {
+/// for _ in 2i32 {}
+/// }
+/// ```
+pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool {
+ use rustc_trait_selection::traits;
+ let predicates = cx
+ .tcx
+ .predicates_of(did)
+ .predicates
+ .iter()
+ .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
+ traits::impossible_predicates(
+ cx.tcx,
+ traits::elaborate_predicates(cx.tcx, predicates)
+ .map(|o| o.predicate)
+ .collect::<Vec<_>>(),
+ )
+}
+
+/// Returns the `DefId` of the callee if the given expression is a function or method call.
+pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> {
+ match &expr.kind {
+ ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
+ ExprKind::Call(
+ Expr {
+ kind: ExprKind::Path(qpath),
+ hir_id: path_hir_id,
+ ..
+ },
+ ..,
+ ) => cx.typeck_results().qpath_res(qpath, *path_hir_id).opt_def_id(),
+ _ => None,
+ }
+}
+
+/// This function checks if any of the lints in the slice is enabled for the provided `HirId`.
+/// A lint counts as enabled with any of the levels: `Level::Forbid` | `Level::Deny` | `Level::Warn`
+///
+/// ```ignore
+/// #[deny(clippy::YOUR_AWESOME_LINT)]
+/// println!("Hello, World!"); // <- Clippy code: run_lints(cx, &[YOUR_AWESOME_LINT], id) == true
+///
+/// #[allow(clippy::YOUR_AWESOME_LINT)]
+/// println!("See you soon!"); // <- Clippy code: run_lints(cx, &[YOUR_AWESOME_LINT], id) == false
+/// ```
+pub fn run_lints(cx: &LateContext<'_>, lints: &[&'static Lint], id: HirId) -> bool {
+ lints.iter().any(|lint| {
+ matches!(
+ cx.tcx.lint_level_at_node(lint, id),
+ (Level::Forbid | Level::Deny | Level::Warn, _)
+ )
+ })
+}
+
+/// Returns Option<String> where String is a textual representation of the type encapsulated in the
+/// slice iff the given expression is a slice of primitives (as defined in the
+/// `is_recursively_primitive_type` function) and None otherwise.
+pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
+ let expr_type = cx.typeck_results().expr_ty_adjusted(expr);
+ let expr_kind = expr_type.kind();
+ let is_primitive = match expr_kind {
+ rustc_ty::Slice(element_type) => is_recursively_primitive_type(element_type),
+ rustc_ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &rustc_ty::Slice(_)) => {
+ if let rustc_ty::Slice(element_type) = inner_ty.kind() {
+ is_recursively_primitive_type(element_type)
+ } else {
+ unreachable!()
+ }
+ },
+ _ => false,
+ };
+
+ if is_primitive {
+ // if we have wrappers like Array, Slice or Tuple, print these
+ // and get the type enclosed in the slice ref
+ match expr_type.peel_refs().walk().nth(1).unwrap().expect_ty().kind() {
+ rustc_ty::Slice(..) => return Some("slice".into()),
+ rustc_ty::Array(..) => return Some("array".into()),
+ rustc_ty::Tuple(..) => return Some("tuple".into()),
+ _ => {
+ // is_recursively_primitive_type() should have taken care
+ // of the rest and we can rely on the type that is found
+ let refs_peeled = expr_type.peel_refs();
+ return Some(refs_peeled.walk().last().unwrap().to_string());
+ },
+ }
+ }
+ None
+}
+
+/// returns list of all pairs (a, b) from `exprs` such that `eq(a, b)`
+/// `hash` must be comformed with `eq`
+pub fn search_same<T, Hash, Eq>(exprs: &[T], hash: Hash, eq: Eq) -> Vec<(&T, &T)>
+where
+ Hash: Fn(&T) -> u64,
+ Eq: Fn(&T, &T) -> bool,
+{
+ if exprs.len() == 2 && eq(&exprs[0], &exprs[1]) {
+ return vec![(&exprs[0], &exprs[1])];
+ }
+
+ let mut match_expr_list: Vec<(&T, &T)> = Vec::new();
+
+ let mut map: FxHashMap<_, Vec<&_>> =
+ FxHashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default());
+
+ for expr in exprs {
+ match map.entry(hash(expr)) {
+ Entry::Occupied(mut o) => {
+ for o in o.get() {
+ if eq(o, expr) {
+ match_expr_list.push((o, expr));
+ }
+ }
+ o.get_mut().push(expr);
+ },
+ Entry::Vacant(v) => {
+ v.insert(vec![expr]);
+ },
+ }
+ }
+
+ match_expr_list
+}
+
+/// Peels off all references on the pattern. Returns the underlying pattern and the number of
+/// references removed.
+pub fn peel_hir_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
+ fn peel(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) {
+ if let PatKind::Ref(pat, _) = pat.kind {
+ peel(pat, count + 1)
+ } else {
+ (pat, count)
+ }
+ }
+ peel(pat, 0)
+}
+
+/// Peels of expressions while the given closure returns `Some`.
+pub fn peel_hir_expr_while<'tcx>(
+ mut expr: &'tcx Expr<'tcx>,
+ mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
+) -> &'tcx Expr<'tcx> {
+ while let Some(e) = f(expr) {
+ expr = e;
+ }
+ expr
+}
+
+/// Peels off up to the given number of references on the expression. Returns the underlying
+/// expression and the number of references removed.
+pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
+ let mut remaining = count;
+ let e = peel_hir_expr_while(expr, |e| match e.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, _, e) if remaining != 0 => {
+ remaining -= 1;
+ Some(e)
+ },
+ _ => None,
+ });
+ (e, count - remaining)
+}
+
+/// Peels off all references on the expression. Returns the underlying expression and the number of
+/// references removed.
+pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
+ let mut count = 0;
+ let e = peel_hir_expr_while(expr, |e| match e.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, _, e) => {
+ count += 1;
+ Some(e)
+ },
+ _ => None,
+ });
+ (e, count)
+}
+
+#[macro_export]
+macro_rules! unwrap_cargo_metadata {
+ ($cx: ident, $lint: ident, $deps: expr) => {{
+ let mut command = cargo_metadata::MetadataCommand::new();
+ if !$deps {
+ command.no_deps();
+ }
+
+ match command.exec() {
+ Ok(metadata) => metadata,
+ Err(err) => {
+ span_lint($cx, $lint, DUMMY_SP, &format!("could not read cargo metadata: {}", err));
+ return;
+ },
+ }
+ }};
+}
+
+pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
+ if_chain! {
+ if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
+ if let Res::Def(_, def_id) = path.res;
+ then {
+ cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr)
+ } else {
+ false
+ }
+ }
+}
--- /dev/null
- #[cfg(feature = "internal-lints")]
+//! This module contains paths to types and functions Clippy needs to know
+//! about.
+//!
+//! Whenever possible, please consider diagnostic items over hardcoded paths.
+//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
+
+pub const ANY_TRAIT: [&str; 3] = ["core", "any", "Any"];
++#[cfg(feature = "metadata-collector-lint")]
++pub const APPLICABILITY: [&str; 2] = ["rustc_lint_defs", "Applicability"];
++#[cfg(feature = "metadata-collector-lint")]
++pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
++ ["rustc_lint_defs", "Applicability", "Unspecified"],
++ ["rustc_lint_defs", "Applicability", "HasPlaceholders"],
++ ["rustc_lint_defs", "Applicability", "MaybeIncorrect"],
++ ["rustc_lint_defs", "Applicability", "MachineApplicable"],
++];
++#[cfg(feature = "metadata-collector-lint")]
++pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"];
+pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
+pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
+pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
+pub(super) const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
+pub(super) const BEGIN_PANIC_FMT: [&str; 3] = ["std", "panicking", "begin_panic_fmt"];
+pub const BINARY_HEAP: [&str; 4] = ["alloc", "collections", "binary_heap", "BinaryHeap"];
+pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"];
+pub const BTREEMAP: [&str; 5] = ["alloc", "collections", "btree", "map", "BTreeMap"];
+pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"];
+pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"];
+pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"];
+pub const BTREESET: [&str; 5] = ["alloc", "collections", "btree", "set", "BTreeSet"];
+pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
+pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"];
+pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"];
+pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
+pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"];
+pub const DEFAULT_TRAIT: [&str; 3] = ["core", "default", "Default"];
+pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
+pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"];
+pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
+pub const DIR_BUILDER: [&str; 3] = ["std", "fs", "DirBuilder"];
+pub const DISPLAY_FMT_METHOD: [&str; 4] = ["core", "fmt", "Display", "fmt"];
+pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
+pub const DOUBLE_ENDED_ITERATOR: [&str; 4] = ["core", "iter", "traits", "DoubleEndedIterator"];
+pub const DROP: [&str; 3] = ["core", "mem", "drop"];
+pub const DURATION: [&str; 3] = ["core", "time", "Duration"];
+#[cfg(feature = "internal-lints")]
+pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
+pub const EXIT: [&str; 3] = ["std", "process", "exit"];
+pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
+pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
+pub const FILE: [&str; 3] = ["std", "fs", "File"];
+pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
+pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
+pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments", "new_v1_formatted"];
+pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
+pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
+pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "FromIterator"];
+pub const FROM_ITERATOR_METHOD: [&str; 6] = ["core", "iter", "traits", "collect", "FromIterator", "from_iter"];
+pub const FROM_STR_METHOD: [&str; 5] = ["core", "str", "traits", "FromStr", "from_str"];
+pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
+pub const HASH: [&str; 3] = ["core", "hash", "Hash"];
+pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
+pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"];
+pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
+pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"];
+pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"];
+#[cfg(feature = "internal-lints")]
+pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
+#[cfg(feature = "internal-lints")]
+pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
+pub const INDEX: [&str; 3] = ["core", "ops", "Index"];
+pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"];
+pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];
+pub const INTO: [&str; 3] = ["core", "convert", "Into"];
+pub const INTO_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "IntoIterator"];
+pub const IO_READ: [&str; 3] = ["std", "io", "Read"];
+pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
+pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"];
+pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"];
+pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"];
+#[cfg(feature = "internal-lints")]
+pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
+#[cfg(feature = "internal-lints")]
+pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
+pub const LINKED_LIST: [&str; 4] = ["alloc", "collections", "linked_list", "LinkedList"];
++#[cfg(any(feature = "internal-lints", feature = "metadata-collector-lint"))]
+pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
+pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"];
+pub const MEM_FORGET: [&str; 3] = ["core", "mem", "forget"];
+pub const MEM_MANUALLY_DROP: [&str; 4] = ["core", "mem", "manually_drop", "ManuallyDrop"];
+pub const MEM_MAYBEUNINIT: [&str; 4] = ["core", "mem", "maybe_uninit", "MaybeUninit"];
+pub const MEM_MAYBEUNINIT_UNINIT: [&str; 5] = ["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"];
+pub const MEM_REPLACE: [&str; 3] = ["core", "mem", "replace"];
+pub const MEM_SIZE_OF: [&str; 3] = ["core", "mem", "size_of"];
+pub const MEM_SIZE_OF_VAL: [&str; 3] = ["core", "mem", "size_of_val"];
+pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
+pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
+pub const OPS_MODULE: [&str; 2] = ["core", "ops"];
+pub const OPTION: [&str; 3] = ["core", "option", "Option"];
+pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"];
+pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
+pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
+pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
+pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
+pub(super) const PANICKING_PANIC: [&str; 3] = ["core", "panicking", "panic"];
+pub(super) const PANICKING_PANIC_FMT: [&str; 3] = ["core", "panicking", "panic_fmt"];
+pub(super) const PANICKING_PANIC_STR: [&str; 3] = ["core", "panicking", "panic_str"];
+pub(super) const PANIC_ANY: [&str; 3] = ["std", "panic", "panic_any"];
+pub const PARKING_LOT_MUTEX_GUARD: [&str; 2] = ["parking_lot", "MutexGuard"];
+pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 2] = ["parking_lot", "RwLockReadGuard"];
+pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 2] = ["parking_lot", "RwLockWriteGuard"];
+pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"];
+pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];
+pub const PERMISSIONS: [&str; 3] = ["std", "fs", "Permissions"];
+pub const PERMISSIONS_FROM_MODE: [&str; 7] = ["std", "os", "imp", "unix", "fs", "PermissionsExt", "from_mode"];
+pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
+pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
+pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
+pub const PTR_COPY: [&str; 4] = ["core", "intrinsics", "", "copy"];
+pub const PTR_COPY_NONOVERLAPPING: [&str; 4] = ["core", "intrinsics", "", "copy_nonoverlapping"];
+pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
+pub const PTR_NULL: [&str; 3] = ["core", "ptr", "null"];
+pub const PTR_NULL_MUT: [&str; 3] = ["core", "ptr", "null_mut"];
+pub const PTR_SLICE_FROM_RAW_PARTS: [&str; 3] = ["core", "ptr", "slice_from_raw_parts"];
+pub const PTR_SLICE_FROM_RAW_PARTS_MUT: [&str; 3] = ["core", "ptr", "slice_from_raw_parts_mut"];
+pub const PTR_SWAP_NONOVERLAPPING: [&str; 3] = ["core", "ptr", "swap_nonoverlapping"];
+pub const PTR_READ: [&str; 3] = ["core", "ptr", "read"];
+pub const PTR_READ_UNALIGNED: [&str; 3] = ["core", "ptr", "read_unaligned"];
+pub const PTR_READ_VOLATILE: [&str; 3] = ["core", "ptr", "read_volatile"];
+pub const PTR_REPLACE: [&str; 3] = ["core", "ptr", "replace"];
+pub const PTR_SWAP: [&str; 3] = ["core", "ptr", "swap"];
+pub const PTR_WRITE: [&str; 3] = ["core", "ptr", "write"];
+pub const PTR_WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];
+pub const PTR_WRITE_UNALIGNED: [&str; 3] = ["core", "ptr", "write_unaligned"];
+pub const PTR_WRITE_VOLATILE: [&str; 3] = ["core", "ptr", "write_volatile"];
+pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
+pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
+pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
+pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"];
+pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"];
+pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"];
+pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"];
+pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
+pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
+pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
+pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
+pub const RESULT: [&str; 3] = ["core", "result", "Result"];
+pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
+pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
+pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
+pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
+pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
+pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
+pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];
+pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"];
+pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
+pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"];
+pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
+pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
+pub const CONVERT_IDENTITY: [&str; 3] = ["core", "convert", "identity"];
+pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
+pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"];
+pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
+pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];
+pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"];
+pub const STR_LEN: [&str; 4] = ["core", "str", "<impl str>", "len"];
+pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "<impl str>", "starts_with"];
+#[cfg(feature = "internal-lints")]
+pub const SYMBOL: [&str; 3] = ["rustc_span", "symbol", "Symbol"];
+#[cfg(feature = "internal-lints")]
+pub const SYMBOL_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Symbol", "as_str"];
+#[cfg(feature = "internal-lints")]
+pub const SYMBOL_INTERN: [&str; 4] = ["rustc_span", "symbol", "Symbol", "intern"];
+#[cfg(feature = "internal-lints")]
+pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"];
+#[cfg(feature = "internal-lints")]
+pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
+#[cfg(feature = "internal-lints")]
+pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
+pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"];
+pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"];
+pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
+pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"];
+pub const TRY_INTO_TRAIT: [&str; 3] = ["core", "convert", "TryInto"];
+pub const VEC: [&str; 3] = ["alloc", "vec", "Vec"];
+pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"];
+pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
+pub const VEC_DEQUE: [&str; 4] = ["alloc", "collections", "vec_deque", "VecDeque"];
+pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
+pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
+pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
+pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
+pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
+pub const WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];
--- /dev/null
- let outer_span = hygiene::walk_chain(span, outer);
- let (span, is_macro_call) = if outer_span.ctxt() == outer {
- (outer_span, span.ctxt() != outer)
- } else {
- // The span is from a macro argument, and the outer context is the macro using the argument
- if *applicability != Applicability::Unspecified {
- *applicability = Applicability::MaybeIncorrect;
- }
- // TODO: get the argument span.
- (span, false)
- };
+//! Utils for extracting, inspecting or transforming source code
+
+#![allow(clippy::module_name_repetitions)]
+
+use crate::line_span;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LintContext};
+use rustc_span::hygiene;
+use rustc_span::{BytePos, Pos, Span, SyntaxContext};
+use std::borrow::Cow;
+
+/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
+/// Also takes an `Option<String>` which can be put inside the braces.
+pub fn expr_block<'a, T: LintContext>(
+ cx: &T,
+ expr: &Expr<'_>,
+ option: Option<String>,
+ default: &'a str,
+ indent_relative_to: Option<Span>,
+) -> Cow<'a, str> {
+ let code = snippet_block(cx, expr.span, default, indent_relative_to);
+ let string = option.unwrap_or_default();
+ if expr.span.from_expansion() {
+ Cow::Owned(format!("{{ {} }}", snippet_with_macro_callsite(cx, expr.span, default)))
+ } else if let ExprKind::Block(_, _) = expr.kind {
+ Cow::Owned(format!("{}{}", code, string))
+ } else if string.is_empty() {
+ Cow::Owned(format!("{{ {} }}", code))
+ } else {
+ Cow::Owned(format!("{{\n{};\n{}\n}}", code, string))
+ }
+}
+
+/// Returns a new Span that extends the original Span to the first non-whitespace char of the first
+/// line.
+///
+/// ```rust,ignore
+/// let x = ();
+/// // ^^
+/// // will be converted to
+/// let x = ();
+/// // ^^^^^^^^^^
+/// ```
+pub fn first_line_of_span<T: LintContext>(cx: &T, span: Span) -> Span {
+ first_char_in_first_line(cx, span).map_or(span, |first_char_pos| span.with_lo(first_char_pos))
+}
+
+fn first_char_in_first_line<T: LintContext>(cx: &T, span: Span) -> Option<BytePos> {
+ let line_span = line_span(cx, span);
+ snippet_opt(cx, line_span).and_then(|snip| {
+ snip.find(|c: char| !c.is_whitespace())
+ .map(|pos| line_span.lo() + BytePos::from_usize(pos))
+ })
+}
+
+/// Returns the indentation of the line of a span
+///
+/// ```rust,ignore
+/// let x = ();
+/// // ^^ -- will return 0
+/// let x = ();
+/// // ^^ -- will return 4
+/// ```
+pub fn indent_of<T: LintContext>(cx: &T, span: Span) -> Option<usize> {
+ snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace()))
+}
+
+/// Gets a snippet of the indentation of the line of a span
+pub fn snippet_indent<T: LintContext>(cx: &T, span: Span) -> Option<String> {
+ snippet_opt(cx, line_span(cx, span)).map(|mut s| {
+ let len = s.len() - s.trim_start().len();
+ s.truncate(len);
+ s
+ })
+}
+
+// If the snippet is empty, it's an attribute that was inserted during macro
+// expansion and we want to ignore those, because they could come from external
+// sources that the user has no control over.
+// For some reason these attributes don't have any expansion info on them, so
+// we have to check it this way until there is a better way.
+pub fn is_present_in_source<T: LintContext>(cx: &T, span: Span) -> bool {
+ if let Some(snippet) = snippet_opt(cx, span) {
+ if snippet.is_empty() {
+ return false;
+ }
+ }
+ true
+}
+
+/// Returns the positon just before rarrow
+///
+/// ```rust,ignore
+/// fn into(self) -> () {}
+/// ^
+/// // in case of unformatted code
+/// fn into2(self)-> () {}
+/// ^
+/// fn into3(self) -> () {}
+/// ^
+/// ```
+pub fn position_before_rarrow(s: &str) -> Option<usize> {
+ s.rfind("->").map(|rpos| {
+ let mut rpos = rpos;
+ let chars: Vec<char> = s.chars().collect();
+ while rpos > 1 {
+ if let Some(c) = chars.get(rpos - 1) {
+ if c.is_whitespace() {
+ rpos -= 1;
+ continue;
+ }
+ }
+ break;
+ }
+ rpos
+ })
+}
+
+/// Reindent a multiline string with possibility of ignoring the first line.
+#[allow(clippy::needless_pass_by_value)]
+pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<usize>) -> Cow<'_, str> {
+ let s_space = reindent_multiline_inner(&s, ignore_first, indent, ' ');
+ let s_tab = reindent_multiline_inner(&s_space, ignore_first, indent, '\t');
+ reindent_multiline_inner(&s_tab, ignore_first, indent, ' ').into()
+}
+
+fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>, ch: char) -> String {
+ let x = s
+ .lines()
+ .skip(ignore_first as usize)
+ .filter_map(|l| {
+ if l.is_empty() {
+ None
+ } else {
+ // ignore empty lines
+ Some(l.char_indices().find(|&(_, x)| x != ch).unwrap_or((l.len(), ch)).0)
+ }
+ })
+ .min()
+ .unwrap_or(0);
+ let indent = indent.unwrap_or(0);
+ s.lines()
+ .enumerate()
+ .map(|(i, l)| {
+ if (ignore_first && i == 0) || l.is_empty() {
+ l.to_owned()
+ } else if x > indent {
+ l.split_at(x - indent).1.to_owned()
+ } else {
+ " ".repeat(indent - x) + l
+ }
+ })
+ .collect::<Vec<String>>()
+ .join("\n")
+}
+
+/// Converts a span to a code snippet if available, otherwise use default.
+///
+/// This is useful if you want to provide suggestions for your lint or more generally, if you want
+/// to convert a given `Span` to a `str`.
+///
+/// # Example
+/// ```rust,ignore
+/// snippet(cx, expr.span, "..")
+/// ```
+pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
+ snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from)
+}
+
+/// Same as `snippet`, but it adapts the applicability level by following rules:
+///
+/// - Applicability level `Unspecified` will never be changed.
+/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
+/// - If the default value is used and the applicability level is `MachineApplicable`, change it to
+/// `HasPlaceholders`
+pub fn snippet_with_applicability<'a, T: LintContext>(
+ cx: &T,
+ span: Span,
+ default: &'a str,
+ applicability: &mut Applicability,
+) -> Cow<'a, str> {
+ if *applicability != Applicability::Unspecified && span.from_expansion() {
+ *applicability = Applicability::MaybeIncorrect;
+ }
+ snippet_opt(cx, span).map_or_else(
+ || {
+ if *applicability == Applicability::MachineApplicable {
+ *applicability = Applicability::HasPlaceholders;
+ }
+ Cow::Borrowed(default)
+ },
+ From::from,
+ )
+}
+
+/// Same as `snippet`, but should only be used when it's clear that the input span is
+/// not a macro argument.
+pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
+ snippet(cx, span.source_callsite(), default)
+}
+
+/// Converts a span to a code snippet. Returns `None` if not available.
+pub fn snippet_opt<T: LintContext>(cx: &T, span: Span) -> Option<String> {
+ cx.sess().source_map().span_to_snippet(span).ok()
+}
+
+/// Converts a span (from a block) to a code snippet if available, otherwise use default.
+///
+/// This trims the code of indentation, except for the first line. Use it for blocks or block-like
+/// things which need to be printed as such.
+///
+/// The `indent_relative_to` arg can be used, to provide a span, where the indentation of the
+/// resulting snippet of the given span.
+///
+/// # Example
+///
+/// ```rust,ignore
+/// snippet_block(cx, block.span, "..", None)
+/// // where, `block` is the block of the if expr
+/// if x {
+/// y;
+/// }
+/// // will return the snippet
+/// {
+/// y;
+/// }
+/// ```
+///
+/// ```rust,ignore
+/// snippet_block(cx, block.span, "..", Some(if_expr.span))
+/// // where, `block` is the block of the if expr
+/// if x {
+/// y;
+/// }
+/// // will return the snippet
+/// {
+/// y;
+/// } // aligned with `if`
+/// ```
+/// Note that the first line of the snippet always has 0 indentation.
+pub fn snippet_block<'a, T: LintContext>(
+ cx: &T,
+ span: Span,
+ default: &'a str,
+ indent_relative_to: Option<Span>,
+) -> Cow<'a, str> {
+ let snip = snippet(cx, span, default);
+ let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
+ reindent_multiline(snip, true, indent)
+}
+
+/// Same as `snippet_block`, but adapts the applicability level by the rules of
+/// `snippet_with_applicability`.
+pub fn snippet_block_with_applicability<'a, T: LintContext>(
+ cx: &T,
+ span: Span,
+ default: &'a str,
+ indent_relative_to: Option<Span>,
+ applicability: &mut Applicability,
+) -> Cow<'a, str> {
+ let snip = snippet_with_applicability(cx, span, default, applicability);
+ let indent = indent_relative_to.and_then(|s| indent_of(cx, s));
+ reindent_multiline(snip, true, indent)
+}
+
+/// Same as `snippet_with_applicability`, but first walks the span up to the given context. This
+/// will result in the macro call, rather then the expansion, if the span is from a child context.
+/// If the span is not from a child context, it will be used directly instead.
+///
+/// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR node
+/// would result in `box []`. If given the context of the address of expression, this function will
+/// correctly get a snippet of `vec![]`.
+///
+/// This will also return whether or not the snippet is a macro call.
+pub fn snippet_with_context(
+ cx: &LateContext<'_>,
+ span: Span,
+ outer: SyntaxContext,
+ default: &'a str,
+ applicability: &mut Applicability,
+) -> (Cow<'a, str>, bool) {
++ let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else(
++ || {
++ // The span is from a macro argument, and the outer context is the macro using the argument
++ if *applicability != Applicability::Unspecified {
++ *applicability = Applicability::MaybeIncorrect;
++ }
++ // TODO: get the argument span.
++ (span, false)
++ },
++ |outer_span| (outer_span, span.ctxt() != outer),
++ );
+
+ (
+ snippet_with_applicability(cx, span, default, applicability),
+ is_macro_call,
+ )
+}
+
++/// Walks the span up to the target context, thereby returning the macro call site if the span is
++/// inside a macro expansion, or the original span if it is not. Note this will return `None` in the
++/// case of the span being in a macro expansion, but the target context is from expanding a macro
++/// argument.
++///
++/// Given the following
++///
++/// ```rust,ignore
++/// macro_rules! m { ($e:expr) => { f($e) }; }
++/// g(m!(0))
++/// ```
++///
++/// If called with a span of the call to `f` and a context of the call to `g` this will return a
++/// span containing `m!(0)`. However, if called with a span of the literal `0` this will give a span
++/// containing `0` as the context is the same as the outer context.
++///
++/// This will traverse through multiple macro calls. Given the following:
++///
++/// ```rust,ignore
++/// macro_rules! m { ($e:expr) => { n!($e, 0) }; }
++/// macro_rules! n { ($e:expr, $f:expr) => { f($e, $f) }; }
++/// g(m!(0))
++/// ```
++///
++/// If called with a span of the call to `f` and a context of the call to `g` this will return a
++/// span containing `m!(0)`.
++pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option<Span> {
++ let outer_span = hygiene::walk_chain(span, outer);
++ (outer_span.ctxt() == outer).then(|| outer_span)
++}
++
+/// Removes block comments from the given `Vec` of lines.
+///
+/// # Examples
+///
+/// ```rust,ignore
+/// without_block_comments(vec!["/*", "foo", "*/"]);
+/// // => vec![]
+///
+/// without_block_comments(vec!["bar", "/*", "foo", "*/"]);
+/// // => vec!["bar"]
+/// ```
+pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> {
+ let mut without = vec![];
+
+ let mut nest_level = 0;
+
+ for line in lines {
+ if line.contains("/*") {
+ nest_level += 1;
+ continue;
+ } else if line.contains("*/") {
+ nest_level -= 1;
+ continue;
+ }
+
+ if nest_level == 0 {
+ without.push(line);
+ }
+ }
+
+ without
+}
+
+#[cfg(test)]
+mod test {
+ use super::{reindent_multiline, without_block_comments};
+
+ #[test]
+ fn test_reindent_multiline_single_line() {
+ assert_eq!("", reindent_multiline("".into(), false, None));
+ assert_eq!("...", reindent_multiline("...".into(), false, None));
+ assert_eq!("...", reindent_multiline(" ...".into(), false, None));
+ assert_eq!("...", reindent_multiline("\t...".into(), false, None));
+ assert_eq!("...", reindent_multiline("\t\t...".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_block() {
+ assert_eq!("\
+ if x {
+ y
+ } else {
+ z
+ }", reindent_multiline(" if x {
+ y
+ } else {
+ z
+ }".into(), false, None));
+ assert_eq!("\
+ if x {
+ \ty
+ } else {
+ \tz
+ }", reindent_multiline(" if x {
+ \ty
+ } else {
+ \tz
+ }".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_empty_line() {
+ assert_eq!("\
+ if x {
+ y
+
+ } else {
+ z
+ }", reindent_multiline(" if x {
+ y
+
+ } else {
+ z
+ }".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_lines_deeper() {
+ assert_eq!("\
+ if x {
+ y
+ } else {
+ z
+ }", reindent_multiline("\
+ if x {
+ y
+ } else {
+ z
+ }".into(), true, Some(8)));
+ }
+
+ #[test]
+ fn test_without_block_comments_lines_without_block_comments() {
+ let result = without_block_comments(vec!["/*", "", "*/"]);
+ println!("result: {:?}", result);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]);
+ assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]);
+
+ let result = without_block_comments(vec!["/* rust", "", "*/"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* one-line comment */"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]);
+ assert!(result.is_empty());
+
+ let result = without_block_comments(vec!["foo", "bar", "baz"]);
+ assert_eq!(result, vec!["foo", "bar", "baz"]);
+ }
+}
--- /dev/null
- use rustc_hir::intravisit::{self, walk_expr, NestedVisitorMap, Visitor};
- use rustc_hir::{Arm, Body, Expr, HirId, Stmt};
+use crate::path_to_local_id;
+use rustc_hir as hir;
++use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor};
++use rustc_hir::{Arm, Block, Body, Destination, Expr, ExprKind, HirId, Stmt};
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
+
+/// returns `true` if expr contains match expr desugared from try
+fn contains_try(expr: &hir::Expr<'_>) -> bool {
+ struct TryFinder {
+ found: bool,
+ }
+
+ impl<'hir> intravisit::Visitor<'hir> for TryFinder {
+ type Map = Map<'hir>;
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::None
+ }
+
+ fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
+ if self.found {
+ return;
+ }
+ match expr.kind {
+ hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar) => self.found = true,
+ _ => intravisit::walk_expr(self, expr),
+ }
+ }
+ }
+
+ let mut visitor = TryFinder { found: false };
+ visitor.visit_expr(expr);
+ visitor.found
+}
+
+pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
+where
+ F: FnMut(&'hir hir::Expr<'hir>) -> bool,
+{
+ struct RetFinder<F> {
+ in_stmt: bool,
+ failed: bool,
+ cb: F,
+ }
+
+ struct WithStmtGuarg<'a, F> {
+ val: &'a mut RetFinder<F>,
+ prev_in_stmt: bool,
+ }
+
+ impl<F> RetFinder<F> {
+ fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
+ let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
+ WithStmtGuarg {
+ val: self,
+ prev_in_stmt,
+ }
+ }
+ }
+
+ impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
+ type Target = RetFinder<F>;
+
+ fn deref(&self) -> &Self::Target {
+ self.val
+ }
+ }
+
+ impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.val
+ }
+ }
+
+ impl<F> Drop for WithStmtGuarg<'_, F> {
+ fn drop(&mut self) {
+ self.val.in_stmt = self.prev_in_stmt;
+ }
+ }
+
+ impl<'hir, F: FnMut(&'hir hir::Expr<'hir>) -> bool> intravisit::Visitor<'hir> for RetFinder<F> {
+ type Map = Map<'hir>;
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::None
+ }
+
+ fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'_>) {
+ intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt)
+ }
+
+ fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) {
+ if self.failed {
+ return;
+ }
+ if self.in_stmt {
+ match expr.kind {
+ hir::ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr),
+ _ => intravisit::walk_expr(self, expr),
+ }
+ } else {
+ match expr.kind {
+ hir::ExprKind::If(cond, then, else_opt) => {
+ self.inside_stmt(true).visit_expr(cond);
+ self.visit_expr(then);
+ if let Some(el) = else_opt {
+ self.visit_expr(el);
+ }
+ },
+ hir::ExprKind::Match(cond, arms, _) => {
+ self.inside_stmt(true).visit_expr(cond);
+ for arm in arms {
+ self.visit_expr(arm.body);
+ }
+ },
+ hir::ExprKind::Block(..) => intravisit::walk_expr(self, expr),
+ hir::ExprKind::Ret(Some(expr)) => self.visit_expr(expr),
+ _ => self.failed |= !(self.cb)(expr),
+ }
+ }
+ }
+ }
+
+ !contains_try(expr) && {
+ let mut ret_finder = RetFinder {
+ in_stmt: false,
+ failed: false,
+ cb: callback,
+ };
+ ret_finder.visit_expr(expr);
+ !ret_finder.failed
+ }
+}
+
+pub struct LocalUsedVisitor<'hir> {
+ hir: Map<'hir>,
+ pub local_hir_id: HirId,
+ pub used: bool,
+}
+
+impl<'hir> LocalUsedVisitor<'hir> {
+ pub fn new(cx: &LateContext<'hir>, local_hir_id: HirId) -> Self {
+ Self {
+ hir: cx.tcx.hir(),
+ local_hir_id,
+ used: false,
+ }
+ }
+
+ fn check<T>(&mut self, t: T, visit: fn(&mut Self, T)) -> bool {
+ visit(self, t);
+ std::mem::replace(&mut self.used, false)
+ }
+
+ pub fn check_arm(&mut self, arm: &'hir Arm<'_>) -> bool {
+ self.check(arm, Self::visit_arm)
+ }
+
+ pub fn check_body(&mut self, body: &'hir Body<'_>) -> bool {
+ self.check(body, Self::visit_body)
+ }
+
+ pub fn check_expr(&mut self, expr: &'hir Expr<'_>) -> bool {
+ self.check(expr, Self::visit_expr)
+ }
+
+ pub fn check_stmt(&mut self, stmt: &'hir Stmt<'_>) -> bool {
+ self.check(stmt, Self::visit_stmt)
+ }
+}
+
+impl<'v> Visitor<'v> for LocalUsedVisitor<'v> {
+ type Map = Map<'v>;
+
+ fn visit_expr(&mut self, expr: &'v Expr<'v>) {
+ if self.used {
+ return;
+ }
+ if path_to_local_id(expr, self.local_hir_id) {
+ self.used = true;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::OnlyBodies(self.hir)
+ }
+}
++
++pub trait Visitable<'tcx> {
++ fn visit<V: Visitor<'tcx>>(self, v: &mut V);
++}
++impl Visitable<'tcx> for &'tcx Expr<'tcx> {
++ fn visit<V: Visitor<'tcx>>(self, v: &mut V) {
++ v.visit_expr(self)
++ }
++}
++impl Visitable<'tcx> for &'tcx Block<'tcx> {
++ fn visit<V: Visitor<'tcx>>(self, v: &mut V) {
++ v.visit_block(self)
++ }
++}
++impl<'tcx> Visitable<'tcx> for &'tcx Stmt<'tcx> {
++ fn visit<V: Visitor<'tcx>>(self, v: &mut V) {
++ v.visit_stmt(self)
++ }
++}
++impl<'tcx> Visitable<'tcx> for &'tcx Body<'tcx> {
++ fn visit<V: Visitor<'tcx>>(self, v: &mut V) {
++ v.visit_body(self)
++ }
++}
++impl<'tcx> Visitable<'tcx> for &'tcx Arm<'tcx> {
++ fn visit<V: Visitor<'tcx>>(self, v: &mut V) {
++ v.visit_arm(self)
++ }
++}
++
++pub fn visit_break_exprs<'tcx>(
++ node: impl Visitable<'tcx>,
++ f: impl FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>),
++) {
++ struct V<F>(F);
++ impl<'tcx, F: FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>)> Visitor<'tcx> for V<F> {
++ type Map = ErasedMap<'tcx>;
++ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
++ NestedVisitorMap::None
++ }
++
++ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
++ if let ExprKind::Break(dest, sub_expr) = e.kind {
++ self.0(e, dest, sub_expr)
++ }
++ walk_expr(self, e);
++ }
++ }
++
++ node.visit(&mut V(f));
++}
--- /dev/null
- (msrv, "msrv": Option<String>, None),
+# Adding a new lint
+
+You are probably here because you want to add a new lint to Clippy. If this is
+the first time you're contributing to Clippy, this document guides you through
+creating an example lint from scratch.
+
+To get started, we will create a lint that detects functions called `foo`,
+because that's clearly a non-descriptive name.
+
+- [Adding a new lint](#adding-a-new-lint)
+ - [Setup](#setup)
+ - [Getting Started](#getting-started)
+ - [Testing](#testing)
+ - [Rustfix tests](#rustfix-tests)
+ - [Edition 2018 tests](#edition-2018-tests)
+ - [Testing manually](#testing-manually)
+ - [Lint declaration](#lint-declaration)
+ - [Lint passes](#lint-passes)
+ - [Emitting a lint](#emitting-a-lint)
+ - [Adding the lint logic](#adding-the-lint-logic)
+ - [Specifying the lint's minimum supported Rust version (MSRV)](#specifying-the-lints-minimum-supported-rust-version-msrv)
+ - [Author lint](#author-lint)
+ - [Documentation](#documentation)
+ - [Running rustfmt](#running-rustfmt)
+ - [Debugging](#debugging)
+ - [PR Checklist](#pr-checklist)
+ - [Adding configuration to a lint](#adding-configuration-to-a-lint)
+ - [Cheatsheet](#cheatsheet)
+
+## Setup
+
+See the [Basics](basics.md#get-the-code) documentation.
+
+## Getting Started
+
+There is a bit of boilerplate code that needs to be set up when creating a new
+lint. Fortunately, you can use the clippy dev tools to handle this for you. We
+are naming our new lint `foo_functions` (lints are generally written in snake
+case), and we don't need type information so it will have an early pass type
+(more on this later on). If you're not sure if the name you chose fits the lint,
+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 run `cargo dev update_lints` to
+register the new lint. 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!
+
+## Testing
+
+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.
+
+We start by opening the test file created at `tests/ui/foo_functions.rs`.
+
+Update the file with some examples to get started:
+
+```rust
+#![warn(clippy::foo_functions)]
+
+// Impl methods
+struct A;
+impl A {
+ pub fn fo(&self) {}
+ pub fn foo(&self) {}
+ pub fn food(&self) {}
+}
+
+// Default trait methods
+trait B {
+ fn fo(&self) {}
+ fn foo(&self) {}
+ fn food(&self) {}
+}
+
+// Plain functions
+fn fo() {}
+fn foo() {}
+fn food() {}
+
+fn main() {
+ // We also don't want to lint method calls
+ foo();
+ let a = A;
+ a.foo();
+}
+```
+
+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.
+
+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`.
+
+### 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.
+
+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.
+
+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.
+
+## 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
+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.
+
+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
+`println!`s and the test suite output becomes unreadable. To try Clippy with
+your local modifications, run
+
+```
+env __CLIPPY_INTERNAL_TESTS=true cargo run --bin clippy-driver -- -L ./target/debug input.rs
+```
+
+from the working copy root. With tests in place, let's have a look at
+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:
+
+```rust
+use rustc_lint::{EarlyLintPass, EarlyContext};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+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:
+
+```rust
+declare_clippy_lint! {
+ /// **What it does:**
+ ///
+ /// **Why is this bad?**
+ ///
+ /// **Known problems:** None.
+ ///
+ /// **Example:**
+ ///
+ /// ```rust
+ /// // example code
+ /// ```
+ pub FOO_FUNCTIONS,
+ pedantic,
+ "function named `foo`, which is not a descriptive name"
+}
+```
+
+* 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`.
+* `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:
+
+```rust
+// clippy_lints/src/foo_functions.rs
+
+// .. imports and lint declaration ..
+
+declare_lint_pass!(FooFunctions => [FOO_FUNCTIONS]);
+
+impl EarlyLintPass for FooFunctions {}
+```
+
+Normally after declaring the lint, we have to run `cargo dev update_lints`,
+which updates some files, so Clippy knows about the new lint. Since we used
+`cargo dev new_lint ...` to generate the lint declaration, this was done
+automatically. While `update_lints` automates most of the things, it doesn't
+automate everything. We will have to register our lint pass manually in the
+`register_plugins` function in `clippy_lints/src/lib.rs`:
+
+```rust
+store.register_early_pass(|| box foo_functions::FooFunctions);
+```
+
+As one may expect, there is a corresponding `register_late_pass` method
+available as well. Without a call to one of `register_early_pass` or
+`register_late_pass`, the lint pass in question will not be run.
+
+One reason that `cargo dev` does not automate this step is that multiple lints
+can use the same lint pass, so registering the lint pass may already be done
+when adding a new lint. Another reason that this step is not automated is that
+the order that the passes are registered determines the order the passes
+actually run, which in turn affects the order that any emitted lints are output
+in.
+
+[declare_clippy_lint]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L60
+[example_lint_page]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
+[lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints
+[category_level_mapping]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L110
+
+## Lint passes
+
+Writing a lint that only checks for the name of a function means that we only
+have to deal with the AST and don't have to deal with the type system at all.
+This is good, because it makes writing this particular lint less complicated.
+
+We have to make this decision with every new Clippy lint. It boils down to using
+either [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass].
+
+In short, the `LateLintPass` has access to type information while the
+`EarlyLintPass` doesn't. If you don't need access to type information, use the
+`EarlyLintPass`. The `EarlyLintPass` is also faster. However linting speed
+hasn't really been a concern with Clippy so far.
+
+Since we don't need type information for checking the function name, we used
+`--pass=early` when running the new lint automation and all the imports were
+added accordingly.
+
+[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
+
+## Emitting a lint
+
+With UI tests and the lint declaration in place, we can start working on the
+implementation of the lint logic.
+
+Let's start by implementing the `EarlyLintPass` for our `FooFunctions`:
+
+```rust
+impl EarlyLintPass for FooFunctions {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
+ // TODO: Emit lint here
+ }
+}
+```
+
+We implement the [`check_fn`][check_fn] method from the
+[`EarlyLintPass`][early_lint_pass] trait. This gives us access to various
+information about the function that is currently being checked. More on that in
+the next section. Let's worry about the details later and emit our lint for
+*every* function definition first.
+
+Depending on how complex we want our lint message to be, we can choose from a
+variety of lint emission functions. They can all be found in
+[`clippy_utils/src/diagnostics.rs`][diagnostics].
+
+`span_lint_and_help` seems most appropriate in this case. It allows us to
+provide an extra help message and we can't really suggest a better name
+automatically. This is how it looks:
+
+```rust
+impl EarlyLintPass for FooFunctions {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
+ span_lint_and_help(
+ cx,
+ FOO_FUNCTIONS,
+ span,
+ "function named `foo`",
+ None,
+ "consider using a more meaningful name"
+ );
+ }
+}
+```
+
+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 \`.
+
+[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
+[the rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/diagnostics.html
+
+## Adding the lint logic
+
+Writing the logic for your lint will most likely be different from our example,
+so this section is kept rather short.
+
+Using the [`check_fn`][check_fn] method gives us access to [`FnKind`][fn_kind]
+that has the [`FnKind::Fn`] variant. It provides access to the name of the
+function/method via an [`Ident`][ident].
+
+With that we can expand our `check_fn` method to:
+
+```rust
+impl EarlyLintPass for FooFunctions {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
+ if is_foo_fn(fn_kind) {
+ span_lint_and_help(
+ cx,
+ FOO_FUNCTIONS,
+ span,
+ "function named `foo`",
+ None,
+ "consider using a more meaningful name"
+ );
+ }
+ }
+}
+```
+
+We separate the lint conditional from the lint emissions because it makes the
+code a bit easier to read. In some cases this separation would also allow to
+write some unit tests (as opposed to only UI tests) for the separate function.
+
+In our example, `is_foo_fn` looks like:
+
+```rust
+// use statements, impl EarlyLintPass, check_fn, ..
+
+fn is_foo_fn(fn_kind: FnKind<'_>) -> bool {
+ match fn_kind {
+ FnKind::Fn(_, ident, ..) => {
+ // check if `fn` name is `foo`
+ ident.name.as_str() == "foo"
+ }
+ // ignore closures
+ FnKind::Closure(..) => false
+ }
+}
+```
+
+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.
+
+`cargo test` (as opposed to `cargo uitest`) will also ensure that our lint
+implementation is not violating any Clippy lints itself.
+
+That should be it for the lint implementation. Running `cargo test` should now
+pass.
+
+[fn_kind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html
+[`FnKind::Fn`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html#variant.Fn
+[ident]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Ident.html
+
+## 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.
+
+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! {
+ ..
+ 1,45,0 { STR_STRIP_PREFIX }
+}
+```
+
+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 {
+ msrv: Option<RustcVersion>,
+}
+
+impl ManualStrip {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+```
+
+The project's MSRV can then be matched against the feature MSRV in the LintPass
+using the `meets_msrv` utility function.
+
+``` rust
+if !meets_msrv(self.msrv.as_ref(), &msrvs::STR_STRIP_PREFIX) {
+ return;
+}
+```
+
+The project's MSRV can also be specified as an inner attribute, which overrides
+the value from `clippy.toml`. This can be accounted for using the
+`extract_msrv_attr!(LintContext)` macro and passing
+`LateContext`/`EarlyContext`.
+
+```rust
+impl<'tcx> LateLintPass<'tcx> for ManualStrip {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ ...
+ }
+ extract_msrv_attr!(LateContext);
+}
+```
+
+Once the `msrv` is added to the lint, a relevant test case should be added to
+`tests/ui/min_rust_version_attr.rs` which verifies that the lint isn't emitted
+if the project's MSRV is lower.
+
+As a last step, the lint should be added to the lint documentation. This is done
+in `clippy_lints/src/utils/conf.rs`:
+
+```rust
+define_Conf! {
+ /// Lint: LIST, OF, LINTS, <THE_NEWLY_ADDED_LINT>. The minimum rust version that the project supports
- (configuration_ident, "configuration_value": Type, DefaultValue),
++ (msrv: Option<String> = None),
+ ...
+}
+```
+
+## Author lint
+
+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.
+
+[Here][author_example] is an example on the playground.
+
+If the command was executed successfully, you can copy the code over to where
+you are implementing your lint.
+
+[author_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=9a12cb60e5c6ad4e3003ac6d5e63cf55
+
+## Documentation
+
+The final thing before submitting our PR is to add some documentation to our
+lint declaration.
+
+Please document your lint with a doc comment akin to the following:
+
+```rust
+declare_clippy_lint! {
+ /// **What it does:** Checks for ... (describe what the lint matches).
+ ///
+ /// **Why is this bad?** Supply the reason for linting the code.
+ ///
+ /// **Known problems:** None. (Or describe where it could go wrong.)
+ ///
+ /// **Example:**
+ ///
+ /// ```rust,ignore
+ /// // Bad
+ /// Insert a short example of code that triggers the lint
+ ///
+ /// // Good
+ /// Insert a short example of improved code that doesn't trigger the lint
+ /// ```
+ pub FOO_FUNCTIONS,
+ pedantic,
+ "function named `foo`, which is not a descriptive name"
+}
+```
+
+Once your lint is merged, this documentation will show up in the [lint
+list][lint_list].
+
+[lint_list]: https://rust-lang.github.io/rust-clippy/master/index.html
+
+## 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.
+
+It can be installed via `rustup`:
+
+```bash
+rustup component add rustfmt --toolchain=nightly
+```
+
+Use `cargo dev fmt` to format the whole codebase. Make sure that `rustfmt` is
+installed for the nightly toolchain.
+
+[Rustfmt]: https://github.com/rust-lang/rustfmt
+
+## Debugging
+
+If you want to debug parts of your lint implementation, you can use the [`dbg!`]
+macro anywhere in your code. Running the tests should then include the debug
+output in the `stdout` part.
+
+[`dbg!`]: https://doc.rust-lang.org/std/macro.dbg.html
+
+## PR Checklist
+
+Before submitting your PR make sure you followed all of the basic requirements:
+
+<!-- Sync this with `.github/PULL_REQUEST_TEMPLATE` -->
+
+- \[ ] Followed [lint naming conventions][lint_naming]
+- \[ ] Added passing UI tests (including committed `.stderr` file)
+- \[ ] `cargo test` passes locally
+- \[ ] Executed `cargo dev update_lints`
+- \[ ] Added lint documentation
+- \[ ] Run `cargo dev fmt`
+
+## 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:
+
+1. Adding a new configuration entry to [clippy_utils::conf](/clippy_utils/src/conf.rs)
+ like this:
+ ```rust
+ /// Lint: LINT_NAME. <The configuration field doc comment>
++ (configuration_ident: Type = DefaultValue),
+ ```
+ The configuration value and identifier should usually be the same. The doc comment will be
+ automatically added to the lint documentation.
+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
+ ]);
+
+ // New manual definition struct
+ #[derive(Copy, Clone)]
+ pub struct StructName {}
+
+ 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,
+ }
+
+ // ...
+
+ 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);
+
+ // 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.
+
+## Cheatsheet
+
+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 (`implements_trait`, `match_def_path`, `snippet`, etc)
+* [Clippy diagnostics][diagnostics]
+* [The `if_chain` macro][if_chain]
+* [`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
+* [The nightly rustc docs][nightly_docs] which has been linked to throughout
+ this guide
+
+For `EarlyLintPass` lints:
+
+* [`EarlyLintPass`][early_lint_pass]
+* [`rustc_ast::ast`][ast]
+
+For `LateLintPass` lints:
+
+* [`LateLintPass`][late_lint_pass]
+* [`Ty::TyKind`][ty]
+
+While most of Clippy's lint utils are documented, most of rustc's internals lack
+documentation currently. This is unfortunate, but in most cases you can probably
+get away with copying things from existing similar lints. If you are stuck,
+don't hesitate to ask on [Zulip] or in the issue/PR.
+
+[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/lib.rs
+[if_chain]: https://docs.rs/if_chain/*/if_chain/
+[from_expansion]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion
+[in_external_macro]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/lint/fn.in_external_macro.html
+[span]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html
+[applicability]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/enum.Applicability.html
+[rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/
+[nightly_docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/
+[ast]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/index.html
+[ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/sty/index.html
+[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy
--- /dev/null
- channel = "nightly-2021-04-22"
+[toolchain]
++channel = "nightly-2021-05-06"
+components = ["llvm-tools-preview", "rustc-dev", "rust-src"]
--- /dev/null
- let conf = clippy_lints::read_conf(&[], sess);
+#![feature(rustc_private)]
+#![feature(once_cell)]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+// warn on lints, that are included in `rust-lang/rust`s bootstrap
+#![warn(rust_2018_idioms, unused_lifetimes)]
+// warn on rustc internal lints
+#![warn(rustc::internal)]
+
+// FIXME: switch to something more ergonomic here, once available.
+// (Currently there is no way to opt into sysroot crates without `extern crate`.)
+extern crate rustc_driver;
+extern crate rustc_errors;
+extern crate rustc_interface;
+extern crate rustc_session;
+extern crate rustc_span;
+
+use rustc_interface::interface;
+use rustc_session::parse::ParseSess;
+use rustc_span::symbol::Symbol;
+use rustc_tools_util::VersionInfo;
+
+use std::borrow::Cow;
+use std::env;
+use std::lazy::SyncLazy;
+use std::ops::Deref;
+use std::panic;
+use std::path::{Path, PathBuf};
+use std::process::{exit, Command};
+
+/// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If
+/// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`.
+fn arg_value<'a, T: Deref<Target = str>>(
+ args: &'a [T],
+ find_arg: &str,
+ pred: impl Fn(&str) -> bool,
+) -> Option<&'a str> {
+ let mut args = args.iter().map(Deref::deref);
+ while let Some(arg) = args.next() {
+ let mut arg = arg.splitn(2, '=');
+ if arg.next() != Some(find_arg) {
+ continue;
+ }
+
+ match arg.next().or_else(|| args.next()) {
+ Some(v) if pred(v) => return Some(v),
+ _ => {},
+ }
+ }
+ None
+}
+
+#[test]
+fn test_arg_value() {
+ let args = &["--bar=bar", "--foobar", "123", "--foo"];
+
+ assert_eq!(arg_value(&[] as &[&str], "--foobar", |_| true), None);
+ assert_eq!(arg_value(args, "--bar", |_| false), None);
+ assert_eq!(arg_value(args, "--bar", |_| true), Some("bar"));
+ assert_eq!(arg_value(args, "--bar", |p| p == "bar"), Some("bar"));
+ assert_eq!(arg_value(args, "--bar", |p| p == "foo"), None);
+ assert_eq!(arg_value(args, "--foobar", |p| p == "foo"), None);
+ assert_eq!(arg_value(args, "--foobar", |p| p == "123"), Some("123"));
+ assert_eq!(arg_value(args, "--foo", |_| true), None);
+}
+
+fn track_clippy_args(parse_sess: &mut ParseSess, args_env_var: &Option<String>) {
+ parse_sess.env_depinfo.get_mut().insert((
+ Symbol::intern("CLIPPY_ARGS"),
+ args_env_var.as_deref().map(Symbol::intern),
+ ));
+}
+
+struct DefaultCallbacks;
+impl rustc_driver::Callbacks for DefaultCallbacks {}
+
+/// This is different from `DefaultCallbacks` that it will inform Cargo to track the value of
+/// `CLIPPY_ARGS` environment variable.
+struct RustcCallbacks {
+ clippy_args_var: Option<String>,
+}
+
+impl rustc_driver::Callbacks for RustcCallbacks {
+ fn config(&mut self, config: &mut interface::Config) {
+ let clippy_args_var = self.clippy_args_var.take();
+ config.parse_sess_created = Some(Box::new(move |parse_sess| {
+ track_clippy_args(parse_sess, &clippy_args_var);
+ }));
+ }
+}
+
+struct ClippyCallbacks {
+ clippy_args_var: Option<String>,
+}
+
+impl rustc_driver::Callbacks for ClippyCallbacks {
+ fn config(&mut self, config: &mut interface::Config) {
+ let previous = config.register_lints.take();
+ let clippy_args_var = self.clippy_args_var.take();
+ config.parse_sess_created = Some(Box::new(move |parse_sess| {
+ track_clippy_args(parse_sess, &clippy_args_var);
+ }));
+ config.register_lints = Some(Box::new(move |sess, lint_store| {
+ // technically we're ~guaranteed that this is none but might as well call anything that
+ // is there already. Certainly it can't hurt.
+ if let Some(previous) = &previous {
+ (previous)(sess, lint_store);
+ }
+
++ let conf = clippy_lints::read_conf(sess);
+ clippy_lints::register_plugins(lint_store, sess, &conf);
+ clippy_lints::register_pre_expansion_lints(lint_store);
+ clippy_lints::register_renamed(lint_store);
+ }));
+
+ // FIXME: #4825; This is required, because Clippy lints that are based on MIR have to be
+ // run on the unoptimized MIR. On the other hand this results in some false negatives. If
+ // MIR passes can be enabled / disabled separately, we should figure out, what passes to
+ // use for Clippy.
+ config.opts.debugging_opts.mir_opt_level = Some(0);
+ }
+}
+
+fn display_help() {
+ println!(
+ "\
+Checks a package to catch common mistakes and improve your Rust code.
+
+Usage:
+ cargo clippy [options] [--] [<opts>...]
+
+Common options:
+ -h, --help Print this message
+ --rustc Pass all args to rustc
+ -V, --version Print version info and exit
+
+Other options are the same as `cargo check`.
+
+To allow or deny a lint from the command line you can use `cargo clippy --`
+with:
+
+ -W --warn OPT Set lint warnings
+ -A --allow OPT Set lint allowed
+ -D --deny OPT Set lint denied
+ -F --forbid OPT Set lint forbidden
+
+You can use tool lints to allow or deny lints from your code, eg.:
+
+ #[allow(clippy::needless_lifetimes)]
+"
+ );
+}
+
+const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new";
+
+static ICE_HOOK: SyncLazy<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>> = SyncLazy::new(|| {
+ let hook = panic::take_hook();
+ panic::set_hook(Box::new(|info| report_clippy_ice(info, BUG_REPORT_URL)));
+ hook
+});
+
+fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
+ // Invoke our ICE handler, which prints the actual panic message and optionally a backtrace
+ (*ICE_HOOK)(info);
+
+ // Separate the output with an empty line
+ eprintln!();
+
+ let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
+ rustc_errors::ColorConfig::Auto,
+ None,
+ false,
+ false,
+ None,
+ false,
+ ));
+ let handler = rustc_errors::Handler::with_emitter(true, None, emitter);
+
+ // a .span_bug or .bug call has already printed what
+ // it wants to print.
+ if !info.payload().is::<rustc_errors::ExplicitBug>() {
+ let d = rustc_errors::Diagnostic::new(rustc_errors::Level::Bug, "unexpected panic");
+ handler.emit_diagnostic(&d);
+ }
+
+ let version_info = rustc_tools_util::get_version_info!();
+
+ let xs: Vec<Cow<'static, str>> = vec![
+ "the compiler unexpectedly panicked. this is a bug.".into(),
+ format!("we would appreciate a bug report: {}", bug_report_url).into(),
+ format!("Clippy version: {}", version_info).into(),
+ ];
+
+ for note in &xs {
+ handler.note_without_error(note);
+ }
+
+ // If backtraces are enabled, also print the query stack
+ let backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |x| &x != "0");
+
+ let num_frames = if backtrace { None } else { Some(2) };
+
+ interface::try_print_query_stack(&handler, num_frames);
+}
+
+fn toolchain_path(home: Option<String>, toolchain: Option<String>) -> Option<PathBuf> {
+ home.and_then(|home| {
+ toolchain.map(|toolchain| {
+ let mut path = PathBuf::from(home);
+ path.push("toolchains");
+ path.push(toolchain);
+ path
+ })
+ })
+}
+
+#[allow(clippy::too_many_lines)]
+pub fn main() {
+ rustc_driver::init_rustc_env_logger();
+ SyncLazy::force(&ICE_HOOK);
+ exit(rustc_driver::catch_with_exit_code(move || {
+ let mut orig_args: Vec<String> = env::args().collect();
+
+ // Get the sysroot, looking from most specific to this invocation to the least:
+ // - command line
+ // - runtime environment
+ // - SYSROOT
+ // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN
+ // - sysroot from rustc in the path
+ // - compile-time environment
+ // - SYSROOT
+ // - RUSTUP_HOME, MULTIRUST_HOME, RUSTUP_TOOLCHAIN, MULTIRUST_TOOLCHAIN
+ let sys_root_arg = arg_value(&orig_args, "--sysroot", |_| true);
+ let have_sys_root_arg = sys_root_arg.is_some();
+ let sys_root = sys_root_arg
+ .map(PathBuf::from)
+ .or_else(|| std::env::var("SYSROOT").ok().map(PathBuf::from))
+ .or_else(|| {
+ let home = std::env::var("RUSTUP_HOME")
+ .or_else(|_| std::env::var("MULTIRUST_HOME"))
+ .ok();
+ let toolchain = std::env::var("RUSTUP_TOOLCHAIN")
+ .or_else(|_| std::env::var("MULTIRUST_TOOLCHAIN"))
+ .ok();
+ toolchain_path(home, toolchain)
+ })
+ .or_else(|| {
+ Command::new("rustc")
+ .arg("--print")
+ .arg("sysroot")
+ .output()
+ .ok()
+ .and_then(|out| String::from_utf8(out.stdout).ok())
+ .map(|s| PathBuf::from(s.trim()))
+ })
+ .or_else(|| option_env!("SYSROOT").map(PathBuf::from))
+ .or_else(|| {
+ let home = option_env!("RUSTUP_HOME")
+ .or(option_env!("MULTIRUST_HOME"))
+ .map(ToString::to_string);
+ let toolchain = option_env!("RUSTUP_TOOLCHAIN")
+ .or(option_env!("MULTIRUST_TOOLCHAIN"))
+ .map(ToString::to_string);
+ toolchain_path(home, toolchain)
+ })
+ .map(|pb| pb.to_string_lossy().to_string())
+ .expect("need to specify SYSROOT env var during clippy compilation, or use rustup or multirust");
+
+ // make "clippy-driver --rustc" work like a subcommand that passes further args to "rustc"
+ // for example `clippy-driver --rustc --version` will print the rustc version that clippy-driver
+ // uses
+ if let Some(pos) = orig_args.iter().position(|arg| arg == "--rustc") {
+ orig_args.remove(pos);
+ orig_args[0] = "rustc".to_string();
+
+ // if we call "rustc", we need to pass --sysroot here as well
+ let mut args: Vec<String> = orig_args.clone();
+ if !have_sys_root_arg {
+ args.extend(vec!["--sysroot".into(), sys_root]);
+ };
+
+ return rustc_driver::RunCompiler::new(&args, &mut DefaultCallbacks).run();
+ }
+
+ if orig_args.iter().any(|a| a == "--version" || a == "-V") {
+ let version_info = rustc_tools_util::get_version_info!();
+ println!("{}", version_info);
+ exit(0);
+ }
+
+ // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument.
+ // We're invoking the compiler programmatically, so we ignore this/
+ let wrapper_mode = orig_args.get(1).map(Path::new).and_then(Path::file_stem) == Some("rustc".as_ref());
+
+ if wrapper_mode {
+ // we still want to be able to invoke it normally though
+ orig_args.remove(1);
+ }
+
+ if !wrapper_mode && (orig_args.iter().any(|a| a == "--help" || a == "-h") || orig_args.len() == 1) {
+ display_help();
+ exit(0);
+ }
+
+ // this conditional check for the --sysroot flag is there so users can call
+ // `clippy_driver` directly
+ // without having to pass --sysroot or anything
+ let mut args: Vec<String> = orig_args.clone();
+ if !have_sys_root_arg {
+ args.extend(vec!["--sysroot".into(), sys_root]);
+ };
+
+ let mut no_deps = false;
+ let clippy_args_var = env::var("CLIPPY_ARGS").ok();
+ let clippy_args = clippy_args_var
+ .as_deref()
+ .unwrap_or_default()
+ .split("__CLIPPY_HACKERY__")
+ .filter_map(|s| match s {
+ "" => None,
+ "--no-deps" => {
+ no_deps = true;
+ None
+ },
+ _ => Some(s.to_string()),
+ })
+ .chain(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()])
+ .collect::<Vec<String>>();
+
+ // We enable Clippy if one of the following conditions is met
+ // - IF Clippy is run on its test suite OR
+ // - IF Clippy is run on the main crate, not on deps (`!cap_lints_allow`) THEN
+ // - IF `--no-deps` is not set (`!no_deps`) OR
+ // - IF `--no-deps` is set and Clippy is run on the specified primary package
+ let clippy_tests_set = env::var("__CLIPPY_INTERNAL_TESTS").map_or(false, |val| val == "true");
+ let cap_lints_allow = arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_some();
+ let in_primary_package = env::var("CARGO_PRIMARY_PACKAGE").is_ok();
+
+ let clippy_enabled = clippy_tests_set || (!cap_lints_allow && (!no_deps || in_primary_package));
+ if clippy_enabled {
+ args.extend(clippy_args);
+ }
+
+ if clippy_enabled {
+ rustc_driver::RunCompiler::new(&args, &mut ClippyCallbacks { clippy_args_var }).run()
+ } else {
+ rustc_driver::RunCompiler::new(&args, &mut RustcCallbacks { clippy_args_var }).run()
+ }
+ }))
+}
--- /dev/null
++//! This test is a part of quality control and makes clippy eat what it produces. Awesome lints and
++//! long error messages
++//!
++//! See [Eating your own dog food](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) for context
++
+// Dogfood cannot run on Windows
+#![cfg(not(windows))]
+#![feature(once_cell)]
+
+use std::lazy::SyncLazy;
+use std::path::PathBuf;
+use std::process::Command;
+
+mod cargo;
+
+static CLIPPY_PATH: SyncLazy<PathBuf> = SyncLazy::new(|| cargo::TARGET_LIB.join("cargo-clippy"));
+
+#[test]
+fn dogfood_clippy() {
+ // run clippy on itself and fail the test if lint warnings are reported
+ if cargo::is_rustc_test_suite() {
+ return;
+ }
+ let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
++ let enable_metadata_collection = std::env::var("ENABLE_METADATA_COLLECTION").unwrap_or_else(|_| "0".to_string());
+
+ let mut command = Command::new(&*CLIPPY_PATH);
+ command
+ .current_dir(root_dir)
+ .env("CLIPPY_DOGFOOD", "1")
+ .env("CARGO_INCREMENTAL", "0")
++ .env("ENABLE_METADATA_COLLECTION", &enable_metadata_collection)
+ .arg("clippy")
+ .arg("--all-targets")
+ .arg("--all-features")
+ .arg("--")
+ .args(&["-D", "clippy::all"])
+ .args(&["-D", "clippy::pedantic"])
+ .arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir
+
+ // internal lints only exist if we build with the internal-lints feature
+ if cfg!(feature = "internal-lints") {
+ command.args(&["-D", "clippy::internal"]);
+ }
+
+ let output = command.output().unwrap();
+
+ println!("status: {}", output.status);
+ println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
+ println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
+
+ assert!(output.status.success());
+}
+
+fn test_no_deps_ignores_path_deps_in_workspaces() {
+ if cargo::is_rustc_test_suite() {
+ return;
+ }
+ let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ let target_dir = root.join("target").join("dogfood");
+ let cwd = root.join("clippy_workspace_tests");
+
+ // Make sure we start with a clean state
+ Command::new("cargo")
+ .current_dir(&cwd)
+ .env("CARGO_TARGET_DIR", &target_dir)
+ .arg("clean")
+ .args(&["-p", "subcrate"])
+ .args(&["-p", "path_dep"])
+ .output()
+ .unwrap();
+
+ // `path_dep` is a path dependency of `subcrate` that would trigger a denied lint.
+ // Make sure that with the `--no-deps` argument Clippy does not run on `path_dep`.
+ let output = Command::new(&*CLIPPY_PATH)
+ .current_dir(&cwd)
+ .env("CLIPPY_DOGFOOD", "1")
+ .env("CARGO_INCREMENTAL", "0")
+ .arg("clippy")
+ .args(&["-p", "subcrate"])
+ .arg("--")
+ .arg("--no-deps")
+ .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir
+ .args(&["--cfg", r#"feature="primary_package_test""#])
+ .output()
+ .unwrap();
+ println!("status: {}", output.status);
+ println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
+ println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
+
+ assert!(output.status.success());
+
+ let lint_path_dep = || {
+ // Test that without the `--no-deps` argument, `path_dep` is linted.
+ let output = Command::new(&*CLIPPY_PATH)
+ .current_dir(&cwd)
+ .env("CLIPPY_DOGFOOD", "1")
+ .env("CARGO_INCREMENTAL", "0")
+ .arg("clippy")
+ .args(&["-p", "subcrate"])
+ .arg("--")
+ .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir
+ .args(&["--cfg", r#"feature="primary_package_test""#])
+ .output()
+ .unwrap();
+ println!("status: {}", output.status);
+ println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
+ println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
+
+ assert!(!output.status.success());
+ assert!(
+ String::from_utf8(output.stderr)
+ .unwrap()
+ .contains("error: empty `loop {}` wastes CPU cycles")
+ );
+ };
+
+ // Make sure Cargo is aware of the removal of `--no-deps`.
+ lint_path_dep();
+
+ let successful_build = || {
+ let output = Command::new(&*CLIPPY_PATH)
+ .current_dir(&cwd)
+ .env("CLIPPY_DOGFOOD", "1")
+ .env("CARGO_INCREMENTAL", "0")
+ .arg("clippy")
+ .args(&["-p", "subcrate"])
+ .arg("--")
+ .arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir
+ .output()
+ .unwrap();
+ println!("status: {}", output.status);
+ println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
+ println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
+
+ assert!(output.status.success());
+
+ output
+ };
+
+ // Trigger a sucessful build, so Cargo would like to cache the build result.
+ successful_build();
+
+ // Make sure there's no spurious rebuild when nothing changes.
+ let stderr = String::from_utf8(successful_build().stderr).unwrap();
+ assert!(!stderr.contains("Compiling"));
+ assert!(!stderr.contains("Checking"));
+ assert!(stderr.contains("Finished"));
+
+ // Make sure Cargo is aware of the new `--cfg` flag.
+ lint_path_dep();
+}
+
+#[test]
+fn dogfood_subprojects() {
+ // run clippy on remaining subprojects and fail the test if lint warnings are reported
+ if cargo::is_rustc_test_suite() {
+ return;
+ }
+ let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+
+ // NOTE: `path_dep` crate is omitted on purpose here
+ for d in &[
+ "clippy_workspace_tests",
+ "clippy_workspace_tests/src",
+ "clippy_workspace_tests/subcrate",
+ "clippy_workspace_tests/subcrate/src",
+ "clippy_dev",
+ "clippy_lints",
+ "clippy_utils",
+ "rustc_tools_util",
+ ] {
+ let mut command = Command::new(&*CLIPPY_PATH);
+ command
+ .current_dir(root_dir.join(d))
+ .env("CLIPPY_DOGFOOD", "1")
+ .env("CARGO_INCREMENTAL", "0")
+ .arg("clippy")
+ .arg("--all-targets")
+ .arg("--all-features")
+ .arg("--")
+ .args(&["-D", "clippy::all"])
+ .args(&["-D", "clippy::pedantic"])
+ .arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir
+
+ // internal lints only exist if we build with the internal-lints feature
+ if cfg!(feature = "internal-lints") {
+ command.args(&["-D", "clippy::internal"]);
+ }
+
+ let output = command.output().unwrap();
+
+ println!("status: {}", output.status);
+ println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
+ println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
+
+ assert!(output.status.success());
+ }
+
+ // NOTE: Since tests run in parallel we can't run cargo commands on the same workspace at the
+ // same time, so we test this immediately after the dogfood for workspaces.
+ test_no_deps_ignores_path_deps_in_workspaces();
+}
--- /dev/null
- error: error reading Clippy's configuration file `$DIR/clippy.toml`: invalid type: integer `42`, expected a sequence
++error: error reading Clippy's configuration file `$DIR/clippy.toml`: invalid type: integer `42`, expected a sequence for key `blacklisted-names`
+
+error: aborting due to previous error
+
--- /dev/null
- error: error reading Clippy's configuration file `$DIR/clippy.toml`: found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.
++error: error reading Clippy's configuration file `$DIR/clippy.toml`: deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead
+
+error: aborting due to previous error
+
--- /dev/null
--- /dev/null
++#![warn(clippy::builtin_type_shadow)]
++#![allow(non_camel_case_types)]
++
++fn foo<u32>(a: u32) -> u32 {
++ 42
++ // ^ rustc's type error
++}
++
++fn main() {}
--- /dev/null
--- /dev/null
++error: this generic shadows the built-in type `u32`
++ --> $DIR/builtin_type_shadow.rs:4:8
++ |
++LL | fn foo<u32>(a: u32) -> u32 {
++ | ^^^
++ |
++ = note: `-D clippy::builtin-type-shadow` implied by `-D warnings`
++
++error[E0308]: mismatched types
++ --> $DIR/builtin_type_shadow.rs:5:5
++ |
++LL | fn foo<u32>(a: u32) -> u32 {
++ | --- --- expected `u32` because of return type
++ | |
++ | this type parameter
++LL | 42
++ | ^^ expected type parameter `u32`, found integer
++ |
++ = note: expected type parameter `u32`
++ found type `{integer}`
++
++error: aborting due to 2 previous errors
++
++For more information about this error, try `rustc --explain E0308`.
--- /dev/null
+#![allow(dead_code)]
+#![warn(clippy::comparison_chain)]
+
+fn a() {}
+fn b() {}
+fn c() {}
+
+fn f(x: u8, y: u8, z: u8) {
+ // Ignored: Only one branch
+ if x > y {
+ a()
+ }
+
+ if x > y {
+ a()
+ } else if x < y {
+ b()
+ }
+
+ // Ignored: Only one explicit conditional
+ if x > y {
+ a()
+ } else {
+ b()
+ }
+
+ if x > y {
+ a()
+ } else if x < y {
+ b()
+ } else {
+ c()
+ }
+
+ if x > y {
+ a()
+ } else if y > x {
+ b()
+ } else {
+ c()
+ }
+
+ if x > 1 {
+ a()
+ } else if x < 1 {
+ b()
+ } else if x == 1 {
+ c()
+ }
+
+ // Ignored: Binop args are not equivalent
+ if x > 1 {
+ a()
+ } else if y > 1 {
+ b()
+ } else {
+ c()
+ }
+
+ // Ignored: Binop args are not equivalent
+ if x > y {
+ a()
+ } else if x > z {
+ b()
+ } else if y > z {
+ c()
+ }
+
+ // Ignored: Not binary comparisons
+ if true {
+ a()
+ } else if false {
+ b()
+ } else {
+ c()
+ }
+}
+
+#[allow(clippy::float_cmp)]
+fn g(x: f64, y: f64, z: f64) {
+ // Ignored: f64 doesn't implement Ord
+ if x > y {
+ a()
+ } else if x < y {
+ b()
+ }
+
+ // Ignored: f64 doesn't implement Ord
+ if x > y {
+ a()
+ } else if x < y {
+ b()
+ } else {
+ c()
+ }
+
+ // Ignored: f64 doesn't implement Ord
+ if x > y {
+ a()
+ } else if y > x {
+ b()
+ } else {
+ c()
+ }
+
+ // Ignored: f64 doesn't implement Ord
+ if x > 1.0 {
+ a()
+ } else if x < 1.0 {
+ b()
+ } else if x == 1.0 {
+ c()
+ }
+}
+
+fn h<T: Ord>(x: T, y: T, z: T) {
+ if x > y {
+ a()
+ } else if x < y {
+ b()
+ }
+
+ if x > y {
+ a()
+ } else if x < y {
+ b()
+ } else {
+ c()
+ }
+
+ if x > y {
+ a()
+ } else if y > x {
+ b()
+ } else {
+ c()
+ }
+}
+
+// The following uses should be ignored
+mod issue_5212 {
+ use super::{a, b, c};
+ fn foo() -> u8 {
+ 21
+ }
+
+ fn same_operation_equals() {
+ // operands are fixed
+
+ if foo() == 42 {
+ a()
+ } else if foo() == 42 {
+ b()
+ }
+
+ if foo() == 42 {
+ a()
+ } else if foo() == 42 {
+ b()
+ } else {
+ c()
+ }
+
+ // operands are transposed
+
+ if foo() == 42 {
+ a()
+ } else if 42 == foo() {
+ b()
+ }
+ }
+
+ fn same_operation_not_equals() {
+ // operands are fixed
+
+ if foo() > 42 {
+ a()
+ } else if foo() > 42 {
+ b()
+ }
+
+ if foo() > 42 {
+ a()
+ } else if foo() > 42 {
+ b()
+ } else {
+ c()
+ }
+
+ if foo() < 42 {
+ a()
+ } else if foo() < 42 {
+ b()
+ }
+
+ if foo() < 42 {
+ a()
+ } else if foo() < 42 {
+ b()
+ } else {
+ c()
+ }
+ }
+}
+
++enum Sign {
++ Negative,
++ Positive,
++ Zero,
++}
++
++impl Sign {
++ const fn sign_i8(n: i8) -> Self {
++ if n == 0 {
++ Sign::Zero
++ } else if n > 0 {
++ Sign::Positive
++ } else {
++ Sign::Negative
++ }
++ }
++}
++
++const fn sign_i8(n: i8) -> Sign {
++ if n == 0 {
++ Sign::Zero
++ } else if n > 0 {
++ Sign::Positive
++ } else {
++ Sign::Negative
++ }
++}
++
+fn main() {}
--- /dev/null
- let a: Vec<_> = my_iterator.take(1).collect();
- assert_eq!(a.len(), 1);
- let b: Vec<_> = my_iterator.collect();
- assert_eq!(b.len(), 5);
+#![warn(clippy::copy_iterator)]
+
+#[derive(Copy, Clone)]
+struct Countdown(u8);
+
+impl Iterator for Countdown {
+ type Item = u8;
+
+ fn next(&mut self) -> Option<u8> {
+ self.0.checked_sub(1).map(|c| {
+ self.0 = c;
+ c
+ })
+ }
+}
+
+fn main() {
+ let my_iterator = Countdown(5);
++ assert_eq!(my_iterator.take(1).count(), 1);
++ assert_eq!(my_iterator.count(), 5);
+}
--- /dev/null
--- /dev/null
++#[derive(Default)]
++struct A<T> {
++ a: Vec<A<T>>,
++ b: T,
++}
++
++fn main() {
++ if let Ok(_) = Ok::<_, ()>(A::<String>::default()) {}
++}
--- /dev/null
--- /dev/null
++error: redundant pattern matching, consider using `is_ok()`
++ --> $DIR/ice-7169.rs:8:12
++ |
++LL | if let Ok(_) = Ok::<_, ()>(A::<String>::default()) {}
++ | -------^^^^^-------------------------------------- help: try this: `if Ok::<_, ()>(A::<String>::default()).is_ok()`
++ |
++ = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings`
++
++error: aborting due to previous error
++
--- /dev/null
++// edition:2018
++
+#[warn(clippy::eval_order_dependence)]
+#[allow(
+ unused_assignments,
+ unused_variables,
+ clippy::many_single_char_names,
+ clippy::no_effect,
+ dead_code,
+ clippy::blacklisted_name
+)]
+fn main() {
+ let mut x = 0;
+ let a = {
+ x = 1;
+ 1
+ } + x;
+
+ // Example from iss#277
+ x += {
+ x = 20;
+ 2
+ };
+
+ // Does it work in weird places?
+ // ...in the base for a struct expression?
+ struct Foo {
+ a: i32,
+ b: i32,
+ };
+ let base = Foo { a: 4, b: 5 };
+ let foo = Foo {
+ a: x,
+ ..{
+ x = 6;
+ base
+ }
+ };
+ // ...inside a closure?
+ let closure = || {
+ let mut x = 0;
+ x += {
+ x = 20;
+ 2
+ };
+ };
+ // ...not across a closure?
+ let mut y = 0;
+ let b = (y, || y = 1);
+
+ // && and || evaluate left-to-right.
+ let a = {
+ x = 1;
+ true
+ } && (x == 3);
+ let a = {
+ x = 1;
+ true
+ } || (x == 3);
+
+ // Make sure we don't get confused by alpha conversion.
+ let a = {
+ let mut x = 1;
+ x = 2;
+ 1
+ } + x;
+
+ // No warning if we don't read the variable...
+ x = {
+ x = 20;
+ 2
+ };
+ // ...if the assignment is in a closure...
+ let b = {
+ || {
+ x = 1;
+ };
+ 1
+ } + x;
+ // ... or the access is under an address.
+ let b = (
+ {
+ let p = &x;
+ 1
+ },
+ {
+ x = 1;
+ x
+ },
+ );
+
+ // Limitation: l-values other than simple variables don't trigger
+ // the warning.
+ let mut tup = (0, 0);
+ let c = {
+ tup.0 = 1;
+ 1
+ } + tup.0;
+ // Limitation: you can get away with a read under address-of.
+ let mut z = 0;
+ let b = (
+ &{
+ z = x;
+ x
+ },
+ {
+ x = 3;
+ x
+ },
+ );
+}
++
++async fn issue_6925() {
++ let _ = vec![async { true }.await, async { false }.await];
++}
--- /dev/null
- error: unsequenced read of a variable
- --> $DIR/eval_order_dependence.rs:15:9
++error: unsequenced read of `x`
++ --> $DIR/eval_order_dependence.rs:17:9
+ |
+LL | } + x;
+ | ^
+ |
+ = note: `-D clippy::eval-order-dependence` implied by `-D warnings`
+note: whether read occurs before this write depends on evaluation order
- --> $DIR/eval_order_dependence.rs:13:9
++ --> $DIR/eval_order_dependence.rs:15:9
+ |
+LL | x = 1;
+ | ^^^^^
+
- error: unsequenced read of a variable
- --> $DIR/eval_order_dependence.rs:18:5
++error: unsequenced read of `x`
++ --> $DIR/eval_order_dependence.rs:20:5
+ |
+LL | x += {
+ | ^
+ |
+note: whether read occurs before this write depends on evaluation order
- --> $DIR/eval_order_dependence.rs:19:9
++ --> $DIR/eval_order_dependence.rs:21:9
+ |
+LL | x = 20;
+ | ^^^^^^
+
- error: unsequenced read of a variable
- --> $DIR/eval_order_dependence.rs:31:12
++error: unsequenced read of `x`
++ --> $DIR/eval_order_dependence.rs:33:12
+ |
+LL | a: x,
+ | ^
+ |
+note: whether read occurs before this write depends on evaluation order
- --> $DIR/eval_order_dependence.rs:33:13
++ --> $DIR/eval_order_dependence.rs:35:13
+ |
+LL | x = 6;
+ | ^^^^^
+
- error: unsequenced read of a variable
- --> $DIR/eval_order_dependence.rs:40:9
++error: unsequenced read of `x`
++ --> $DIR/eval_order_dependence.rs:42:9
+ |
+LL | x += {
+ | ^
+ |
+note: whether read occurs before this write depends on evaluation order
- --> $DIR/eval_order_dependence.rs:41:13
++ --> $DIR/eval_order_dependence.rs:43:13
+ |
+LL | x = 20;
+ | ^^^^^^
+
+error: aborting due to 4 previous errors
+
--- /dev/null
- #![allow(clippy::needless_return, unused)]
++// edition:2018
+// run-rustfix
+
+#![warn(clippy::implicit_return)]
- #[allow(clippy::needless_bool)]
++#![allow(clippy::needless_return, clippy::needless_bool, unused, clippy::never_loop)]
+
+fn test_end_of_fn() -> bool {
+ if true {
+ // no error!
+ return true;
+ }
+
+ return true
+}
+
- #[allow(clippy::needless_return)]
+fn test_if_block() -> bool {
+ if true { return true } else { return false }
+}
+
+#[rustfmt::skip]
+fn test_match(x: bool) -> bool {
+ match x {
+ true => return false,
+ false => { return true },
+ }
+}
+
- #[allow(clippy::never_loop)]
+fn test_match_with_unreachable(x: bool) -> bool {
+ match x {
+ true => return false,
+ false => unreachable!(),
+ }
+}
+
- #[allow(clippy::never_loop)]
+fn test_loop() -> bool {
+ loop {
+ return true;
+ }
+}
+
- #[allow(clippy::never_loop)]
+fn test_loop_with_block() -> bool {
+ loop {
+ {
+ return true;
+ }
+ }
+}
+
- fn main() {
- let _ = test_end_of_fn();
- let _ = test_if_block();
- let _ = test_match(true);
- let _ = test_match_with_unreachable(true);
- let _ = test_loop();
- let _ = test_loop_with_block();
- let _ = test_loop_with_nests();
- let _ = test_loop_with_if_let();
- test_closure();
- let _ = test_return_macro();
+fn test_loop_with_nests() -> bool {
+ loop {
+ if true {
+ return true;
+ } else {
+ let _ = true;
+ }
+ }
+}
+
+#[allow(clippy::redundant_pattern_matching)]
+fn test_loop_with_if_let() -> bool {
+ loop {
+ if let Some(x) = Some(true) {
+ return x;
+ }
+ }
+}
+
+fn test_closure() {
+ #[rustfmt::skip]
+ let _ = || { return true };
+ let _ = || return true;
+}
+
+fn test_panic() -> bool {
+ panic!()
+}
+
+fn test_return_macro() -> String {
+ return format!("test {}", "test")
+}
+
++fn macro_branch_test() -> bool {
++ macro_rules! m {
++ ($t:expr, $f:expr) => {
++ if true { $t } else { $f }
++ };
++ }
++ return m!(true, false)
++}
++
++fn loop_test() -> bool {
++ 'outer: loop {
++ if true {
++ return true;
++ }
++
++ let _ = loop {
++ if false {
++ return false;
++ }
++ if true {
++ break true;
++ }
++ };
++ }
+}
++
++fn loop_macro_test() -> bool {
++ macro_rules! m {
++ ($e:expr) => {
++ break $e
++ };
++ }
++ return loop {
++ m!(true);
++ }
++}
++
++fn divergent_test() -> bool {
++ fn diverge() -> ! {
++ panic!()
++ }
++ diverge()
++}
++
++// issue #6940
++async fn foo() -> bool {
++ return true
++}
++
++fn main() {}
--- /dev/null
- #![allow(clippy::needless_return, unused)]
++// edition:2018
+// run-rustfix
+
+#![warn(clippy::implicit_return)]
- #[allow(clippy::needless_bool)]
++#![allow(clippy::needless_return, clippy::needless_bool, unused, clippy::never_loop)]
+
+fn test_end_of_fn() -> bool {
+ if true {
+ // no error!
+ return true;
+ }
+
+ true
+}
+
- #[allow(clippy::needless_return)]
+fn test_if_block() -> bool {
+ if true { true } else { false }
+}
+
+#[rustfmt::skip]
+fn test_match(x: bool) -> bool {
+ match x {
+ true => false,
+ false => { true },
+ }
+}
+
- #[allow(clippy::never_loop)]
+fn test_match_with_unreachable(x: bool) -> bool {
+ match x {
+ true => return false,
+ false => unreachable!(),
+ }
+}
+
- #[allow(clippy::never_loop)]
+fn test_loop() -> bool {
+ loop {
+ break true;
+ }
+}
+
- #[allow(clippy::never_loop)]
+fn test_loop_with_block() -> bool {
+ loop {
+ {
+ break true;
+ }
+ }
+}
+
- fn main() {
- let _ = test_end_of_fn();
- let _ = test_if_block();
- let _ = test_match(true);
- let _ = test_match_with_unreachable(true);
- let _ = test_loop();
- let _ = test_loop_with_block();
- let _ = test_loop_with_nests();
- let _ = test_loop_with_if_let();
- test_closure();
- let _ = test_return_macro();
+fn test_loop_with_nests() -> bool {
+ loop {
+ if true {
+ break true;
+ } else {
+ let _ = true;
+ }
+ }
+}
+
+#[allow(clippy::redundant_pattern_matching)]
+fn test_loop_with_if_let() -> bool {
+ loop {
+ if let Some(x) = Some(true) {
+ return x;
+ }
+ }
+}
+
+fn test_closure() {
+ #[rustfmt::skip]
+ let _ = || { true };
+ let _ = || true;
+}
+
+fn test_panic() -> bool {
+ panic!()
+}
+
+fn test_return_macro() -> String {
+ format!("test {}", "test")
+}
+
++fn macro_branch_test() -> bool {
++ macro_rules! m {
++ ($t:expr, $f:expr) => {
++ if true { $t } else { $f }
++ };
++ }
++ m!(true, false)
++}
++
++fn loop_test() -> bool {
++ 'outer: loop {
++ if true {
++ break true;
++ }
++
++ let _ = loop {
++ if false {
++ break 'outer false;
++ }
++ if true {
++ break true;
++ }
++ };
++ }
++}
++
++fn loop_macro_test() -> bool {
++ macro_rules! m {
++ ($e:expr) => {
++ break $e
++ };
++ }
++ loop {
++ m!(true);
++ }
++}
++
++fn divergent_test() -> bool {
++ fn diverge() -> ! {
++ panic!()
++ }
++ diverge()
+}
++
++// issue #6940
++async fn foo() -> bool {
++ true
++}
++
++fn main() {}
--- /dev/null
- --> $DIR/implicit_return.rs:12:5
+error: missing `return` statement
- --> $DIR/implicit_return.rs:39:9
++ --> $DIR/implicit_return.rs:13:5
+ |
+LL | true
+ | ^^^^ help: add `return` as shown: `return true`
+ |
+ = note: `-D clippy::implicit-return` implied by `-D warnings`
+
+error: missing `return` statement
+ --> $DIR/implicit_return.rs:17:15
+ |
+LL | if true { true } else { false }
+ | ^^^^ help: add `return` as shown: `return true`
+
+error: missing `return` statement
+ --> $DIR/implicit_return.rs:17:29
+ |
+LL | if true { true } else { false }
+ | ^^^^^ help: add `return` as shown: `return false`
+
+error: missing `return` statement
+ --> $DIR/implicit_return.rs:23:17
+ |
+LL | true => false,
+ | ^^^^^ help: add `return` as shown: `return false`
+
+error: missing `return` statement
+ --> $DIR/implicit_return.rs:24:20
+ |
+LL | false => { true },
+ | ^^^^ help: add `return` as shown: `return true`
+
+error: missing `return` statement
- --> $DIR/implicit_return.rs:47:13
++ --> $DIR/implicit_return.rs:37:9
+ |
+LL | break true;
+ | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true`
+
+error: missing `return` statement
- --> $DIR/implicit_return.rs:56:13
++ --> $DIR/implicit_return.rs:44:13
+ |
+LL | break true;
+ | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true`
+
+error: missing `return` statement
- --> $DIR/implicit_return.rs:74:18
++ --> $DIR/implicit_return.rs:52:13
+ |
+LL | break true;
+ | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true`
+
+error: missing `return` statement
- --> $DIR/implicit_return.rs:75:16
++ --> $DIR/implicit_return.rs:70:18
+ |
+LL | let _ = || { true };
+ | ^^^^ help: add `return` as shown: `return true`
+
+error: missing `return` statement
- --> $DIR/implicit_return.rs:83:5
++ --> $DIR/implicit_return.rs:71:16
+ |
+LL | let _ = || true;
+ | ^^^^ help: add `return` as shown: `return true`
+
+error: missing `return` statement
- error: aborting due to 11 previous errors
++ --> $DIR/implicit_return.rs:79:5
+ |
+LL | format!("test {}", "test")
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `return` as shown: `return format!("test {}", "test")`
+
++error: missing `return` statement
++ --> $DIR/implicit_return.rs:88:5
++ |
++LL | m!(true, false)
++ | ^^^^^^^^^^^^^^^ help: add `return` as shown: `return m!(true, false)`
++
++error: missing `return` statement
++ --> $DIR/implicit_return.rs:94:13
++ |
++LL | break true;
++ | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true`
++
++error: missing `return` statement
++ --> $DIR/implicit_return.rs:99:17
++ |
++LL | break 'outer false;
++ | ^^^^^^^^^^^^^^^^^^ help: change `break` to `return` as shown: `return false`
++
++error: missing `return` statement
++ --> $DIR/implicit_return.rs:114:5
++ |
++LL | / loop {
++LL | | m!(true);
++LL | | }
++ | |_____^
++ |
++help: add `return` as shown
++ |
++LL | return loop {
++LL | m!(true);
++LL | }
++ |
++
++error: missing `return` statement
++ --> $DIR/implicit_return.rs:128:5
++ |
++LL | true
++ | ^^^^ help: add `return` as shown: `return true`
++
++error: aborting due to 16 previous errors
+
--- /dev/null
+fn fn_val(i: i32) -> i32 {
+ unimplemented!()
+}
+fn fn_constref(i: &i32) -> i32 {
+ unimplemented!()
+}
+fn fn_mutref(i: &mut i32) {
+ unimplemented!()
+}
+fn fooi() -> i32 {
+ unimplemented!()
+}
+fn foob() -> bool {
+ unimplemented!()
+}
+
+#[allow(clippy::many_single_char_names)]
+fn immutable_condition() {
+ // Should warn when all vars mentioned are immutable
+ let y = 0;
+ while y < 10 {
+ println!("KO - y is immutable");
+ }
+
+ let x = 0;
+ while y < 10 && x < 3 {
+ let mut k = 1;
+ k += 2;
+ println!("KO - x and y immutable");
+ }
+
+ let cond = false;
+ while !cond {
+ println!("KO - cond immutable");
+ }
+
+ let mut i = 0;
+ while y < 10 && i < 3 {
+ i += 1;
+ println!("OK - i is mutable");
+ }
+
+ let mut mut_cond = false;
+ while !mut_cond || cond {
+ mut_cond = true;
+ println!("OK - mut_cond is mutable");
+ }
+
+ while fooi() < x {
+ println!("OK - Fn call results may vary");
+ }
+
+ while foob() {
+ println!("OK - Fn call results may vary");
+ }
+
+ let mut a = 0;
+ let mut c = move || {
+ while a < 5 {
+ a += 1;
+ println!("OK - a is mutable");
+ }
+ };
+ c();
+
+ let mut tup = (0, 0);
+ while tup.0 < 5 {
+ tup.0 += 1;
+ println!("OK - tup.0 gets mutated")
+ }
+}
+
+fn unused_var() {
+ // Should warn when a (mutable) var is not used in while body
+ let (mut i, mut j) = (0, 0);
+
+ while i < 3 {
+ j = 3;
+ println!("KO - i not mentioned");
+ }
+
+ while i < 3 && j > 0 {
+ println!("KO - i and j not mentioned");
+ }
+
+ while i < 3 {
+ let mut i = 5;
+ fn_mutref(&mut i);
+ println!("KO - shadowed");
+ }
+
+ while i < 3 && j > 0 {
+ i = 5;
+ println!("OK - i in cond and mentioned");
+ }
+}
+
+fn used_immutable() {
+ let mut i = 0;
+
+ while i < 3 {
+ fn_constref(&i);
+ println!("KO - const reference");
+ }
+
+ while i < 3 {
+ fn_val(i);
+ println!("KO - passed by value");
+ }
+
+ while i < 3 {
+ println!("OK - passed by mutable reference");
+ fn_mutref(&mut i)
+ }
+
+ while i < 3 {
+ fn_mutref(&mut i);
+ println!("OK - passed by mutable reference");
+ }
+}
+
+const N: i32 = 5;
+const B: bool = false;
+
+fn consts() {
+ while false {
+ println!("Constants are not linted");
+ }
+
+ while B {
+ println!("Constants are not linted");
+ }
+
+ while N > 0 {
+ println!("Constants are not linted");
+ }
+}
+
+use std::cell::Cell;
+
+fn maybe_i_mutate(i: &Cell<bool>) {
+ unimplemented!()
+}
+
+fn internally_mutable() {
+ let b = Cell::new(true);
+
+ while b.get() {
+ // b cannot be silently coerced to `bool`
+ maybe_i_mutate(&b);
+ println!("OK - Method call within condition");
+ }
+}
+
+struct Counter {
+ count: usize,
+}
+
+impl Counter {
+ fn inc(&mut self) {
+ self.count += 1;
+ }
+
+ fn inc_n(&mut self, n: usize) {
+ while self.count < n {
+ self.inc();
+ }
+ println!("OK - self borrowed mutably");
+ }
+
+ fn print_n(&self, n: usize) {
+ while self.count < n {
+ println!("KO - {} is not mutated", self.count);
+ }
+ }
+}
+
+fn while_loop_with_break_and_return() {
+ let y = 0;
+ while y < 10 {
+ if y == 0 {
+ break;
+ }
+ println!("KO - loop contains break");
+ }
+
+ while y < 10 {
+ if y == 0 {
+ return;
+ }
+ println!("KO - loop contains return");
+ }
+}
+
++fn immutable_condition_false_positive(mut n: u64) -> u32 {
++ let mut count = 0;
++ while {
++ n >>= 1;
++ n != 0
++ } {
++ count += 1;
++ }
++ count
++}
++
+fn main() {
+ immutable_condition();
+ unused_var();
+ used_immutable();
+ internally_mutable();
++ immutable_condition_false_positive(5);
+
+ let mut c = Counter { count: 0 };
+ c.inc_n(5);
+ c.print_n(2);
+
+ while_loop_with_break_and_return();
+}
--- /dev/null
- use std::collections::{HashMap, VecDeque};
++use std::collections::{BinaryHeap, HashMap, LinkedList, VecDeque};
+
+fn main() {
+ let sample = [1; 5];
+ let indirect_iter = sample.iter().collect::<Vec<_>>();
+ indirect_iter.into_iter().map(|x| (x, x + 1)).collect::<HashMap<_, _>>();
+ let indirect_len = sample.iter().collect::<VecDeque<_>>();
+ indirect_len.len();
+ let indirect_empty = sample.iter().collect::<VecDeque<_>>();
+ indirect_empty.is_empty();
+ let indirect_contains = sample.iter().collect::<VecDeque<_>>();
+ indirect_contains.contains(&&5);
+ let indirect_negative = sample.iter().collect::<Vec<_>>();
+ indirect_negative.len();
+ indirect_negative
+ .into_iter()
+ .map(|x| (*x, *x + 1))
+ .collect::<HashMap<_, _>>();
+
+ // #6202
+ let a = "a".to_string();
+ let sample = vec![a.clone(), "b".to_string(), "c".to_string()];
+ let non_copy_contains = sample.into_iter().collect::<Vec<_>>();
+ non_copy_contains.contains(&a);
+
+ // Fix #5991
+ let vec_a = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+ let vec_b = vec_a.iter().collect::<Vec<_>>();
+ if vec_b.len() > 3 {}
+ let other_vec = vec![1, 3, 12, 4, 16, 2];
+ let we_got_the_same_numbers = other_vec.iter().filter(|item| vec_b.contains(item)).collect::<Vec<_>>();
+
+ // Fix #6297
+ let sample = [1; 5];
+ let multiple_indirect = sample.iter().collect::<Vec<_>>();
+ let sample2 = vec![2, 3];
+ if multiple_indirect.is_empty() {
+ // do something
+ } else {
+ let found = sample2
+ .iter()
+ .filter(|i| multiple_indirect.iter().any(|s| **s % **i == 0))
+ .collect::<Vec<_>>();
+ }
+}
++
++mod issue7110 {
++ // #7110 - lint for type annotation cases
++ use super::*;
++
++ fn lint_vec(string: &str) -> usize {
++ let buffer: Vec<&str> = string.split('/').collect();
++ buffer.len()
++ }
++ fn lint_vec_deque() -> usize {
++ let sample = [1; 5];
++ let indirect_len: VecDeque<_> = sample.iter().collect();
++ indirect_len.len()
++ }
++ fn lint_linked_list() -> usize {
++ let sample = [1; 5];
++ let indirect_len: LinkedList<_> = sample.iter().collect();
++ indirect_len.len()
++ }
++ fn lint_binary_heap() -> usize {
++ let sample = [1; 5];
++ let indirect_len: BinaryHeap<_> = sample.iter().collect();
++ indirect_len.len()
++ }
++ fn dont_lint(string: &str) -> usize {
++ let buffer: Vec<&str> = string.split('/').collect();
++ for buff in &buffer {
++ println!("{}", buff);
++ }
++ buffer.len()
++ }
++}
--- /dev/null
- error: aborting due to 5 previous errors
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect_indirect.rs:5:39
+ |
+LL | let indirect_iter = sample.iter().collect::<Vec<_>>();
+ | ^^^^^^^
+LL | indirect_iter.into_iter().map(|x| (x, x + 1)).collect::<HashMap<_, _>>();
+ | ------------------------- the iterator could be used here instead
+ |
+ = note: `-D clippy::needless-collect` implied by `-D warnings`
+help: use the original Iterator instead of collecting it and then producing a new one
+ |
+LL |
+LL | sample.iter().map(|x| (x, x + 1)).collect::<HashMap<_, _>>();
+ |
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect_indirect.rs:7:38
+ |
+LL | let indirect_len = sample.iter().collect::<VecDeque<_>>();
+ | ^^^^^^^
+LL | indirect_len.len();
+ | ------------------ the iterator could be used here instead
+ |
+help: take the original Iterator's count instead of collecting it and finding the length
+ |
+LL |
+LL | sample.iter().count();
+ |
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect_indirect.rs:9:40
+ |
+LL | let indirect_empty = sample.iter().collect::<VecDeque<_>>();
+ | ^^^^^^^
+LL | indirect_empty.is_empty();
+ | ------------------------- the iterator could be used here instead
+ |
+help: check if the original Iterator has anything instead of collecting it and seeing if it's empty
+ |
+LL |
+LL | sample.iter().next().is_none();
+ |
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect_indirect.rs:11:43
+ |
+LL | let indirect_contains = sample.iter().collect::<VecDeque<_>>();
+ | ^^^^^^^
+LL | indirect_contains.contains(&&5);
+ | ------------------------------- the iterator could be used here instead
+ |
+help: check if the original Iterator contains an element instead of collecting then checking
+ |
+LL |
+LL | sample.iter().any(|x| x == &5);
+ |
+
+error: avoid using `collect()` when not needed
+ --> $DIR/needless_collect_indirect.rs:23:48
+ |
+LL | let non_copy_contains = sample.into_iter().collect::<Vec<_>>();
+ | ^^^^^^^
+LL | non_copy_contains.contains(&a);
+ | ------------------------------ the iterator could be used here instead
+ |
+help: check if the original Iterator contains an element instead of collecting then checking
+ |
+LL |
+LL | sample.into_iter().any(|x| x == a);
+ |
+
++error: avoid using `collect()` when not needed
++ --> $DIR/needless_collect_indirect.rs:52:51
++ |
++LL | let buffer: Vec<&str> = string.split('/').collect();
++ | ^^^^^^^
++LL | buffer.len()
++ | ------------ the iterator could be used here instead
++ |
++help: take the original Iterator's count instead of collecting it and finding the length
++ |
++LL |
++LL | string.split('/').count()
++ |
++
++error: avoid using `collect()` when not needed
++ --> $DIR/needless_collect_indirect.rs:57:55
++ |
++LL | let indirect_len: VecDeque<_> = sample.iter().collect();
++ | ^^^^^^^
++LL | indirect_len.len()
++ | ------------------ the iterator could be used here instead
++ |
++help: take the original Iterator's count instead of collecting it and finding the length
++ |
++LL |
++LL | sample.iter().count()
++ |
++
++error: avoid using `collect()` when not needed
++ --> $DIR/needless_collect_indirect.rs:62:57
++ |
++LL | let indirect_len: LinkedList<_> = sample.iter().collect();
++ | ^^^^^^^
++LL | indirect_len.len()
++ | ------------------ the iterator could be used here instead
++ |
++help: take the original Iterator's count instead of collecting it and finding the length
++ |
++LL |
++LL | sample.iter().count()
++ |
++
++error: avoid using `collect()` when not needed
++ --> $DIR/needless_collect_indirect.rs:67:57
++ |
++LL | let indirect_len: BinaryHeap<_> = sample.iter().collect();
++ | ^^^^^^^
++LL | indirect_len.len()
++ | ------------------ the iterator could be used here instead
++ |
++help: take the original Iterator's count instead of collecting it and finding the length
++ |
++LL |
++LL | sample.iter().count()
++ |
++
++error: aborting due to 9 previous errors
+
--- /dev/null
+// run-rustfix
+
+#![allow(unused_must_use)]
+
+use std::collections::HashSet;
+
+fn main() {
+ let x = "foo";
+ x.split('x');
+ x.split("xx");
+ x.split('x');
+
+ let y = "x";
+ x.split(y);
+ x.split('ß');
+ x.split('ℝ');
+ x.split('💣');
+ // Can't use this lint for unicode code points which don't fit in a char
+ x.split("❤️");
+ x.contains('x');
+ x.starts_with('x');
+ x.ends_with('x');
+ x.find('x');
+ x.rfind('x');
+ x.rsplit('x');
+ x.split_terminator('x');
+ x.rsplit_terminator('x');
+ x.splitn(0, 'x');
+ x.rsplitn(0, 'x');
+ x.matches('x');
+ x.rmatches('x');
+ x.match_indices('x');
+ x.rmatch_indices('x');
+ x.trim_start_matches('x');
+ x.trim_end_matches('x');
++ x.strip_prefix('x');
++ x.strip_suffix('x');
+ // Make sure we escape characters correctly.
+ x.split('\n');
+ x.split('\'');
+ x.split('\'');
+
+ let h = HashSet::<String>::new();
+ h.contains("X"); // should not warn
+
+ x.replace(";", ",").split(','); // issue #2978
+ x.starts_with('\x03'); // issue #2996
+
+ // Issue #3204
+ const S: &str = "#";
+ x.find(S);
+
+ // Raw string
+ x.split('a');
+ x.split('a');
+ x.split('a');
+ x.split('\'');
+ x.split('#');
+}
--- /dev/null
+// run-rustfix
+
+#![allow(unused_must_use)]
+
+use std::collections::HashSet;
+
+fn main() {
+ let x = "foo";
+ x.split("x");
+ x.split("xx");
+ x.split('x');
+
+ let y = "x";
+ x.split(y);
+ x.split("ß");
+ x.split("ℝ");
+ x.split("💣");
+ // Can't use this lint for unicode code points which don't fit in a char
+ x.split("❤️");
+ x.contains("x");
+ x.starts_with("x");
+ x.ends_with("x");
+ x.find("x");
+ x.rfind("x");
+ x.rsplit("x");
+ x.split_terminator("x");
+ x.rsplit_terminator("x");
+ x.splitn(0, "x");
+ x.rsplitn(0, "x");
+ x.matches("x");
+ x.rmatches("x");
+ x.match_indices("x");
+ x.rmatch_indices("x");
+ x.trim_start_matches("x");
+ x.trim_end_matches("x");
++ x.strip_prefix("x");
++ x.strip_suffix("x");
+ // Make sure we escape characters correctly.
+ x.split("\n");
+ x.split("'");
+ x.split("\'");
+
+ let h = HashSet::<String>::new();
+ h.contains("X"); // should not warn
+
+ x.replace(";", ",").split(","); // issue #2978
+ x.starts_with("\x03"); // issue #2996
+
+ // Issue #3204
+ const S: &str = "#";
+ x.find(S);
+
+ // Raw string
+ x.split(r"a");
+ x.split(r#"a"#);
+ x.split(r###"a"###);
+ x.split(r###"'"###);
+ x.split(r###"#"###);
+}
--- /dev/null
- --> $DIR/single_char_pattern.rs:37:13
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:9:13
+ |
+LL | x.split("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+ |
+ = note: `-D clippy::single-char-pattern` implied by `-D warnings`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:15:13
+ |
+LL | x.split("ß");
+ | ^^^ help: try using a `char` instead: `'ß'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:16:13
+ |
+LL | x.split("ℝ");
+ | ^^^ help: try using a `char` instead: `'ℝ'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:17:13
+ |
+LL | x.split("💣");
+ | ^^^^ help: try using a `char` instead: `'💣'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:20:16
+ |
+LL | x.contains("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:21:19
+ |
+LL | x.starts_with("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:22:17
+ |
+LL | x.ends_with("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:23:12
+ |
+LL | x.find("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:24:13
+ |
+LL | x.rfind("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:25:14
+ |
+LL | x.rsplit("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:26:24
+ |
+LL | x.split_terminator("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:27:25
+ |
+LL | x.rsplit_terminator("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:28:17
+ |
+LL | x.splitn(0, "x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:29:18
+ |
+LL | x.rsplitn(0, "x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:30:15
+ |
+LL | x.matches("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:31:16
+ |
+LL | x.rmatches("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:32:21
+ |
+LL | x.match_indices("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:33:22
+ |
+LL | x.rmatch_indices("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:34:26
+ |
+LL | x.trim_start_matches("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:35:24
+ |
+LL | x.trim_end_matches("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:38:13
++ --> $DIR/single_char_pattern.rs:36:20
++ |
++LL | x.strip_prefix("x");
++ | ^^^ help: try using a `char` instead: `'x'`
++
++error: single-character string constant used as pattern
++ --> $DIR/single_char_pattern.rs:37:20
++ |
++LL | x.strip_suffix("x");
++ | ^^^ help: try using a `char` instead: `'x'`
++
++error: single-character string constant used as pattern
++ --> $DIR/single_char_pattern.rs:39:13
+ |
+LL | x.split("/n");
+ | ^^^^ help: try using a `char` instead: `'/n'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:39:13
++ --> $DIR/single_char_pattern.rs:40:13
+ |
+LL | x.split("'");
+ | ^^^ help: try using a `char` instead: `'/''`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:44:31
++ --> $DIR/single_char_pattern.rs:41:13
+ |
+LL | x.split("/'");
+ | ^^^^ help: try using a `char` instead: `'/''`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:45:19
++ --> $DIR/single_char_pattern.rs:46:31
+ |
+LL | x.replace(";", ",").split(","); // issue #2978
+ | ^^^ help: try using a `char` instead: `','`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:52:13
++ --> $DIR/single_char_pattern.rs:47:19
+ |
+LL | x.starts_with("/x03"); // issue #2996
+ | ^^^^^^ help: try using a `char` instead: `'/x03'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:53:13
++ --> $DIR/single_char_pattern.rs:54:13
+ |
+LL | x.split(r"a");
+ | ^^^^ help: try using a `char` instead: `'a'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:54:13
++ --> $DIR/single_char_pattern.rs:55:13
+ |
+LL | x.split(r#"a"#);
+ | ^^^^^^ help: try using a `char` instead: `'a'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:55:13
++ --> $DIR/single_char_pattern.rs:56:13
+ |
+LL | x.split(r###"a"###);
+ | ^^^^^^^^^^ help: try using a `char` instead: `'a'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:56:13
++ --> $DIR/single_char_pattern.rs:57:13
+ |
+LL | x.split(r###"'"###);
+ | ^^^^^^^^^^ help: try using a `char` instead: `'/''`
+
+error: single-character string constant used as pattern
- error: aborting due to 30 previous errors
++ --> $DIR/single_char_pattern.rs:58:13
+ |
+LL | x.split(r###"#"###);
+ | ^^^^^^^^^^ help: try using a `char` instead: `'#'`
+
++error: aborting due to 32 previous errors
+
--- /dev/null
+fn main() {
+ let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None });
+ let _ = (0..4).filter_map(|x| {
+ if x > 1 {
+ return Some(x);
+ };
+ None
+ });
+ let _ = (0..4).filter_map(|x| match x {
+ 0 | 1 => None,
+ _ => Some(x),
+ });
+
+ let _ = (0..4).filter_map(|x| Some(x + 1));
+
+ let _ = (0..4).filter_map(i32::checked_abs);
+}
++
++fn filter_map_none_changes_item_type() -> impl Iterator<Item = bool> {
++ "".chars().filter_map(|_| None)
++}
--- /dev/null
+// run-rustfix
+
+// The output for humans should just highlight the whole span without showing
+// the suggested replacement, but we also want to test that suggested
+// replacement only removes one set of parentheses, rather than naïvely
+// stripping away any starting or ending parenthesis characters—hence this
+// test of the JSON error format.
+
+#![feature(custom_inner_attributes)]
+#![rustfmt::skip]
+
+#![deny(clippy::unused_unit)]
+#![allow(dead_code)]
+#![allow(clippy::from_over_into)]
+
+struct Unitter;
+impl Unitter {
+ #[allow(clippy::no_effect)]
+ pub fn get_unit<F: Fn(), G>(&self, f: F, _g: G)
+ where G: Fn() {
+ let _y: &dyn Fn() = &f;
+ (); // this should not lint, as it's not in return type position
+ }
+}
+
+impl Into<()> for Unitter {
+ #[rustfmt::skip]
+ fn into(self) {
+
+ }
+}
+
+trait Trait {
+ fn redundant<F: FnOnce(), G, H>(&self, _f: F, _g: G, _h: H)
+ where
+ G: FnMut(),
+ H: Fn();
+}
+
+impl Trait for Unitter {
+ fn redundant<F: FnOnce(), G, H>(&self, _f: F, _g: G, _h: H)
+ where
+ G: FnMut(),
+ H: Fn() {}
+}
+
+fn return_unit() { }
+
+#[allow(clippy::needless_return)]
+#[allow(clippy::never_loop)]
+#[allow(clippy::unit_cmp)]
+fn main() {
+ let u = Unitter;
+ assert_eq!(u.get_unit(|| {}, return_unit), u.into());
+ return_unit();
+ loop {
+ break;
+ }
+ return;
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/4076
+fn foo() {
+ macro_rules! foo {
+ (recv($r:expr) -> $res:pat => $body:expr) => {
+ $body
+ }
+ }
+
+ foo! {
+ recv(rx) -> _x => ()
+ }
+}
+
+#[rustfmt::skip]
+fn test(){}
+
+#[rustfmt::skip]
+fn test2(){}
+
+#[rustfmt::skip]
+fn test3(){}
++
++fn macro_expr() {
++ macro_rules! e {
++ () => (());
++ }
++ e!()
++}
--- /dev/null
+// run-rustfix
+
+// The output for humans should just highlight the whole span without showing
+// the suggested replacement, but we also want to test that suggested
+// replacement only removes one set of parentheses, rather than naïvely
+// stripping away any starting or ending parenthesis characters—hence this
+// test of the JSON error format.
+
+#![feature(custom_inner_attributes)]
+#![rustfmt::skip]
+
+#![deny(clippy::unused_unit)]
+#![allow(dead_code)]
+#![allow(clippy::from_over_into)]
+
+struct Unitter;
+impl Unitter {
+ #[allow(clippy::no_effect)]
+ pub fn get_unit<F: Fn() -> (), G>(&self, f: F, _g: G) -> ()
+ where G: Fn() -> () {
+ let _y: &dyn Fn() -> () = &f;
+ (); // this should not lint, as it's not in return type position
+ }
+}
+
+impl Into<()> for Unitter {
+ #[rustfmt::skip]
+ fn into(self) -> () {
+ ()
+ }
+}
+
+trait Trait {
+ fn redundant<F: FnOnce() -> (), G, H>(&self, _f: F, _g: G, _h: H)
+ where
+ G: FnMut() -> (),
+ H: Fn() -> ();
+}
+
+impl Trait for Unitter {
+ fn redundant<F: FnOnce() -> (), G, H>(&self, _f: F, _g: G, _h: H)
+ where
+ G: FnMut() -> (),
+ H: Fn() -> () {}
+}
+
+fn return_unit() -> () { () }
+
+#[allow(clippy::needless_return)]
+#[allow(clippy::never_loop)]
+#[allow(clippy::unit_cmp)]
+fn main() {
+ let u = Unitter;
+ assert_eq!(u.get_unit(|| {}, return_unit), u.into());
+ return_unit();
+ loop {
+ break();
+ }
+ return();
+}
+
+// https://github.com/rust-lang/rust-clippy/issues/4076
+fn foo() {
+ macro_rules! foo {
+ (recv($r:expr) -> $res:pat => $body:expr) => {
+ $body
+ }
+ }
+
+ foo! {
+ recv(rx) -> _x => ()
+ }
+}
+
+#[rustfmt::skip]
+fn test()->(){}
+
+#[rustfmt::skip]
+fn test2() ->(){}
+
+#[rustfmt::skip]
+fn test3()-> (){}
++
++fn macro_expr() {
++ macro_rules! e {
++ () => (());
++ }
++ e!()
++}
--- /dev/null
- r'''/// Lint: ([\w,\s]+)\. (.*)\n\s*\([^,]+,\s+"([^"]+)":\s+([^,]+),\s+([^\.\)]+).*\),''', re.MULTILINE)
+# Common utils for the several housekeeping scripts.
+
+import os
+import re
+import collections
+
+import logging as log
+log.basicConfig(level=log.INFO, format='%(levelname)s: %(message)s')
+
+Lint = collections.namedtuple('Lint', 'name level doc sourcefile group')
+Config = collections.namedtuple('Config', 'name ty doc default')
+
+lintname_re = re.compile(r'''pub\s+([A-Z_][A-Z_0-9]*)''')
+group_re = re.compile(r'''\s*([a-z_][a-z_0-9]+)''')
+conf_re = re.compile(r'''define_Conf! {\n([^}]*)\n}''', re.MULTILINE)
+confvar_re = re.compile(
++ r'''/// Lint: ([\w,\s]+)\. (.*)\n\s*\(([^:]+):\s*([^\s=]+)\s*=\s*([^\.\)]+).*\),''', re.MULTILINE)
+comment_re = re.compile(r'''\s*/// ?(.*)''')
+
+lint_levels = {
+ "correctness": 'Deny',
+ "style": 'Warn',
+ "complexity": 'Warn',
+ "perf": 'Warn',
+ "restriction": 'Allow',
+ "pedantic": 'Allow',
+ "nursery": 'Allow',
+ "cargo": 'Allow',
+}
+
+
+def parse_lints(lints, filepath):
+ comment = []
+ clippy = False
+ deprecated = False
+ name = ""
+
+ with open(filepath) as fp:
+ for line in fp:
+ if clippy or deprecated:
+ m = lintname_re.search(line)
+ if m:
+ name = m.group(1).lower()
+ line = next(fp)
+
+ if deprecated:
+ level = "Deprecated"
+ group = "deprecated"
+ else:
+ while True:
+ g = group_re.search(line)
+ if g:
+ group = g.group(1).lower()
+ level = lint_levels.get(group, None)
+ break
+ line = next(fp)
+
+ if level is None:
+ continue
+
+ log.info("found %s with level %s in %s",
+ name, level, filepath)
+ lints.append(Lint(name, level, comment, filepath, group))
+ comment = []
+
+ clippy = False
+ deprecated = False
+ name = ""
+ else:
+ m = comment_re.search(line)
+ if m:
+ comment.append(m.group(1))
+ elif line.startswith("declare_clippy_lint!"):
+ clippy = True
+ deprecated = False
+ elif line.startswith("declare_deprecated_lint!"):
+ clippy = False
+ deprecated = True
+ elif line.startswith("declare_lint!"):
+ import sys
+ print(
+ "don't use `declare_lint!` in Clippy, "
+ "use `declare_clippy_lint!` instead"
+ )
+ sys.exit(42)
+
+
+def parse_configs(path):
+ configs = {}
+ with open(os.path.join(path, 'utils/conf.rs')) as fp:
+ contents = fp.read()
+
+ match = re.search(conf_re, contents)
+ confvars = re.findall(confvar_re, match.group(1))
+
+ for (lints, doc, name, ty, default) in confvars:
+ for lint in lints.split(','):
+ configs[lint.strip().lower()] = Config(name.replace("_", "-"), ty, doc, default)
+ return configs
+
+
+def parse_all(path="clippy_lints/src"):
+ lints = []
+ for root, dirs, files in os.walk(path):
+ for fn in files:
+ if fn.endswith('.rs'):
+ parse_lints(lints, os.path.join(root, fn))
+
+ log.info("got %s lints", len(lints))
+
+ configs = parse_configs(path)
+ log.info("got %d configs", len(configs))
+
+ return lints, configs