# Clippy

A collection of lints to catch common mistakes and improve your Rust code.

There are over 550 lints included in this crate!

Lints are divided into categories, each with a default lint level. You can choose how much Clippy is supposed to annoy help you by changing the lint level by category.

CategoryDescriptionDefault level
clippy::allall lints that are on by default (correctness, suspicious, style, complexity, perf)warn/deny
clippy::correctnesscode that is outright wrong or uselessdeny
clippy::suspiciouscode that is most likely wrong or uselesswarn
clippy::complexitycode that does something simple but in a complex waywarn
clippy::perfcode that can be written to run fasterwarn
clippy::stylecode that should be written in a more idiomatic waywarn
clippy::pedanticlints which are rather strict or might have false positivesallow
clippy::nurserynew lints that are still under developmentallow
clippy::cargolints for the cargo manifestallow

More to come, please file an issue if you have ideas!

The lint list also contains "restriction lints", which are for things which are usually not considered "bad", but may be useful to turn on in specific cases. These should be used very selectively, if at all.

# Installation

If you're using rustup to install and manage you're Rust toolchains, Clippy is usually already installed. In that case you can skip this chapter and go to the Usage chapter.

Note: If you used the minimal profile when installing a Rust toolchain, Clippy is not automatically installed.

## Using Rustup

If Clippy was not installed for a toolchain, it can be installed with

$rustup component add clippy [--toolchain=<name>]  ## From Source Take a look at the Basics chapter in the Clippy developer guide to find step by step instructions on how to build and install Clippy from source. # Usage This chapter describes how to use Clippy to get the most out of it. Clippy can be used as a cargo subcommand or, like rustc, directly with the clippy-driver binary. Note: This chapter assumes that you have Clippy installed already. If you're not sure, take a look at the Installation chapter. ## Cargo subcommand The easiest and most common way to run Clippy is through cargo. To do that, just run cargo clippy  ### Lint configuration The above command will run the default set of lints, which are included in the lint group clippy::all. You might want to use even more lints or you might not agree with every Clippy lint, and for that there are ways to configure lint levels. Note: Clippy is meant to be used with a generous sprinkling of #[allow(..)]s through your code. So if you disagree with a lint, don't feel bad disabling them for parts of your code or the whole project. #### Command line You can configure lint levels on the command line by adding -A/W/D clippy::lint_name like this: cargo clippy -- -Aclippy::style -Wclippy::double_neg -Dclippy::perf  For CI all warnings can be elevated to errors which will inturn fail the build and cause Clippy to exit with a code other than 0. cargo clippy -- -Dwarnings  Note: Adding -D warnings will cause your build to fail if any warnings are found in your code. That includes warnings found by rustc (e.g. dead_code, etc.). For more information on configuring lint levels, see the rustc documentation. #### Even more lints Clippy has lint groups which are allow-by-default. This means, that you will have to enable the lints in those groups manually. For a full list of all lints with their description and examples, please refer to Clippy's lint list. The two most important allow-by-default groups are described below: ##### clippy::pedantic The first group is the pedantic group. This group contains really opinionated lints, that may have some intentional false positives in order to prevent false negatives. So while this group is ready to be used in production, you can expect to sprinkle multiple #[allow(..)]s in your code. If you find any false positives, you're still welcome to report them to us for future improvements. FYI: Clippy uses the whole group to lint itself. ##### clippy::restriction The second group is the restriction group. This group contains lints that "restrict" the language in some way. For example the clippy::unwrap lint from this group won't allow you to use .unwrap() in your code. You may want to look through the lints in this group and enable the ones that fit your need. Note: You shouldn't enable the whole lint group, but cherry-pick lints from this group. Some lints in this group will even contradict other Clippy lints! #### Too many lints The most opinionated warn-by-default group of Clippy is the clippy::style group. Some people prefer to disable this group completely and then cherry-pick some lints they like from this group. The same is of course possible with every other of Clippy's lint groups. Note: We try to keep the warn-by-default groups free from false positives (FP). If you find that a lint wrongly triggers, please report it in an issue (if there isn't an issue for that FP already) #### Source Code You can configure lint levels in source code the same way you can configure rustc lints: #![allow(clippy::style)] #[warn(clippy::double_neg)] fn main() { let x = 1; let y = --x; // ^^ warning: double negation }  ### Automatically applying Clippy suggestions Clippy can automatically apply some lint suggestions, just like the compiler. cargo clippy --fix  ### Workspaces All the usual workspace options should work with Clippy. For example the following command will run Clippy on the example crate in your workspace: cargo clippy -p example  As with cargo check, this includes dependencies that are members of the workspace, like path dependencies. If you want to run Clippy only on the given crate, use the --no-deps option like this: cargo clippy -p example -- --no-deps  ## Using Clippy without cargo: clippy-driver Clippy can also be used in projects that do not use cargo. To do so, run clippy-driver with the same arguments you use for rustc. For example: clippy-driver --edition 2018 -Cpanic=abort foo.rs  Note: clippy-driver is designed for running Clippy and should not be used as a general replacement for rustc. clippy-driver may produce artifacts that are not optimized as expected, for example. # Configuring Clippy Note: The configuration file is unstable and may be deprecated in the future. Some lints can be configured in a TOML file named clippy.toml or .clippy.toml. It contains a basic variable = value mapping eg. avoid-breaking-exported-api = false disallowed-names = ["toto", "tata", "titi"] cognitive-complexity-threshold = 30  See the list of configurable lints, the lint descriptions contain the names and meanings of these configuration variables. To deactivate the "for further information visit lint-link" message you can define the CLIPPY_DISABLE_DOCS_LINKS environment variable. ### Allowing/denying lints You can add options to your code to allow/warn/deny Clippy lints: • the whole set of Warn lints using the clippy lint group (#![deny(clippy::all)]) • all lints using both the clippy and clippy::pedantic lint groups (#![deny(clippy::all)], #![deny(clippy::pedantic)]). Note that clippy::pedantic contains some very aggressive lints prone to false positives. • only some lints (#![deny(clippy::single_match, clippy::box_vec)], etc.) • allow/warn/deny can be limited to a single function or module using #[allow(...)], etc. Note: allow means to suppress the lint for your code. With warn the lint will only emit a warning, while with deny the lint will emit an error, when triggering for your code. An error causes clippy to exit with an error code, so is useful in scripts like CI/CD. If you do not want to include your lint levels in your code, you can globally enable/disable lints by passing extra flags to Clippy during the run: To allow lint_name, run cargo clippy -- -A clippy::lint_name  And to warn on lint_name, run cargo clippy -- -W clippy::lint_name  This also works with lint groups. For example you can run Clippy with warnings for all lints enabled: cargo clippy -- -W clippy::pedantic  If you care only about a single lint, you can allow all others and then explicitly warn on the lint(s) you are interested in: cargo clippy -- -A clippy::all -W clippy::useless_format -W clippy::...  ### Specifying the minimum supported Rust version Projects that intend to support old versions of Rust can disable lints pertaining to newer features by specifying the minimum supported Rust version (MSRV) in the clippy configuration file. msrv = "1.30.0"  The MSRV can also be specified as an attribute, like below. #![feature(custom_inner_attributes)] #![clippy::msrv = "1.30.0"] fn main() { ... }  You can also omit the patch version when specifying the MSRV, so msrv = 1.30 is equivalent to msrv = 1.30.0. Note: custom_inner_attributes is an unstable feature so it has to be enabled explicitly. Lints that recognize this configuration option can be found here # Clippy's Lints Clippy offers a bunch of additional lints, to help its users write more correct and idiomatic Rust code. A full list of all lints, that can be filtered by category, lint level or keywords, can be found in the Clippy lint documentation. This chapter will give an overview of the different lint categories, which kind of lints they offer and recommended actions when you should see a lint out of that category. For examples, see the Clippy lint documentation and filter by category. The different lint groups were defined in the Clippy 1.0 RFC. ## Correctness The clippy::correctness group is the only lint group in Clippy which lints are deny-by-default and abort the compilation when triggered. This is for good reason: If you see a correctness lint, it means that your code is outright wrong or useless and you should try to fix it. Lints in this category are carefully picked and should be free of false positives. So just #[allow]ing those lints is not recommended. ## Suspicious The clippy::suspicious group is similar to the correctness lints in that it contains lints that trigger on code that is really sus and should be fixed. As opposed to correctness lints, it might be possible that the linted code is intentionally written like it is. It is still recommended to fix code that is linted by lints out of this group instead of #[allow]ing the lint. In case you intentionally have written code that offends the lint you should specifically and locally #[allow] the lint and add give a reason why the code is correct as written. ## Complexity The clippy::complexity group offers lints that give you suggestions on how to simplify your code. It mostly focuses on code that can be written in a shorter and more readable way, while preserving the semantics. If you should see a complexity lint, it usually means that you can remove or replace some code and it is recommended to do so. However, if you need the more complex code for some expressiveness reason, it is recommended to allow complexity lints on a case-by-case basis. ## Perf The clippy::perf group gives you suggestions on how you can increase the performance of your code. Those lints are mostly about code that the compiler can't trivially optimize, but has to be written in a slightly different way to make the optimizer's job easier. Perf lints are usually easy to apply and it is recommended to do so. ## Style The clippy::style group is mostly about writing idiomatic code. Because style is subjective, this lint group is the most opinionated warn-by-default group in Clippy. If you see a style lint, applying the suggestion usually makes your code more readable and idiomatic. But because we know that this is opinionated, feel free to sprinkle #[allow]s for style lints in your code or #![allow] a style lint on your whole crate if you disagree with the suggested style completely. ## Pedantic The clippy::pedantic group makes Clippy even more pedantic. You can enable the whole group with #![warn(clippy::pedantic)] in the lib.rs/main.rs of your crate. This lint group is for Clippy power users that want an in depth check of their code. Note: Instead of enabling the whole group (like Clippy itself does), you may want to cherry-pick lints out of the pedantic group. If you enable this group, expect to also use #[allow] attributes generously throughout your code. Lints in this group are designed to be pedantic and false positives sometimes are intentional in order to prevent false negatives. ## Restriction The clippy::restriction group contains lints that will restrict you from using certain parts of the Rust language. It is not recommended to enable the whole group, but rather cherry-pick lints that are useful for your code base and your use case. Note: Clippy will produce a warning if it finds a #![warn(clippy::restriction)] attribute in your code! Lints from this group will restrict you in some way. If you enable a restriction lint for your crate it is recommended to also fix code that this lint triggers on. However, those lints are really strict by design and you might want to #[allow] them in some special cases, with a comment justifying that. ## Cargo The clippy::cargo group gives you suggestions on how to improve your Cargo.toml file. This might be especially interesting if you want to publish your crate and are not sure if you have all useful information in your Cargo.toml. # Continuous Integration It is recommended to run Clippy on CI with -Dwarnings, so that Clippy lints prevent CI from passing. To enforce errors on warnings on all cargo commands not just cargo clippy, you can set the env var RUSTFLAGS="-Dwarnings". We recommend to use Clippy from the same toolchain, that you use for compiling your crate for maximum compatibility. E.g. if your crate is compiled with the stable toolchain, you should also use stable Clippy. Note: New Clippy lints are first added to the nightly toolchain. If you want to help with improving Clippy and have CI resources left, please consider adding a nightly Clippy check to your CI and report problems like false positives back to us. With that we can fix bugs early, before they can get to stable. This chapter will give an overview on how to use Clippy on different popular CI providers. # GitHub Actions GitHub hosted runners using the latest stable version of Rust have Clippy pre-installed. It is as simple as running cargo clippy to run lints against the codebase. on: push name: Clippy check # Make sure CI fails on all warnings, including Clippy lints env: RUSTFLAGS: "-Dwarnings" jobs: clippy_check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run Clippy run: cargo clippy --all-targets --all-features  # Travis CI You can add Clippy to Travis CI in the same way you use it locally: language: rust rust: - stable - beta before_script: - rustup component add clippy script: - cargo clippy # if you want the build job to fail when encountering warnings, use - cargo clippy -- -D warnings # in order to also check tests and non-default crate features, use - cargo clippy --all-targets --all-features -- -D warnings - cargo test # etc.  # Clippy Development Hello fellow Rustacean! If you made it here, you're probably interested in making Clippy better by contributing to it. In that case, welcome to the project! Note: If you're just interested in using Clippy, there's nothing to see from this point onward and you should return to one of the earlier chapters. ## Getting started If this is your first time contributing to Clippy, you should first read the Basics docs. This will explain the basics on how to get the source code and how to compile and test the code. ## Writing code If you have done the basic setup, it's time to start hacking. The Adding lints chapter is a walk through on how to add a new lint to Clippy. This is also interesting if you just want to fix a lint, because it also covers how to test lints and gives an overview of the bigger picture. If you want to add a new lint or change existing ones apart from bugfixing, it's also a good idea to give the stability guarantees and lint categories sections of the Clippy 1.0 RFC a quick read. The lint categories are also described earlier in this book. Note: Some higher level things about contributing to Clippy are still covered in the CONTRIBUTING.md document. Some of those will be moved to the book over time, like: • Finding something to fix • IDE setup • High level overview on how Clippy works • Triage procedure • Bors and Homu # Basics for hacking on Clippy This document explains the basics for hacking on Clippy. Besides others, this includes how to build and test Clippy. For a more in depth description on the codebase take a look at Adding Lints or Common Tools. ## Get the Code First, make sure you have checked out the latest version of Clippy. If this is your first time working on Clippy, create a fork of the repository and clone it afterwards with the following command: git clone git@github.com:<your-username>/rust-clippy  If you've already cloned Clippy in the past, update it to the latest version: # If the upstream remote has not been added yet git remote add upstream https://github.com/rust-lang/rust-clippy # upstream has to be the remote of the rust-lang/rust-clippy repo git fetch upstream # make sure that you are on the master branch git checkout master # rebase your master branch on the upstream master git rebase upstream/master # push to the master branch of your fork git push  ## Building and Testing You can build and test Clippy like every other Rust project: cargo build # builds Clippy cargo test # tests Clippy  Since Clippy's test suite is pretty big, there are some commands that only run a subset of Clippy's tests: # only run UI tests cargo uitest # only run UI tests starting with test_ TESTNAME="test_" cargo uitest # only run dogfood tests cargo dev dogfood  If the output of a UI test differs from the expected output, you can update the reference file with: cargo dev bless  For example, this is necessary if you fix a typo in an error message of a lint, or if you modify a test file to add a test case. Note: This command may update more files than you intended. In that case only commit the files you wanted to update. ## cargo dev Clippy has some dev tools to make working on Clippy more convenient. These tools can be accessed through the cargo dev command. Available tools are listed below. To get more information about these commands, just call them with --help. # formats the whole Clippy codebase and all tests cargo dev fmt # register or update lint names/groups/... cargo dev update_lints # create a new lint and register it cargo dev new_lint # deprecate a lint and attempt to remove code relating to it cargo dev deprecate # automatically formatting all code before each commit cargo dev setup git-hook # (experimental) Setup Clippy to work with IntelliJ-Rust cargo dev setup intellij # runs the dogfood tests cargo dev dogfood  More about intellij command usage and reasons. ## lintcheck cargo lintcheck will build and run clippy on a fixed set of crates and generate a log of the results. You can git diff the updated log against its previous version and see what impact your lint made on a small set of crates. If you add a new lint, please audit the resulting warnings and make sure there are no false positives and that the suggestions are valid. Refer to the tools README for more details. ## PR We follow a rustc no merge-commit policy. See https://rustc-dev-guide.rust-lang.org/contributing.html#opening-a-pr. ## Common Abbreviations AbbreviationMeaning UBUndefined Behavior FPFalse Positive FNFalse Negative ICEInternal Compiler Error ASTAbstract Syntax Tree MIRMid-Level Intermediate Representation HIRHigh-Level Intermediate Representation TCXType context This is a concise list of abbreviations that can come up during Clippy development. An extensive general list can be found in the rustc-dev-guide glossary. Always feel free to ask if an abbreviation or meaning is unclear to you. ## Install from source If you are hacking on Clippy and want to install it from source, do the following: First, take note of the toolchain override in /rust-toolchain. We will use this override to install Clippy into the right toolchain. Tip: You can view the active toolchain for the current directory with rustup show active-toolchain. From the Clippy project root, run the following command to build the Clippy binaries and copy them into the toolchain directory. This will override the currently installed Clippy component. cargo build --release --bin cargo-clippy --bin clippy-driver -Zunstable-options --out-dir "$(rustc --print=sysroot)/bin"


Now you may run cargo clippy in any project, using the toolchain where you just installed Clippy.

cd my-project
cargo +nightly-2021-07-01 clippy


...or clippy-driver

clippy-driver +nightly-2021-07-01 <filename>


If you need to restore the default Clippy installation, run the following (from the Clippy project root).

rustup component remove clippy
rustup component add clippy


DO NOT install using cargo install --path . --force since this will overwrite rustup proxies. That is, ~/.cargo/bin/cargo-clippy and ~/.cargo/bin/clippy-driver should be hard or soft links to ~/.cargo/bin/rustup. You can repair these by running rustup update.

# 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.

## Setup

See the Basics 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). If you're unsure if the name you chose fits the lint, take a look at our lint naming guidelines.

## Defining Our Lint

To get started, there are two ways to define our lint.

### Standalone

Command: cargo dev new_lint --name=foo_functions --pass=early --category=pedantic (category will default to nursery if not provided)

This command will create a new file: clippy_lints/src/foo_functions.rs, as well as register the lint.

### Specific Type

Command: cargo dev new_lint --name=foo_functions --type=functions --category=pedantic

This command will create a new file: clippy_lints/src/{type}/foo_functions.rs.

Notice how this command has a --type flag instead of --pass. Unlike a standalone definition, this lint won't be registered in the traditional sense. Instead, you will call your lint from within the type's lint pass, found in clippy_lints/src/{type}/mod.rs.

A "type" is just the name of a directory in clippy_lints/src, like functions in the example command. These are groupings of lints with common behaviors, so if your lint falls into one, it would be best to add it to that type.

### Tests Location

Both commands will create a file: tests/ui/foo_functions.rs. 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:

#![allow(unused)]
#![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: 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.

## 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

cargo dev lint 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:

#![allow(unused)]
fn main() {
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! macro, and we just need to update the auto-generated lint declaration to have a real description, something like this:

#![allow(unused)]
fn main() {
declare_clippy_lint! {
/// ### What it does
///
/// ### Why is this bad?
///
/// ### Example
/// rust
/// // example code
/// 
#[clippy::version = "1.29.0"]
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. To render and open this documentation locally in a browser, run cargo dev serve.
• The #[clippy::version] attribute will be rendered as part of the lint documentation. The value should be set to the current Rust version that the lint is developed in, it can be retrieved by running rustc -vV in the rust-clippy directory. The version is listed under release. (Use the version without the -nightly) suffix.
• FOO_FUNCTIONS is the name of our lint. Be sure to follow the lint naming guidelines 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
• 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:

#![allow(unused)]
fn main() {
// clippy_lints/src/foo_functions.rs

// .. imports and lint declaration ..

declare_lint_pass!(FooFunctions => [FOO_FUNCTIONS]);

impl EarlyLintPass for FooFunctions {}
}


## Lint registration

When using cargo dev new_lint, the lint is automatically registered and nothing more has to be done.

When declaring a new lint by hand and cargo dev update_lints is used, the lint pass may have to be registered manually in the register_plugins function in clippy_lints/src/lib.rs:

#![allow(unused)]
fn main() {
store.register_early_pass(|| Box::new(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 update_lints 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.

## 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 or LateLintPass.

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.

## 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:

#![allow(unused)]
fn main() {
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 method from the EarlyLintPass 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.

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:

#![allow(unused)]
fn main() {
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 .

## 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 method gives us access to FnKind that has the FnKind::Fn variant. It provides access to the name of the function/method via an Ident.

With that we can expand our check_fn method to:

#![allow(unused)]
fn main() {
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:

#![allow(unused)]
fn main() {
// 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.

## 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. This can be accessed later as msrvs::STR_STRIP_PREFIX, for example.

#![allow(unused)]
fn main() {
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.

#![allow(unused)]
fn main() {
pub struct ManualStrip {
msrv: Msrv,
}

impl ManualStrip {
#[must_use]
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
}


The project's MSRV can then be matched against the feature MSRV in the LintPass using the Msrv::meets method.

#![allow(unused)]
fn main() {
if !self.msrv.meets(msrvs::STR_STRIP_PREFIX) {
return;
}
}


The project's MSRV can also be specified as an attribute, which overrides the value from clippy.toml. This can be accounted for using the extract_msrv_attr!(LintContext) macro and passing LateContext/EarlyContext.

#![allow(unused)]
fn main() {
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 the lint's test file, tests/ui/manual_strip.rs in this example. It should have a case for the version below the MSRV and one with the same contents but for the MSRV version itself.

#![allow(unused)]
fn main() {
...

#[clippy::msrv = "1.44"]
fn msrv_1_44() {
/* something that would trigger the lint */
}

#[clippy::msrv = "1.45"]
fn msrv_1_45() {
/* something that would trigger the lint */
}
}


As a last step, the lint should be added to the lint documentation. This is done in clippy_lints/src/utils/conf.rs:

#![allow(unused)]
fn main() {
define_Conf! {
/// Lint: LIST, OF, LINTS, <THE_NEWLY_ADDED_LINT>. The minimum rust version that the project supports
(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. 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 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.

To implement a lint, it's helpful to first understand the internal representation that rustc uses. Clippy has the #[clippy::dump] attribute that prints the High-Level Intermediate Representation (HIR) of the item, statement, or expression that the attribute is attached to. To attach the attribute to expressions you often need to enable #![feature(stmt_expr_attributes)].

Here you can find an example, just select Tools and run Clippy.

## 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:

#![allow(unused)]
fn main() {
declare_clippy_lint! {
/// ### What it does
/// Checks for ... (describe what the lint matches).
///
/// ### Why is this bad?
/// Supply the reason for linting the code.
///
/// ### Example
///
/// rust,ignore
/// // A short example of code that triggers the lint
/// 
///
/// rust,ignore
/// // A short example of improved code that doesn't trigger the lint
/// 
#[clippy::version = "1.29.0"]
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.

## 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:

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.

## 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.

## PR Checklist

Before submitting your PR make sure you followed all of the basic requirements:

• [ ] Followed lint naming conventions
• [ ] 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_lints::utils::conf like this:

#![allow(unused)]
fn main() {
/// Lint: LINT_NAME.
///
/// <The configuration field doc comment>
(configuration_ident: Type = DefaultValue),
}


The doc comment is automatically added to the documentation of the listed lints. The default value will be formatted using the Debug implementation of the type.

2. Adding the configuration value to the lint impl struct:

1. This first requires the definition of a lint impl struct. Lint impl structs are usually generated with the declare_lint_pass! macro. This struct needs to be defined manually to add some kind of metadata to it:

#![allow(unused)]
fn main() {
// 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:

#![allow(unused)]
fn main() {
#[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. The configuration value is now cloned or copied into a local value that is then passed to the impl struct like this:

#![allow(unused)]
fn main() {
// 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.

1. The default configured value can be tested like any normal lint in tests/ui.
2. The configuration itself will be tested separately in tests/ui-toml. Simply add a new subfolder with a fitting name. This folder contains a clippy.toml file with the configuration value and a rust file that should be linted by Clippy. The test can otherwise be written as usual.

## Cheat Sheet

Here are some pointers to things you are likely going to need for every lint:

For EarlyLintPass lints:

For LateLintPass lints:

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.

# Common tools for writing lints

You may need following tooltips to catch up with common operations.

Useful Rustc dev guide links:

## Retrieving the type of an expression

Sometimes you may want to retrieve the type Ty of an expression Expr, for example to answer following questions:

• which type does this expression correspond to (using its TyKind)?
• is it a sized type?
• is it a primitive type?
• does it implement a trait?

This operation is performed using the expr_ty() method from the TypeckResults struct, that gives you access to the underlying structure Ty.

Example of use:

#![allow(unused)]
fn main() {
impl LateLintPass<'_> for MyStructLint {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
// Get type of expr
let ty = cx.typeck_results().expr_ty(expr);
// Match its kind to enter its type
match ty.kind {
ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our expr is a struct!"),
_ => ()
}
}
}
}


Similarly in TypeckResults methods, you have the pat_ty() method to retrieve a type from a pattern.

Two noticeable items here:

• cx is the lint context LateContext. The two most useful data structures in this context are tcx and the TypeckResults returned by LateContext::typeck_results, allowing us to jump to type definitions and other compilation stages such as HIR.
• typeck_results's return value is TypeckResults and is created by type checking step, it includes useful information such as types of expressions, ways to resolve methods and so on.

## Checking if an expr is calling a specific method

Starting with an expr, you can check whether it is calling a specific method some_method:

#![allow(unused)]
fn main() {
impl<'tcx> LateLintPass<'tcx> for MyStructLint {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
// Check our expr is calling a method
if let hir::ExprKind::MethodCall(path, _, _self_arg, ..) = &expr.kind
// Check the name of this method is some_method
&& path.ident.name == sym!(some_method)
// Optionally, check the type of the self argument.
// - See "Checking for a specific type"
{
// ...
}
}
}
}


## Checking for a specific type

There are three ways to check if an expression type is a specific type we want to check for. All of these methods only check for the base type, generic arguments have to be checked separately.

#![allow(unused)]
fn main() {
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::{paths, match_def_path};
use rustc_span::symbol::sym;
use rustc_hir::LangItem;

impl LateLintPass<'_> for MyStructLint {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
// Getting the expression type
let ty = cx.typeck_results().expr_ty(expr);

// 1. Using diagnostic items
// The last argument is the diagnostic item to check for
if is_type_diagnostic_item(cx, ty, sym::Option) {
// The type is an Option
}

// 2. Using lang items
if is_type_lang_item(cx, ty, LangItem::RangeFull) {
// The type is a full range like .drain(..)
}

// 3. Using the type path
// This method should be avoided if possible
if match_def_path(cx, def_id, &paths::RESULT) {
// The type is a core::result::Result
}
}
}
}


Prefer using diagnostic items and lang items where possible.

## Checking if a type implements a specific trait

There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither.

#![allow(unused)]
fn main() {
use clippy_utils::ty::implements_trait;
use clippy_utils::is_trait_method;
use rustc_span::symbol::sym;

impl LateLintPass<'_> for MyStructLint {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
// 1. Using diagnostic items with the expression
// we use is_trait_method function from Clippy's utils
if is_trait_method(cx, expr, sym::Iterator) {
// method call in expr belongs to Iterator trait
}

// 2. Using lang items with the expression type
let ty = cx.typeck_results().expr_ty(expr);
if cx.tcx.lang_items()
// we are looking for the DefId of Drop trait in lang items
.drop_trait()
// then we use it with our type ty by calling implements_trait from Clippy's utils
.map_or(false, |id| implements_trait(cx, ty, id, &[])) {
// expr implements Drop trait
}
}
}
}


Prefer using diagnostic and lang items, if the target trait has one.

We access lang items through the type context tcx. tcx is of type TyCtxt and is defined in the rustc_middle crate. A list of defined paths for Clippy can be found in paths.rs

## Checking if a type defines a specific method

To check if our type defines a method called some_method:

#![allow(unused)]
fn main() {
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::return_ty;

impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
// Check if item is a method/function
if let ImplItemKind::Fn(ref signature, _) = impl_item.kind
// Check the method is named some_method
&& impl_item.ident.name == sym!(some_method)
// We can also check it has a parameter self
&& signature.decl.implicit_self.has_implicit_self()
// We can go further and even check if its return type is String
&& is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type))
{
// ...
}
}
}
}


## Dealing with macros and expansions

Keep in mind that macros are already expanded and desugaring is already applied to the code representation that you are working with in Clippy. This unfortunately causes a lot of false positives because macro expansions are "invisible" unless you actively check for them. Generally speaking, code with macro expansions should just be ignored by Clippy because that code can be dynamic in ways that are difficult or impossible to see. Use the following functions to deal with macros:

• span.from_expansion(): detects if a span is from macro expansion or desugaring. Checking this is a common first step in a lint.

#![allow(unused)]
fn main() {
if expr.span.from_expansion() {
// just forget it
return;
}
}

• span.ctxt(): the span's context represents whether it is from expansion, and if so, which macro call expanded it. It is sometimes useful to check if the context of two spans are equal.

#![allow(unused)]
fn main() {
// expands to 1 + 0, but don't lint
1 + mac!()
}

#![allow(unused)]
fn main() {
if left.span.ctxt() != right.span.ctxt() {
// the coder most likely cannot modify this expression
return;
}
}


Note: Code that is not from expansion is in the "root" context. So any spans where from_expansion returns true can be assumed to have the same context. And so just using span.from_expansion() is often good enough.

• in_external_macro(span): detect if the given span is from a macro defined in a foreign crate. If you want the lint to work with macro-generated code, this is the next line of defense to avoid macros not defined in the current crate. It doesn't make sense to lint code that the coder can't change.

You may want to use it for example to not start linting in macros from other crates

#![allow(unused)]
fn main() {
use rustc_middle::lint::in_external_macro;

use a_crate_with_macros::foo;

// foo is defined in a_crate_with_macros
foo!("bar");

// if we lint the match of foo call and test its span
assert_eq!(in_external_macro(cx.sess(), match_span), true);
}

• span.ctxt(): the span's context represents whether it is from expansion, and if so, what expanded it

One thing SpanContext is useful for is to check if two spans are in the same context. For example, in a == b, a and b have the same context. In a macro_rules! with a == $b, $b is expanded to some expression with a different context from a.

#![allow(unused)]
fn main() {
macro_rules! m {
($a:expr,$b:expr) => {
if $a.is_some() {$b;
}
}
}

let x: Option<u32> = Some(42);
m!(x, x.unwrap());

// These spans are not from the same context
// x.is_some() is from inside the macro
// x.unwrap() is from outside the macro
assert_eq!(x_is_some_span.ctxt(), x_unwrap_span.ctxt());
}


# Infrastructure

In order to deploy Clippy over rustup, some infrastructure is necessary. This chapter describes the different parts of the Clippy infrastructure that need to be maintained to make this possible.

The most important part is the sync between the rust-lang/rust repository and the Clippy repository that takes place every two weeks. This process is described in the Syncing changes between Clippy and rust-lang/rust section.

A new Clippy release is done together with every Rust release, so every six weeks. The release process is described in the Release a new Clippy Version section. During a release cycle a changelog entry for the next release has to be written. The format of that and how to do that is documented in the Changelog Update section.

Note: The Clippy CI should also be described in this chapter, but for now is left as a TODO.

# 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 subtrees in the Rust repository see Rust's CONTRIBUTING.md.

## 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. Put this file under /usr/lib/git-core (making a backup of the previous file) and make sure it has the proper permissions:

sudo cp --backup /path/to/patched/git-subtree.sh /usr/lib/git-core/git-subtree
sudo chmod --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree
sudo chown --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree


Note: The first time running git subtree push a cache has to be built. This involves going through the complete Clippy history once. For this you have to increase the stack limit though, which you can do with ulimit -s 60000. Make sure to run the ulimit command from the same session you call git subtree.

Note: If you are a Debian user, dash is the shell used by default for scripts instead of sh. This shell has a hardcoded recursion limit set to 1000. In order to make this process work, you need to force the script to run bash instead. You can do this by editing the first line of the git-subtree script and changing sh to bash.

## Defining remotes

You may want to define remotes, so you don't have to type out the remote addresses on every sync. You can do this with the following commands (these commands still have to be run inside the rust directory):

# Set clippy-upstream remote for pulls
$git remote add clippy-upstream https://github.com/rust-lang/rust-clippy # Make sure to not push to the upstream repo$ git remote set-url --push clippy-upstream DISABLED
# Set a local remote
$git remote add clippy-local /path/to/rust-clippy  Note: The following sections assume that you have set those remotes with the above remote names. ## Performing the sync from rust-lang/rust to Clippy Here is a TL;DR version of the sync process (all of the following commands have to be run inside the rust directory): 1. Clone the rust-lang/rust repository or make sure it is up to date. 2. Checkout the commit from the latest available nightly. You can get it using rustup check. 3. Sync the changes to the rust-copy of Clippy to your Clippy fork: # Make sure to change your-github-name to your github name in the following command. Also be # sure to either use a net-new branch, e.g. sync-from-rust, or delete the branch beforehand # because changes cannot be fast forwarded and you have to run this command again. git subtree push -P src/tools/clippy clippy-local sync-from-rust  Note: Most of the time you have to create a merge commit in the rust-clippy repo (this has to be done in the Clippy repo, not in the rust-copy of Clippy): git fetch upstream # assuming upstream is the rust-lang/rust remote git checkout sync-from-rust git merge upstream/master --no-ff  Note: This is one of the few instances where a merge commit is allowed in a PR. 4. Bump the nightly version in the Clippy repository by changing the date in the rust-toolchain file to the current date and committing it with the message: git commit -m "Bump nightly version -> YYYY-MM-DD"  5. Open a PR to rust-lang/rust-clippy and wait for it to get merged (to accelerate the process ping the @rust-lang/clippy team in your PR and/or ask them in the Zulip stream.) ## Performing the sync from Clippy to rust-lang/rust All of the following commands have to be run inside the rust directory. 1. Make sure you have checked out the latest master of rust-lang/rust. 2. Sync the rust-lang/rust-clippy master to the rust-copy of Clippy: git checkout -b sync-from-clippy git subtree pull -P src/tools/clippy clippy-upstream master  3. Open a PR to rust-lang/rust # Backport Changes Sometimes it is necessary to backport changes to the beta release of Clippy. Backports in Clippy are rare and should be approved by the Clippy team. For example, a backport is done, if a crucial ICE was fixed or a lint is broken to a point, that it has to be disabled, before landing on stable. Backports are done to the beta branch of Clippy. Backports to stable Clippy releases basically don't exist, since this would require a Rust point release, which is almost never justifiable for a Clippy fix. ## Backport the changes Backports are done on the beta branch of the Clippy repository. # Assuming the current directory corresponds to the Clippy repository$ git checkout beta
$git checkout -b backport$ git cherry-pick <SHA>  # <SHA> is the commit hash of the commit(s), that should be backported
$git push origin backport  Now you should test that the backport passes all the tests in the Rust repository. You can do this with: # Assuming the current directory corresponds to the Rust repository$ git checkout beta
$git subtree pull -p src/tools/clippy https://github.com/<your-github-name>/rust-clippy backport$ ./x.py test src/tools/clippy


Should the test fail, you can fix Clippy directly in the Rust repository. This has to be first applied to the Clippy beta branch and then again synced to the Rust repository, though. The easiest way to do this is:

# In the Rust repository
$git diff --patch --relative=src/tools/clippy > clippy.patch # In the Clippy repository$ git apply /path/to/clippy.patch
$git add -u$ git commit -m "Fix rustup fallout"
$git push origin backport  After this, you can open a PR to the beta branch of the Clippy repository. ## Update Clippy in the Rust Repository This step must be done, after the PR of the previous step was merged. After the backport landed in the Clippy repository, the branch has to be synced back to the beta branch of the Rust repository. # Assuming the current directory corresponds to the Rust repository$ git checkout beta
$git checkout -b clippy_backport$ git subtree pull -p src/tools/clippy https://github.com/rust-lang/rust-clippy beta
$git push origin clippy_backport  Make sure to test the backport in the Rust repository before opening a PR. This is done with ./x.py test src/tools/clippy. If that passes all tests, open a PR to the beta branch of the Rust repository. In this PR you should tag the Clippy team member, that agreed to the backport or the @rust-lang/clippy team. Make sure to add [beta] to the title of the PR. # Changelog Update If you want to help with updating the changelog, you're in the right place. ## When to update Typos and other small fixes/additions are always welcome. Special care needs to be taken when it comes to updating the changelog for a new Rust release. For that purpose, the changelog is ideally updated during the week before an upcoming stable release. You can find the release dates on the Rust Forge. Most of the time we only need to update the changelog for minor Rust releases. It's been very rare that Clippy changes were included in a patch release. ## Changelog update walkthrough ### 1. Finding the relevant Clippy commits Each Rust release ships with its own version of Clippy. The Clippy subtree can be found in the tools directory of the Rust repository. Depending on the current time and what exactly you want to update, the following bullet points might be helpful: • When writing the release notes for the upcoming stable release you need to check out the Clippy commit of the current Rust beta branch. Link • When writing the release notes for the upcoming beta release, you need to check out the Clippy commit of the current Rust master. Link • When writing the (forgotten) release notes for a past stable release, you need to check out the Rust release tag of the stable release. Link Usually you want to write the changelog of the upcoming stable release. Make sure though, that beta was already branched in the Rust repository. To find the commit hash, issue the following command when in a rust-lang/rust checkout: git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '$$[a-f0-9]*$$' into .*/\1/g"  ### 2. Fetching the PRs between those commits Once you've got the correct commit range, run util/fetch_prs_between.sh commit1 commit2 > changes.txt  and open that file in your editor of choice. When updating the changelog it's also a good idea to make sure that commit1 is already correct in the current changelog. ### 3. Authoring the final changelog The above script should have dumped all the relevant PRs to the file you specified. It should have filtered out most of the irrelevant PRs already, but it's a good idea to do a manual cleanup pass where you look for more irrelevant PRs. If you're not sure about some PRs, just leave them in for the review and ask for feedback. With the PRs filtered, you can start to take each PR and move the changelog:  content to CHANGELOG.md. Adapt the wording as you see fit but try to keep it somewhat coherent. The order should roughly be: 1. New lints 2. Moves or deprecations of lints 3. Changes that expand what code existing lints cover 4. False positive fixes 5. Suggestion fixes/improvements 6. ICE fixes 7. Documentation improvements 8. Others As section headers, we use: ### New Lints ### Moves and Deprecations ### Enhancements ### False Positive Fixes ### Suggestion Fixes/Improvements ### ICE Fixes ### Documentation Improvements ### Others  Please also be sure to update the Beta/Unreleased sections at the top with the relevant commit ranges. If you have the time, it would be appreciated if you double-check, that the #[clippy::version] attributes for the added lints contains the correct version. # Release a new Clippy Version NOTE: This document is probably only relevant to you, if you're a member of the Clippy team. Clippy is released together with stable Rust releases. The dates for these releases can be found at the Rust Forge. This document explains the necessary steps to create a Clippy release. NOTE: This document is for stable Rust releases, not for point releases. For point releases, step 1. and 2. should be enough. ## Remerge the beta branch This step is only necessary, if since the last release something was backported to the beta Rust release. The remerge is then necessary, to make sure that the Clippy commit, that was used by the now stable Rust release, persists in the tree of the Clippy repository. To find out if this step is necessary run # Assumes that the local master branch of rust-lang/rust-clippy is up-to-date$ git fetch upstream
$git branch master --contains upstream/beta  If this command outputs master, this step is not necessary. # Assuming HEAD is the current master branch of rust-lang/rust-clippy$ git checkout -b backport_remerge
$git merge upstream/beta$ git diff  # This diff has to be empty, otherwise something with the remerge failed
$git push origin backport_remerge # This can be pushed to your fork  After this, open a PR to the master branch. In this PR, the commit hash of the HEAD of the beta branch must exists. In addition to that, no files should be changed by this PR. ## Update the beta branch This step must be done after the PR of the previous step was merged. First, the Clippy commit of the beta branch of the Rust repository has to be determined. # Assuming the current directory corresponds to the Rust repository$ git fetch upstream
$git checkout upstream/beta$ BETA_SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '$$[a-f0-9]*$$' into .*/\1/g")  After finding the Clippy commit, the beta branch in the Clippy repository can be updated. # Assuming the current directory corresponds to the Clippy repository$ git checkout beta
$git reset --hard$BETA_SHA
$git push upstream rust-1.XX.0 # upstream is the rust-lang/rust-clippy remote  After this, the release should be available on the Clippy release page. ## Update the stable branch At this step you should have already checked out the commit of the rust-1.XX.0 tag. Updating the stable branch from here is as easy as: # Assuming the current directory corresponds to the Clippy repository and the # commit of the just created rust-1.XX.0 tag is checked out.$ git push upstream rust-1.XX.0:stable  # upstream is the rust-lang/rust-clippy remote


NOTE: Usually there are no stable backports for Clippy, so this update should be possible without force pushing or anything like this. If there should have happened a stable backport, make sure to re-merge those changes just as with the beta branch.

## Update CHANGELOG.md

For this see the document on how to update the changelog.

If you don't have time to do a complete changelog update right away, just update the following parts:

• Remove the (beta) from the new stable version:

## Rust 1.XX (beta) -> ## Rust 1.XX

• Update the release date line of the new stable version:

Current beta, release 20YY-MM-DD -> Current stable, released 20YY-MM-DD

• Update the release date line of the previous stable version:

Current stable, released 20YY-MM-DD -> Released 20YY-MM-DD


# The Clippy Book

This document explains how to make additions and changes to the Clippy book, the guide to Clippy that you're reading right now. The Clippy book is formatted with Markdown and generated by mdbook.

## Get mdbook

While not strictly necessary since the book source is simply Markdown text files, having mdbook locally will allow you to build, test and serve the book locally to view changes before you commit them to the repository. You likely already have cargo installed, so the easiest option is to simply:

cargo install mdbook


See the mdbook installation instructions for other options.

## Make changes

The book's src directory contains all of the markdown files used to generate the book. If you want to see your changes in real time, you can use the mdbook serve command to run a web server locally that will automatically update changes as they are made. From the top level of your rust-clippy directory:

mdbook serve book --open


Then navigate to http://localhost:3000 to see the generated book. While the server is running, changes you make will automatically be updated.

For more information, see the mdbook guide.

# Proposals

This chapter is about accepted proposals for changes that should be worked on in or around Clippy in the long run.

Besides adding more and more lints and improve the lints that Clippy already has, Clippy is also interested in making the experience of its users, developers and maintainers better over time. Projects that address bigger picture things like this usually take more time and it is useful to have a proposal for those first. This is the place where such proposals are collected, so that we can refer to them when working on them.

# Summary

This Roadmap lays out the plans for Clippy in 2021:

• Improving usability and reliability
• Improving experience of contributors and maintainers
• Develop and specify processes

Members of the Clippy team will be assigned tasks from one or more of these topics. The team member is then responsible to complete the assigned tasks. This can either be done by implementing them or by providing mentorship to interested contributors.

# Motivation

With the ongoing growth of the Rust language and with that of the whole ecosystem, also Clippy gets more and more users and contributors. This is good for the project, but also brings challenges along. Some of these challenges are:

• More issues about reliability or usability are popping up
• Traffic is hard to handle for a small team
• Bigger projects don't get completed due to the lack of processes and/or time of the team members

Additionally, according to the Rust Roadmap 2021, clear processes should be defined by every team and unified across teams. This Roadmap is the first step towards this.

# Explanation

This section will explain the things that should be done in 2021. It is important to note, that this document focuses on the "What?", not the "How?". The later will be addressed in follow-up tracking issue, with an assigned team member.

The following is split up in two major sections. The first section covers the user facing plans, the second section the internal plans.

## User Facing

Clippy should be as pleasant to use and configure as possible. This section covers plans that should be implemented to improve the situation of Clippy in this regard.

### Usability

In the following, plans to improve the usability are covered.

#### No Output After cargo check

Currently when cargo clippy is run after cargo check, it does not produce any output. This is especially problematic since rust-analyzer is on the rise and it uses cargo check for checking code. A fix is already implemented, but it still has to be pushed over the finish line. This also includes the stabilization of the cargo clippy --fix command or the support of multi-span suggestions in rustfix.

#### lints.toml Configuration

This is something that comes up every now and then: a reusable configuration file, where lint levels can be defined. Discussions about this often lead to nothing specific or to "we need an RFC for this". And this is exactly what needs to be done. Get together with the cargo team and write an RFC and implement such a configuration file somehow and somewhere.

#### Lint Groups

There are more and more issues about managing lints in Clippy popping up. Lints are hard to implement with a guarantee of no/few false positives (FPs). One way to address this might be to introduce more lint groups to give users the ability to better manage lints, or improve the process of classifying lints, so that disabling lints due to FPs becomes rare. It is important to note, that Clippy lints are less conservative than rustc lints, which won't change in the future.

### Reliability

In the following, plans to improve the reliability are covered.

#### False Positive Rate

In the worst case, new lints are only available in nightly for 2 weeks, before hitting beta and ultimately stable. This and the fact that fewer people use nightly Rust nowadays makes it more probable that a lint with many FPs hits stable. This leads to annoyed users, that will disable these new lints in the best case and to more annoyed users, that will stop using Clippy in the worst. A process should be developed and implemented to prevent this from happening.

## Internal

(The end of) 2020 has shown, that Clippy has to think about the available resources, especially regarding management and maintenance of the project. This section address issues affecting team members and contributors.

### Management

In 2020 Clippy achieved over 1000 open issues with regularly between 25-35 open PRs. This is simultaneously a win and a loss. More issues and PRs means more people are interested in Clippy and in contributing to it. On the other hand, it means for team members more work and for contributors longer wait times for reviews. The following will describe plans how to improve the situation for both team members and contributors.

#### Clear Expectations for Team Members

According to the Rust Roadmap 2021, a document specifying what it means to be a member of the team should be produced. This should not put more pressure on the team members, but rather help them and interested folks to know what the expectations are. With this it should also be easier to recruit new team members and may encourage people to get in touch, if they're interested to join.

#### Scaling up the Team

More people means less work for each individual. Together with the document about expectations for team members, a document defining the process of how to join the team should be produced. This can also increase the stability of the team, in case of current members dropping out (temporarily). There can also be different roles in the team, like people triaging vs. people reviewing.

#### Regular Meetings

Other teams have regular meetings. Clippy is big enough that it might be worth to also do them. Especially if more people join the team, this can be important for sync-ups. Besides the asynchronous communication, that works well for working on separate lints, a meeting adds a synchronous alternative at a known time. This is especially helpful if there are bigger things that need to be discussed (like the projects in this roadmap). For starters bi-weekly meetings before Rust syncs might make sense.

#### Triaging

To get a handle on the influx of open issues, a process for triaging issues and PRs should be developed. Officially, Clippy follows the Rust triage process, but currently no one enforces it. This can be improved by sharing triage teams across projects or by implementing dashboards / tools which simplify triaging.

### Development

Improving the developer and contributor experience is something the Clippy team works on regularly. Though, some things might need special attention and planing. These topics are listed in the following.

#### Process for New and Existing Lints

As already mentioned above, classifying new lints gets quite hard, because the probability of a buggy lint getting into stable is quite high. A process should be implemented on how to classify lints. In addition, a test system should be developed to find out which lints are currently problematic in real world code to fix or disable them.

#### Processes

Related to the point before, a process for suggesting and discussing major changes should be implemented. It's also not clearly defined when a lint should be enabled or disabled by default. This can also be improved by the test system mentioned above.

#### Dev-Tools

There's already cargo dev which makes Clippy development easier and more pleasant. This can still be expanded, so that it covers more areas of the development process.

#### Contributor Guide

Similar to a Clippy Book, which describes how to use Clippy, a book about how to contribute to Clippy might be helpful for new and existing contributors. There's already the doc directory in the Clippy repo, this can be turned into a mdbook.

#### rustc integration

Recently Clippy was integrated with git subtree into the rust-lang/rust repository. This made syncing between the two repositories easier. A #[non_exhaustive] list of things that still can be improved is:

1. Use the same rustfmt version and configuration as rustc.
2. Make cargo dev work in the Rust repo, just as it works in the Clippy repo. E.g. cargo dev bless or cargo dev update_lints. And even add more things to it that might be useful for the Rust repo, e.g. cargo dev deprecate.
3. Easier sync process. The subtree situation is not ideal.

## Prioritization

The most pressing issues for users of Clippy are of course the user facing issues. So there should be a priority on those issues, but without losing track of the internal issues listed in this document.

Getting the FP rate of warn/deny-by-default lints under control should have the highest priority. Other user facing issues should also get a high priority, but shouldn't be in the way of addressing internal issues.

To better manage the upcoming projects, the basic internal processes, like meetings, tracking issues and documentation, should be established as soon as possible. They might even be necessary to properly manage the projects, regarding the user facing issues.

# Prior Art

Rust's roadmap process was established by RFC 1728 in 2016. Since then every year a roadmap was published, that defined the bigger plans for the coming years. This years roadmap can be found here.

# Drawbacks

This roadmap is pretty big and not all items listed in this document might be addressed during 2021. Because this is the first roadmap for Clippy, having open tasks at the end of 2021 is fine, but they should be revisited in the 2022 roadmap.

• Feature Name: syntax-tree-patterns
• Start Date: 2019-03-12
• RFC PR: #3875

# Summary

Introduce a domain-specific language (similar to regular expressions) that allows to describe lints using syntax tree patterns.

# Motivation

Finding parts of a syntax tree (AST, HIR, ...) that have certain properties (e.g. "an if that has a block as its condition") is a major task when writing lints. For non-trivial lints, it often requires nested pattern matching of AST / HIR nodes. For example, testing that an expression is a boolean literal requires the following checks:

#![allow(unused)]
fn main() {
if let ast::ExprKind::Lit(lit) = &expr.node {
if let ast::LitKind::Bool(_) = &lit.node {
...
}
}
}


Writing this kind of matching code quickly becomes a complex task and the resulting code is often hard to comprehend. The code below shows a simplified version of the pattern matching required by the collapsible_if lint:

#![allow(unused)]
fn main() {
// simplified version of the collapsible_if lint
if let ast::ExprKind::If(check, then, None) = &expr.node {
if then.stmts.len() == 1 {
if let ast::StmtKind::Expr(inner) | ast::StmtKind::Semi(inner) = &then.stmts[0].node {
if let ast::ExprKind::If(check_inner, content, None) = &inner.node {
...
}
}
}
}
}


The if_chain macro can improve readability by flattening the nested if statements, but the resulting code is still quite hard to read:

#![allow(unused)]
fn main() {
// simplified version of the collapsible_if lint
if_chain! {
if let ast::ExprKind::If(check, then, None) = &expr.node;
if then.stmts.len() == 1;
if let ast::StmtKind::Expr(inner) | ast::StmtKind::Semi(inner) = &then.stmts[0].node;
if let ast::ExprKind::If(check_inner, content, None) = &inner.node;
then {
...
}
}
}


The code above matches if expressions that contain only another if expression (where both ifs don't have an else branch). While it's easy to explain what the lint does, it's hard to see that from looking at the code samples above.

Following the motivation above, the first goal this RFC is to simplify writing and reading lints.

The second part of the motivation is clippy's dependence on unstable compiler-internal data structures. Clippy lints are currently written against the compiler's AST / HIR which means that even small changes in these data structures might break a lot of lints. The second goal of this RFC is to make lints independant of the compiler's AST / HIR data structures.

# Approach

A lot of complexity in writing lints currently seems to come from having to manually implement the matching logic (see code samples above). It's an imparative style that describes how to match a syntax tree node instead of specifying what should be matched against declaratively. In other areas, it's common to use declarative patterns to describe desired information and let the implementation do the actual matching. A well-known example of this approach are regular expressions. Instead of writing code that detects certain character sequences, one can describe a search pattern using a domain-specific language and search for matches using that pattern. The advantage of using a declarative domain-specific language is that its limited domain (e.g. matching character sequences in the case of regular expressions) allows to express entities in that domain in a very natural and expressive way.

While regular expressions are very useful when searching for patterns in flat character sequences, they cannot easily be applied to hierarchical data structures like syntax trees. This RFC therefore proposes a pattern matching system that is inspired by regular expressions and designed for hierarchical syntax trees.

# Guide-level explanation

This proposal adds a pattern! macro that can be used to specify a syntax tree pattern to search for. A simple pattern is shown below:

#![allow(unused)]
fn main() {
pattern!{
my_pattern: Expr =
Lit(Bool(false))
}
}


This macro call defines a pattern named my_pattern that can be matched against an Expr syntax tree node. The actual pattern (Lit(Bool(false)) in this case) defines which syntax trees should match the pattern. This pattern matches expressions that are boolean literals with value false.

The pattern can then be used to implement lints in the following way:

#![allow(unused)]
fn main() {
...

impl EarlyLintPass for MyAwesomeLint {
fn check_expr(&mut self, cx: &EarlyContext, expr: &syntax::ast::Expr) {

if my_pattern(expr).is_some() {
cx.span_lint(
MY_AWESOME_LINT,
expr.span,
"This is a match for a simple pattern. Well done!",
);
}

}
}
}


The pattern! macro call expands to a function my_pattern that expects a syntax tree expression as its argument and returns an Option that indicates whether the pattern matched.

Note: The result type is explained in more detail in a later section. For now, it's enough to know that the result is Some if the pattern matched and None otherwise.

## Pattern syntax

The following examples demonstate the pattern syntax:

#### Any (_)

The simplest pattern is the any pattern. It matches anything and is therefore similar to regex's *.

#![allow(unused)]
fn main() {
pattern!{
// matches any expression
my_pattern: Expr =
_
}
}


#### Node (<node-name>(<args>))

Nodes are used to match a specific variant of an AST node. A node has a name and a number of arguments that depends on the node type. For example, the Lit node has a single argument that describes the type of the literal. As another example, the If node has three arguments describing the if's condition, then block and else block.

#![allow(unused)]
fn main() {
pattern!{
// matches any expression that is a literal
my_pattern: Expr =
Lit(_)
}

pattern!{
// matches any expression that is a boolean literal
my_pattern: Expr =
Lit(Bool(_))
}

pattern!{
// matches if expressions that have a boolean literal in their condition
// Note: The _? syntax here means that the else branch is optional and can be anything.
//       This is discussed in more detail in the section Repetition.
my_pattern: Expr =
If( Lit(Bool(_)) , _, _?)
}
}


#### Literal (<lit>)

A pattern can also contain Rust literals. These literals match themselves.

#![allow(unused)]
fn main() {
pattern!{
// matches the boolean literal false
my_pattern: Expr =
Lit(Bool(false))
}

pattern!{
// matches the character literal 'x'
my_pattern: Expr =
Lit(Char('x'))
}
}


#### Alternations (a | b)

#![allow(unused)]
fn main() {
pattern!{
// matches if the literal is a boolean or integer literal
my_pattern: Lit =
Bool(_) | Int(_)
}

pattern!{
// matches if the expression is a char literal with value 'x' or 'y'
my_pattern: Expr =
Lit( Char('x' | 'y') )
}
}


#### Empty (())

The empty pattern represents an empty sequence or the None variant of an optional.

#![allow(unused)]
fn main() {
pattern!{
// matches if the expression is an empty array
my_pattern: Expr =
Array( () )
}

pattern!{
// matches if expressions that don't have an else clause
my_pattern: Expr =
If(_, _, ())
}
}


#### Sequence (<a> <b>)

#![allow(unused)]
fn main() {
pattern!{
// matches the array [true, false]
my_pattern: Expr =
Array( Lit(Bool(true)) Lit(Bool(false)) )
}
}


#### Repetition (<a>*, <a>+, <a>?, <a>{n}, <a>{n,m}, <a>{n,})

Elements may be repeated. The syntax for specifying repetitions is identical to regex's syntax.

#![allow(unused)]
fn main() {
pattern!{
// matches arrays that contain 2 'x's as their last or second-last elements
// Examples:
//     ['x', 'x']                         match
//     ['x', 'x', 'y']                    match
//     ['a', 'b', 'c', 'x', 'x', 'y']     match
//     ['x', 'x', 'y', 'z']               no match
my_pattern: Expr =
Array( _* Lit(Char('x')){2} _? )
}

pattern!{
// matches if expressions that **may or may not** have an else block
// Attn: If(_, _, _) matches only ifs that **have** an else block
//
//              | if with else block | if witout else block
// If(_, _, _)  |       match        |       no match
// If(_, _, _?) |       match        |        match
// If(_, _, ()) |      no match      |        match
my_pattern: Expr =
If(_, _, _?)
}
}


#### Named submatch (<a>#<name>)

#![allow(unused)]
fn main() {
pattern!{
// matches character literals and gives the literal the name foo
my_pattern: Expr =
Lit(Char(_)#foo)
}

pattern!{
// matches character literals and gives the char the name bar
my_pattern: Expr =
Lit(Char(_#bar))
}

pattern!{
// matches character literals and gives the expression the name baz
my_pattern: Expr =
Lit(Char(_))#baz
}
}


The reason for using named submatches is described in the section The result type.

### Summary

The following table gives an summary of the pattern syntax:

SyntaxConceptExamples
_Any_
<node-name>(<args>)NodeLit(Bool(true)), If(_, _, _)
<lit>Literal'x', false, 101
<a> \| <b>AlternationChar(_) \| Bool(_)
()EmptyArray( () )
<a> <b>SequenceTuple( Lit(Bool(_)) Lit(Int(_)) Lit(_) )
<a>*
<a>+
<a>?
<a>{n}
<a>{n,m}
<a>{n,}
Repetition

Array( _* ),
Block( Semi(_)+ ),
If(_, _, Block(_)?),
Array( Lit(_){10} ),
Lit(_){5,10},
Lit(Bool(_)){10,}
<a>#<name>Named submatchLit(Int(_))#foo Lit(Int(_#bar))

## The result type

A lot of lints require checks that go beyond what the pattern syntax described above can express. For example, a lint might want to check whether a node was created as part of a macro expansion or whether there's no comment above a node. Another example would be a lint that wants to match two nodes that have the same value (as needed by lints like almost_swapped). Instead of allowing users to write these checks into the pattern directly (which might make patterns hard to read), the proposed solution allows users to assign names to parts of a pattern expression. When matching a pattern against a syntax tree node, the return value will contain references to all nodes that were matched by these named subpatterns. This is similar to capture groups in regular expressions.

For example, given the following pattern

#![allow(unused)]
fn main() {
pattern!{
// matches character literals
my_pattern: Expr =
Lit(Char(_#val_inner)#val)#val_outer
}
}


one could get references to the nodes that matched the subpatterns in the following way:

#![allow(unused)]
fn main() {
...
fn check_expr(expr: &syntax::ast::Expr) {
if let Some(result) = my_pattern(expr) {
result.val_inner  // type: &char
result.val        // type: &syntax::ast::Lit
result.val_outer  // type: &syntax::ast::Expr
}
}
}


The types in the result struct depend on the pattern. For example, the following pattern

#![allow(unused)]
fn main() {
pattern!{
// matches arrays of character literals
my_pattern_seq: Expr =
Array( Lit(_)*#foo )
}
}


matches arrays that consist of any number of literal expressions. Because those expressions are named foo, the result struct contains a foo attribute which is a vector of expressions:

#![allow(unused)]
fn main() {
...
if let Some(result) = my_pattern_seq(expr) {
result.foo        // type: Vec<&syntax::ast::Expr>
}
}


Another result type occurs when a name is only defined in one branch of an alternation:

#![allow(unused)]
fn main() {
pattern!{
// matches if expression is a boolean or integer literal
my_pattern_alt: Expr =
Lit( Bool(_#bar) | Int(_) )
}
}


In the pattern above, the bar name is only defined if the pattern matches a boolean literal. If it matches an integer literal, the name isn't set. To account for this, the result struct's bar attribute is an option type:

#![allow(unused)]
fn main() {
...
if let Some(result) = my_pattern_alt(expr) {
result.bar        // type: Option<&bool>
}
}


It's also possible to use a name in multiple alternation branches if they have compatible types:

#![allow(unused)]
fn main() {
pattern!{
// matches if expression is a boolean or integer literal
my_pattern_mult: Expr =
Lit(_#baz) | Array( Lit(_#baz) )
}
...
if let Some(result) = my_pattern_mult(expr) {
result.baz        // type: &syntax::ast::Lit
}
}


Named submatches are a flat namespace and this is intended. In the example above, two different sub-structures are assigned to a flat name. I expect that for most lints, a flat namespace is sufficient and easier to work with than a hierarchical one.

#### Two stages

Using named subpatterns, users can write lints in two stages. First, a coarse selection of possible matches is produced by the pattern syntax. In the second stage, the named subpattern references can be used to do additional tests like asserting that a node hasn't been created as part of a macro expansion.

## Implementing clippy lints using patterns

As a "real-world" example, I re-implemented the collapsible_if lint using patterns. The code can be found here. The pattern-based version passes all test cases that were written for collapsible_if.

# Reference-level explanation

## Overview

The following diagram shows the dependencies between the main parts of the proposed solution:

                          Pattern syntax
|
|  parsing / lowering
v
PatternTree
^
|
|
IsMatch trait
|
|
+---------------+-----------+---------+
|               |           |         |
v               v           v         v
syntax::ast     rustc::hir      syn       ...


The pattern syntax described in the previous section is parsed / lowered into the so-called PatternTree data structure that represents a valid syntax tree pattern. Matching a PatternTree against an actual syntax tree (e.g. rust ast / hir or the syn ast, ...) is done using the IsMatch trait.

The PatternTree and the IsMatch trait are introduced in more detail in the following sections.

## PatternTree

The core data structure of this RFC is the PatternTree.

It's a data structure similar to rust's AST / HIR, but with the following differences:

• The PatternTree doesn't contain parsing information like Spans
• The PatternTree can represent alternatives, sequences and optionals

The code below shows a simplified version of the current PatternTree:

Note: The current implementation can be found here.

#![allow(unused)]
fn main() {
pub enum Expr {
Lit(Alt<Lit>),
Array(Seq<Expr>),
Block_(Alt<BlockType>),
If(Alt<Expr>, Alt<BlockType>, Opt<Expr>),
IfLet(
Alt<BlockType>,
Opt<Expr>,
),
}

pub enum Lit {
Char(Alt<char>),
Bool(Alt<bool>),
Int(Alt<u128>),
}

pub enum Stmt {
Expr(Alt<Expr>),
Semi(Alt<Expr>),
}

pub enum BlockType {
Block(Seq<Stmt>),
}
}


The Alt, Seq and Opt structs look like these:

Note: The current implementation can be found here.

#![allow(unused)]
fn main() {
pub enum Alt<T> {
Any,
Elmt(Box<T>),
Alt(Box<Self>, Box<Self>),
Named(Box<Self>, ...)
}

pub enum Opt<T> {
Any,  // anything, but not None
Elmt(Box<T>),
None,
Alt(Box<Self>, Box<Self>),
Named(Box<Self>, ...)
}

pub enum Seq<T> {
Any,
Empty,
Elmt(Box<T>),
Repeat(Box<Self>, RepeatRange),
Seq(Box<Self>, Box<Self>),
Alt(Box<Self>, Box<Self>),
Named(Box<Self>, ...)
}

pub struct RepeatRange {
pub start: usize,
pub end: Option<usize>  // exclusive
}
}


## Parsing / Lowering

The input of a pattern! macro call is parsed into a ParseTree first and then lowered to a PatternTree.

Valid patterns depend on the PatternTree definitions. For example, the pattern Lit(Bool(_)*) isn't valid because the parameter type of the Lit variant of the Expr enum is Any<Lit> and therefore doesn't support repetition (*). As another example, Array( Lit(_)* ) is a valid pattern because the parameter of Array is of type Seq<Expr> which allows sequences and repetitions.

Note: names in the pattern syntax correspond to PatternTree enum variants. For example, the Lit in the pattern above refers to the Lit variant of the Expr enum (Expr::Lit), not the Lit enum.

## The IsMatch Trait

The pattern syntax and the PatternTree are independant of specific syntax tree implementations (rust ast / hir, syn, ...). When looking at the different pattern examples in the previous sections, it can be seen that the patterns don't contain any information specific to a certain syntax tree implementation. In contrast, clippy lints currently match against ast / hir syntax tree nodes and therefore directly depend on their implementation.

The connection between the PatternTree and specific syntax tree implementations is the IsMatch trait. It defines how to match PatternTree nodes against specific syntax tree nodes. A simplified implementation of the IsMatch trait is shown below:

#![allow(unused)]
fn main() {
pub trait IsMatch<O> {
fn is_match(&self, other: &'o O) -> bool;
}
}


This trait needs to be implemented on each enum of the PatternTree (for the corresponding syntax tree types). For example, the IsMatch implementation for matching ast::LitKind against the PatternTree's Lit enum might look like this:

#![allow(unused)]
fn main() {
impl IsMatch<ast::LitKind> for Lit {
fn is_match(&self, other: &ast::LitKind) -> bool {
match (self, other) {
(Lit::Char(i), ast::LitKind::Char(j)) => i.is_match(j),
(Lit::Bool(i), ast::LitKind::Bool(j)) => i.is_match(j),
(Lit::Int(i), ast::LitKind::Int(j, _)) => i.is_match(j),
_ => false,
}
}
}
}


All IsMatch implementations for matching the current PatternTree against syntax::ast can be found here.

# Drawbacks

#### Performance

The pattern matching code is currently not optimized for performance, so it might be slower than hand-written matching code. Additionally, the two-stage approach (matching against the coarse pattern first and checking for additional properties later) might be slower than the current practice of checking for structure and additional properties in one pass. For example, the following lint

#![allow(unused)]
fn main() {
pattern!{
pat_if_without_else: Expr =
If(
_,
Block(
Expr( If(_, _, ())#inner )
| Semi( If(_, _, ())#inner )
)#then,
()
)
}
...
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
if let Some(result) = pat_if_without_else(expr) {
if !block_starts_with_comment(cx, result.then) {
...
}
}
}


first matches against the pattern and then checks that the then block doesn't start with a comment. Using clippy's current approach, it's possible to check for these conditions earlier:

#![allow(unused)]
fn main() {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
if_chain! {
if let ast::ExprKind::If(ref check, ref then, None) = expr.node;
if !block_starts_with_comment(cx, then);
if let Some(inner) = expr_block(then);
if let ast::ExprKind::If(ref check_inner, ref content, None) = inner.node;
then {
...
}
}
}
}


Whether or not this causes performance regressions depends on actual patterns. If it turns out to be a problem, the pattern matching algorithms could be extended to allow "early filtering" (see the Early Filtering section in Future Possibilities).

That being said, I don't see any conceptual limitations regarding pattern matching performance.

#### Applicability

Even though I'd expect that a lot of lints can be written using the proposed pattern syntax, it's unlikely that all lints can be expressed using patterns. I suspect that there will still be lints that need to be implemented by writing custom pattern matching code. This would lead to mix within clippy's codebase where some lints are implemented using patterns and others aren't. This inconsistency might be considered a drawback.

# Rationale and alternatives

Specifying lints using syntax tree patterns has a couple of advantages compared to the current approach of manually writing matching code. First, syntax tree patterns allow users to describe patterns in a simple and expressive way. This makes it easier to write new lints for both novices and experts and also makes reading / modifying existing lints simpler.

Another advantage is that lints are independent of specific syntax tree implementations (e.g. AST / HIR, ...). When these syntax tree implementations change, only the IsMatch trait implementations need to be adapted and existing lints can remain unchanged. This also means that if the IsMatch trait implementations were integrated into the compiler, updating the IsMatch implementations would be required for the compiler to compile successfully. This could reduce the number of times clippy breaks because of changes in the compiler. Another advantage of the pattern's independence is that converting an EarlyLintPass lint into a LatePassLint wouldn't require rewriting the whole pattern matching code. In fact, the pattern might work just fine without any adaptions.

## Alternatives

### Rust-like pattern syntax

The proposed pattern syntax requires users to know the structure of the PatternTree (which is very similar to the AST's / HIR's structure) and also the pattern syntax. An alternative would be to introduce a pattern syntax that is similar to actual Rust syntax (probably like the quote! macro). For example, a pattern that matches if expressions that have false in their condition could look like this:

#![allow(unused)]
fn main() {
if false {
#[*]
}
}


#### Problems

Extending Rust syntax (which is quite complex by itself) with additional syntax needed for specifying patterns (alternations, sequences, repetisions, named submatches, ...) might become difficult to read and really hard to parse properly.

For example, a pattern that matches a binary operation that has 0 on both sides might look like this:

0 #[*:BinOpKind] 0


Now consider this slightly more complex example:

1 + 0 #[*:BinOpKind] 0


The parser would need to know the precedence of #[*:BinOpKind] because it affects the structure of the resulting AST. 1 + 0 + 0 is parsed as (1 + 0) + 0 while 1 + 0 * 0 is parsed as 1 + (0 * 0). Since the pattern could be any BinOpKind, the precedence cannot be known in advance.

Another example of a problem would be named submatches. Take a look at this pattern:

#![allow(unused)]
fn main() {
fn test() {
1 #foo
}
}


Which node is #foo referring to? int, ast::Lit, ast::Expr, ast::Stmt? Naming subpatterns in a rust-like syntax is difficult because a lot of AST nodes don't have a syntactic element that can be used to put the name tag on. In these situations, the only sensible option would be to assign the name tag to the outermost node (ast::Stmt in the example above), because the information of all child nodes can be retrieved through the outermost node. The problem with this then would be that accessing inner nodes (like ast::Lit) would again require manual pattern matching.

In general, Rust syntax contains a lot of code structure implicitly. This structure is reconstructed during parsing (e.g. binary operations are reconstructed using operator precedence and left-to-right) and is one of the reasons why parsing is a complex task. The advantage of this approach is that writing code is simpler for users.

When writing syntax tree patterns, each element of the hierarchy might have alternatives, repetitions, etc.. Respecting that while still allowing human-friendly syntax that contains structure implicitly seems to be really complex, if not impossible.

Developing such a syntax would also require to maintain a custom parser that is at least as complex as the Rust parser itself. Additionally, future changes in the Rust syntax might be incompatible with such a syntax.

In summary, I think that developing such a syntax would introduce a lot of complexity to solve a relatively minor problem.

The issue of users not knowing about the PatternTree structure could be solved by a tool that, given a rust program, generates a pattern that matches only this program (similar to the clippy author lint).

For some simple cases (like the first example above), it might be possible to successfully mix Rust and pattern syntax. This space could be further explored in a future extension.

# Prior art

The pattern syntax is heavily inspired by regular expressions (repetitions, alternatives, sequences, ...).

From what I've seen until now, other linters also implement lints that directly work on syntax tree data structures, just like clippy does currently. I would therefore consider the pattern syntax to be new, but please correct me if I'm wrong.

# Unresolved questions

#### How to handle multiple matches?

When matching a syntax tree node against a pattern, there are possibly multiple ways in which the pattern can be matched. A simple example of this would be the following pattern:

#![allow(unused)]
fn main() {
pattern!{
my_pattern: Expr =
Array( _* Lit(_)+#literals)
}
}


This pattern matches arrays that end with at least one literal. Now given the array [x, 1, 2], should 1 be matched as part of the _* or the Lit(_)+ part of the pattern? The difference is important because the named submatch #literals would contain 1 or 2 elements depending how the pattern is matched. In regular expressions, this problem is solved by matching "greedy" by default and "non-greedy" optionally.

I haven't looked much into this yet because I don't know how relevant it is for most lints. The current implementation simply returns the first match it finds.

# Future possibilities

#### Implement rest of Rust Syntax

The current project only implements a small part of the Rust syntax. In the future, this should incrementally be extended to more syntax to allow implementing more lints. Implementing more of the Rust syntax requires extending the PatternTree and IsMatch implementations, but should be relatively straight-forward.

#### Early filtering

As described in the Drawbacks/Performance section, allowing additional checks during the pattern matching might be beneficial.

The pattern below shows how this could look like:

#![allow(unused)]
fn main() {
pattern!{
pat_if_without_else: Expr =
If(
_,
Block(
Expr( If(_, _, ())#inner )
| Semi( If(_, _, ())#inner )
)#then,
()
)
where
!in_macro(#then.span);
}
}


The difference compared to the currently proposed two-stage filtering is that using early filtering, the condition (!in_macro(#then.span) in this case) would be evaluated as soon as the Block(_)#then was matched.

Another idea in this area would be to introduce a syntax for backreferences. They could be used to require that multiple parts of a pattern should match the same value. For example, the assign_op_pattern lint that searches for a = a op b and recommends changing it to a op= b requires that both occurrances of a are the same. Using =#... as syntax for backreferences, the lint could be implemented like this:

#![allow(unused)]
fn main() {
pattern!{
assign_op_pattern: Expr =
Assign(_#target, Binary(_, =#target, _)
}
}


#### Match descendant

A lot of lints currently implement custom visitors that check whether any subtree (which might not be a direct descendant) of the current node matches some properties. This cannot be expressed with the proposed pattern syntax. Extending the pattern syntax to allow patterns like "a function that contains at least two return statements" could be a practical addition.

#### Negation operator for alternatives

For patterns like "a literal that is not a boolean literal" one currently needs to list all alternatives except the boolean case. Introducing a negation operator that allows to write Lit(!Bool(_)) might be a good idea. This pattern would be eqivalent to Lit( Char(_) | Int(_) ) (given that currently only three literal types are implemented).

#### Functional composition

Patterns currently don't have any concept of composition. This leads to repetitions within patterns. For example, one of the collapsible-if patterns currently has to be written like this:

#![allow(unused)]
fn main() {
pattern!{
pat_if_else: Expr =
If(
_,
_,
Block_(
Block(
Expr((If(_, _, _?) | IfLet(_, _?))#else_) |
Semi((If(_, _, _?) | IfLet(_, _?))#else_)
)#block_inner
)#block
) |
IfLet(
_,
Block_(
Block(
Expr((If(_, _, _?) | IfLet(_, _?))#else_) |
Semi((If(_, _, _?) | IfLet(_, _?))#else_)
)#block_inner
)#block
)
}
}


If patterns supported defining functions of subpatterns, the code could be simplified as follows:

#![allow(unused)]
fn main() {
pattern!{
fn expr_or_semi(expr: Expr) -> Stmt {
Expr(expr) | Semi(expr)
}
fn if_or_if_let(then: Block, else: Opt<Expr>) -> Expr {
If(_, then, else) | IfLet(then, else)
}
pat_if_else: Expr =
if_or_if_let(
_,
Block_(
Block(
expr_or_semi( if_or_if_let(_, _?)#else_ )
)#block_inner
)#block
)
}
}


Additionally, common patterns like expr_or_semi could be shared between different lints.

#### Clippy Pattern Author

Another improvement could be to create a tool that, given some valid Rust syntax, generates a pattern that matches this syntax exactly. This would make starting to write a pattern easier. A user could take a look at the patterns generated for a couple of Rust code examples and use that information to write a pattern that matches all of them.

This is similar to clippy's author lint.

#### Supporting other syntaxes

Most of the proposed system is language-agnostic. For example, the pattern syntax could also be used to describe patterns for other programming languages.

In order to support other languages' syntaxes, one would need to implement another PatternTree that sufficiently describes the languages' AST and implement IsMatch for this PatternTree and the languages' AST.

One aspect of this is that it would even be possible to write lints that work on the pattern syntax itself. For example, when writing the following pattern

#![allow(unused)]
fn main() {
pattern!{
my_pattern: Expr =
Array( Lit(Bool(false)) Lit(Bool(false)) )
}
}


a lint that works on the pattern syntax's AST could suggest using this pattern instead:

#![allow(unused)]
fn main() {
pattern!{
my_pattern: Expr =
Array( Lit(Bool(false)){2} )
}
}
`

In the future, clippy could use this system to also provide lints for custom syntaxes like those found in macros.