Clippy
A collection of lints to catch common mistakes and improve your Rust code.
There are over 800 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.
| Category | Description | Default level |
|---|---|---|
clippy::all | all lints that are on by default (correctness, suspicious, style, complexity, perf) | warn/deny |
clippy::correctness | code that is outright wrong or useless | deny |
clippy::suspicious | code that is most likely wrong or useless | warn |
clippy::style | code that should be written in a more idiomatic way | warn |
clippy::complexity | code that does something simple but in a complex way | warn |
clippy::perf | code that can be written to run faster | warn |
clippy::pedantic | lints which are rather strict or have occasional false positives | allow |
clippy::restriction | lints which prevent the use of language and library features1 | allow |
clippy::nursery | new lints that are still under development | allow |
clippy::cargo | lints for the cargo manifest | allow |
More to come, please file an issue if you have ideas!
The restriction category should, emphatically, not be enabled as a whole. The contained
lints may lint against perfectly reasonable code, may not have an alternative suggestion,
and may contradict any other lints (including other categories). Lints should be considered
on a case-by-case basis before enabling.
-
Some use cases for
restrictionlints include:- Strict coding styles (e.g.
clippy::else_if_without_else). - Additional restrictions on CI (e.g.
clippy::todo). - Preventing panicking in certain functions (e.g.
clippy::unwrap_used). - Running a lint only on a subset of code (e.g.
#[forbid(clippy::float_arithmetic)]on a module).
- Strict coding styles (e.g.
Installation
If youâre using rustup to install and manage your 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
minimalprofile 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 may 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::box_default -Dclippy::perf
For CI all warnings can be elevated to errors which will in turn fail
the build and cause Clippy to exit with a code other than 0.
cargo clippy -- -Dwarnings
Note: Adding
-D warningswill 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::box_default)]
fn main() {
let _ = Box::<String>::new(Default::default());
// ^ warning: `Box::new(_)` of default value
}
Automatically applying Clippy suggestions
Clippy can automatically apply some lint suggestions, just like the compiler. Note that --fix implies
--all-targets, so it can fix as much code as it can.
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-driveris designed for running Clippy and should not be used as a general replacement forrustc.clippy-drivermay 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, which is searched for starting in the
first defined directory according to the following priority order:
- The directory specified by the
CLIPPY_CONF_DIRenvironment variable, or - The directory specified by the CARGO_MANIFEST_DIR environment variable, or
- The current directory.
If the chosen directory does not contain a configuration file, Clippy will walk up the directory tree, searching each parent directory until it finds one or reaches the filesystem root.
It contains a basic variable = value mapping e.g.
avoid-breaking-exported-api = false
disallowed-names = ["toto", "tata", "titi"]
The table of configurations contains all config values, their default, and a list of lints they affect. Each configurable lint , also contains information about these values.
For configurations that are a list type with default values such as
disallowed-names,
you can use the unique value ".." to extend the default values instead of replacing them.
# default of disallowed-names is ["foo", "baz", "quux"]
disallowed-names = ["bar", ".."] # -> ["bar", "foo", "baz", "quux"]
To deactivate the âfor further information visit lint-linkâ message you can define the CLIPPY_DISABLE_DOCS_LINKS
environment variable.
Allowing/Denying Lints
Attributes in Code
You can add attributes to your code to allow/warn/deny Clippy lints:
-
the whole set of
warn-by-default lints using theclippylint group (#![allow(clippy::all)]) -
all lints using both the
clippyandclippy::pedanticlint groups (#![warn(clippy::all, clippy::pedantic)]. Note thatclippy::pedanticcontains some very aggressive lints prone to false positives. -
only some lints (
#![deny(clippy::single_match, clippy::box_vec)], etc.) -
allow/warn/denycan 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
most useful in scripts used in CI/CD.
Command Line Flags
If you do not want to include your lint levels in the 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 pedantic lints enabled:
cargo clippy -- -W clippy::pedantic
If you care only about certain lints, you can allow all others and then explicitly warn on the lints you are interested in:
cargo clippy -- -A clippy::all -W clippy::useless_format -W clippy::...
Lints Section in Cargo.toml
Finally, lints can be allowed/denied using the lints
section in the Cargo.toml file:
To deny clippy::enum_glob_use, put the following in the Cargo.toml:
[lints.clippy]
enum_glob_use = "deny"
For more details and options, refer to the Cargo documentation.
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: Some lints change their behavior depending on the configured MSRV. In some cases, Clippy may suppress a lint entirely to avoid suggesting APIs or syntax unavailable for the configured MSRV. In other cases, Clippy may emit the lint but choose an older compatible suggestion.
Note:
custom_inner_attributesis an unstable feature, so it has to be enabled explicitly.
Lints that recognize this configuration option can be found here
Disabling evaluation of certain code
Note: This should only be used in cases where other solutions, like
#[allow(clippy::all)], are not sufficient.
Very rarely, you may wish to prevent Clippy from evaluating certain sections of code entirely. You can do this with
conditional compilation by checking that the
clippy cfg is not set. You may need to provide a stub so that the code compiles:
#![allow(unused)]
fn main() {
#[cfg(not(clippy))]
include!(concat!(env!("OUT_DIR"), "/my_big_function-generated.rs"));
#[cfg(clippy)]
fn my_big_function(_input: &str) -> Option<MyStruct> {
None
}
}
Lint Configuration Options
The following list shows each configuration option, along with a description, its default value, an example and lints affected.
absolute-paths-allowed-crates
Which crates to allow absolute paths from
Default Value: []
Affected lints:
absolute-paths-max-segments
The maximum number of segments a path can have before being linted, anything above this will be linted.
Default Value: 2
Affected lints:
accept-comment-above-attributes
Whether to accept a safety comment to be placed above the attributes for the unsafe block
Default Value: true
Affected lints:
accept-comment-above-statement
Whether to accept a safety comment to be placed above the statement containing the unsafe block
Default Value: true
Affected lints:
allow-comparison-to-zero
Donât lint when comparing the result of a modulo operation to zero.
Default Value: true
Affected lints:
allow-dbg-in-tests
Whether dbg! should be allowed in test functions or #[cfg(test)]
Default Value: false
Affected lints:
allow-exact-repetitions
Whether an item should be allowed to have the same name as its containing module
Default Value: true
Affected lints:
allow-expect-in-consts
Whether expect should be allowed in code always evaluated at compile time
Default Value: true
Affected lints:
allow-expect-in-tests
Whether expect should be allowed in test functions or #[cfg(test)]
Default Value: false
Affected lints:
allow-indexing-slicing-in-tests
Whether indexing_slicing should be allowed in test functions or #[cfg(test)]
Default Value: false
Affected lints:
allow-large-stack-frames-in-tests
Whether functions inside #[cfg(test)] modules or test functions should be checked.
Default Value: true
Affected lints:
allow-mixed-uninlined-format-args
Whether to allow mixed uninlined format args, e.g. format!("{} {}", a, foo.bar)
Default Value: true
Affected lints:
allow-one-hash-in-raw-strings
Whether to allow r#""# when r"" can be used
Default Value: false
Affected lints:
allow-panic-in-tests
Whether panic should be allowed in test functions or #[cfg(test)]
Default Value: false
Affected lints:
allow-print-in-tests
Whether print macros (ex. println!) should be allowed in test functions or #[cfg(test)]
Default Value: false
Affected lints:
allow-private-module-inception
Whether to allow module inception if itâs not public.
Default Value: false
Affected lints:
allow-renamed-params-for
List of trait paths to ignore when checking renamed function parameters.
Example
allow-renamed-params-for = [ "std::convert::From" ]
Noteworthy
- By default, the following traits are ignored:
From,TryFrom,FromStr ".."can be used as part of the list to indicate that the configured values should be appended to the default configuration of Clippy. By default, any configuration will replace the default value.
Default Value: ["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"]
Affected lints:
allow-unwrap-in-consts
Whether unwrap should be allowed in code always evaluated at compile time
Default Value: true
Affected lints:
allow-unwrap-in-tests
Whether unwrap should be allowed in test functions or #[cfg(test)]
Default Value: false
Affected lints:
allow-unwrap-types
List of types to allow unwrap() and expect() on.
Example
allow-unwrap-types = [ "std::sync::LockResult" ]
Default Value: []
Affected lints:
allow-useless-vec-in-tests
Whether useless_vec should ignore test functions or #[cfg(test)]
Default Value: false
Affected lints:
allowed-dotfiles
Additional dotfiles (files or directories starting with a dot) to allow
Default Value: []
Affected lints:
allowed-duplicate-crates
A list of crate names to allow duplicates of
Default Value: []
Affected lints:
allowed-idents-below-min-chars
Allowed names below the minimum allowed characters. The value ".." can be used as part of
the list to indicate that the configured values should be appended to the default
configuration of Clippy. By default, any configuration will replace the default value.
Default Value: ["i", "j", "x", "y", "z", "w", "n"]
Affected lints:
allowed-prefixes
List of prefixes to allow when determining whether an itemâs name ends with the moduleâs name.
If the rest of an itemâs name is an allowed prefix (e.g. item ToFoo or to_foo in module foo),
then donât emit a warning.
Example
allowed-prefixes = [ "to", "from" ]
Noteworthy
- By default, the following prefixes are allowed:
to,as,into,from,try_intoandtry_from - PascalCase variant is included automatically for each snake_case variant (e.g. if
try_intois included,TryIntowill also be included) - Use
".."as part of the list to indicate that the configured values should be appended to the default configuration of Clippy. By default, any configuration will replace the default value
Default Value: ["to", "as", "into", "from", "try_into", "try_from"]
Affected lints:
allowed-scripts
The list of unicode scripts allowed to be used in the scope.
Default Value: ["Latin"]
Affected lints:
allowed-wildcard-imports
List of path segments allowed to have wildcard imports.
Example
allowed-wildcard-imports = [ "utils", "common" ]
Noteworthy
- This configuration has no effects if used with
warn_on_all_wildcard_imports = true. - Paths with any segment that containing the word âpreludeâ are already allowed by default.
Default Value: []
Affected lints:
arithmetic-side-effects-allowed
Suppress checking of the passed type names in all types of operations.
If a specific operation is desired, consider using arithmetic_side_effects_allowed_binary or arithmetic_side_effects_allowed_unary instead.
Example
arithmetic-side-effects-allowed = ["SomeType", "AnotherType"]
Noteworthy
A type, say SomeType, listed in this configuration has the same behavior of
["SomeType" , "*"], ["*", "SomeType"] in arithmetic_side_effects_allowed_binary.
Default Value: []
Affected lints:
arithmetic-side-effects-allowed-binary
Suppress checking of the passed type pair names in binary operations like addition or multiplication.
Supports the â*â wildcard to indicate that a certain type wonât trigger the lint regardless
of the involved counterpart. For example, ["SomeType", "*"] or ["*", "AnotherType"].
Pairs are asymmetric, which means that ["SomeType", "AnotherType"] is not the same as
["AnotherType", "SomeType"].
Example
arithmetic-side-effects-allowed-binary = [["SomeType" , "f32"], ["AnotherType", "*"]]
Default Value: []
Affected lints:
arithmetic-side-effects-allowed-unary
Suppress checking of the passed type names in unary operations like ânegationâ (-).
Example
arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"]
Default Value: []
Affected lints:
array-size-threshold
The maximum allowed size for arrays on the stack
Default Value: 16384
Affected lints:
avoid-breaking-exported-api
Suppress lints whenever the suggested change would cause breakage for other crates.
Default Value: true
Affected lints:
box_collectionenum_variant_nameslarge_types_passed_by_valuelinkedlistneedless_pass_by_ref_mutoption_optionowned_cowrc_bufferrc_mutexredundant_allocationref_optionsingle_call_fntrivially_copy_pass_by_refunnecessary_box_returnsunnecessary_wrapsunused_selfupper_case_acronymsvec_boxwrong_self_convention
await-holding-invalid-types
The list of types which may not be held across an await point.
Default Value: []
Affected lints:
cargo-ignore-publish
For internal testing only, ignores the current publish settings in the Cargo manifest.
Default Value: false
Affected lints:
check-grouped-late-init
Whether to check for grouped late initializations from multiple let statements.
Example
#![allow(unused)]
fn main() {
let a;
let b;
if true {
a = 1;
b = 2;
} else {
a = 3;
b = 4;
}
}
Use instead:
#![allow(unused)]
fn main() {
let (a, b) = if true {
(1, 2)
} else {
(3, 4)
};
}
Default Value: true
Affected lints:
check-incompatible-msrv-in-tests
Whether to check MSRV compatibility in #[test] and #[cfg(test)] code.
Default Value: false
Affected lints:
check-inconsistent-struct-field-initializers
Whether to suggest reordering constructor fields when initializers are present.
Warnings produced by this configuration arenât necessarily fixed by just reordering the fields. Even if the suggested code would compile, it can change semantics if the initializer expressions have side effects. The following example from rust-clippy#11846 shows how the suggestion can run into borrow check errors:
struct MyStruct {
vector: Vec<u32>,
length: usize
}
fn main() {
let vector = vec![1,2,3];
MyStruct { length: vector.len(), vector};
}
Default Value: false
Affected lints:
check-private-items
Whether to also run the listed lints on private items.
Default Value: false
Affected lints:
cognitive-complexity-threshold
The maximum cognitive complexity a function can have
Default Value: 25
Affected lints:
const-literal-digits-threshold
The minimum digits a const float literal must have to supress the excessive_precicion lint
Default Value: 30
Affected lints:
disallowed-fields
The list of disallowed fields, written as fully qualified paths.
Fields:
path(required): the fully qualified path to the field that should be disallowedreason(optional): explanation why this field is disallowedreplacement(optional): suggested alternative methodallow-invalid(optional,falseby default): when set totrue, it will ignore this entry if the path doesnât exist, instead of emitting an error
Default Value: []
Affected lints:
disallowed-macros
The list of disallowed macros, written as fully qualified paths.
Fields:
path(required): the fully qualified path to the macro that should be disallowedreason(optional): explanation why this macro is disallowedreplacement(optional): suggested alternative macroallow-invalid(optional,falseby default): when set totrue, it will ignore this entry if the path doesnât exist, instead of emitting an error
Default Value: []
Affected lints:
disallowed-methods
The list of disallowed methods, written as fully qualified paths.
Fields:
path(required): the fully qualified path to the method that should be disallowedreason(optional): explanation why this method is disallowedreplacement(optional): suggested alternative methodallow-invalid(optional,falseby default): when set totrue, it will ignore this entry if the path doesnât exist, instead of emitting an error
Default Value: []
Affected lints:
disallowed-names
The list of disallowed names to lint about. NB: bar is not here since it has legitimate uses. The value
".." can be used as part of the list to indicate that the configured values should be appended to the
default configuration of Clippy. By default, any configuration will replace the default value.
Default Value: ["foo", "baz", "quux"]
Affected lints:
disallowed-types
The list of disallowed types, written as fully qualified paths.
Fields:
path(required): the fully qualified path to the type that should be disallowedreason(optional): explanation why this type is disallowedreplacement(optional): suggested alternative typeallow-invalid(optional,falseby default): when set totrue, it will ignore this entry if the path doesnât exist, instead of emitting an error
Default Value: []
Affected lints:
doc-valid-idents
The list of words this lint should not consider as identifiers needing ticks. The value
".." can be used as part of the list to indicate that the configured values should be appended to the
default configuration of Clippy. By default, any configuration will replace the default value. For example:
doc-valid-idents = ["ClipPy"]would replace the default list with["ClipPy"].doc-valid-idents = ["ClipPy", ".."]would appendClipPyto the default list.
Default Value: ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "InfiniBand", "RoCE", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "PowerPC", "PowerShell", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "SQLite", "MySQL", "PostgreSQL", "MariaDB", "MongoDB", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]
Affected lints:
enable-raw-pointer-heuristic-for-send
Whether to apply the raw pointer heuristic to determine if a type is Send.
Default Value: true
Affected lints:
enforce-iter-loop-reborrow
Whether to recommend using implicit into iter for reborrowed values.
Example
let mut vec = vec![1, 2, 3];
let rmvec = &mut vec;
for _ in rmvec.iter() {}
for _ in rmvec.iter_mut() {}
Use instead:
let mut vec = vec![1, 2, 3];
let rmvec = &mut vec;
for _ in &*rmvec {}
for _ in &mut *rmvec {}
Default Value: false
Affected lints:
enforced-import-renames
The list of imports to always rename, a fully qualified path followed by the rename.
Default Value: []
Affected lints:
enum-variant-name-threshold
The minimum number of enum variants for the lints about variant names to trigger
Default Value: 3
Affected lints:
enum-variant-size-threshold
The maximum size of an enumâs variant to avoid box suggestion
Default Value: 200
Affected lints:
excessive-nesting-threshold
The maximum amount of nesting a block can reside in
Default Value: 0
Affected lints:
future-size-threshold
The maximum byte size a Future can have, before it triggers the clippy::large_futures lint
Default Value: 16384
Affected lints:
ignore-interior-mutability
A list of paths to types that should be treated as if they do not contain interior mutability
Default Value: ["bytes::Bytes"]
Affected lints:
inherent-impl-lint-scope
Sets the scope (âcrateâ, âfileâ, or âmoduleâ) in which duplicate inherent impl blocks for the same type are linted.
Default Value: "crate"
Affected lints:
large-error-ignored
A list of paths to types that should be ignored as overly large Err-variants in a
Result returned from a function
Default Value: []
Affected lints:
large-error-threshold
The maximum size of the Err-variant in a Result returned from a function
Default Value: 128
Affected lints:
lint-commented-code
Whether collapsible if and else if chains are linted if they contain comments inside the parts
that would be collapsed.
Default Value: false
Affected lints:
literal-representation-threshold
The lower bound for linting decimal literals
Default Value: 16384
Affected lints:
matches-for-let-else
Whether the matches should be considered by the lint, and whether there should be filtering for common types.
Default Value: "WellKnownTypes"
Affected lints:
max-fn-params-bools
The maximum number of bool parameters a function can have.
Use 0 to lint on any function with a bool parameter.
Default Value: 3
Affected lints:
max-include-file-size
The maximum size of a file included via include_bytes!() or include_str!(), in bytes
Default Value: 1000000
Affected lints:
max-struct-bools
The maximum number of bool fields a struct can have
Default Value: 3
Affected lints:
max-suggested-slice-pattern-length
When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in
the slice pattern that is suggested. If more elements are necessary, the lint is suppressed.
For example, [_, _, _, e, ..] is a slice pattern with 4 elements.
Default Value: 3
Affected lints:
max-trait-bounds
The maximum number of bounds a trait can have to be linted
Default Value: 3
Affected lints:
min-ident-chars-threshold
Minimum chars an ident can have, anything below or equal to this will be linted.
Default Value: 1
Affected lints:
missing-docs-allow-unused
Whether to allow fields starting with an underscore to skip documentation requirements
Default Value: false
Affected lints:
missing-docs-in-crate-items
Whether to only check for missing documentation in items visible within the current
crate. For example, pub(crate) items.
Default Value: false
Affected lints:
module-item-order-groupings
The named groupings of different source item kinds within modules.
Default Value: [["modules", ["extern_crate", "mod", "foreign_mod"]], ["use", ["use"]], ["macros", ["macro"]], ["global_asm", ["global_asm"]], ["UPPER_SNAKE_CASE", ["static", "const"]], ["PascalCase", ["ty_alias", "enum", "struct", "union", "trait", "trait_alias", "impl"]], ["lower_snake_case", ["fn"]]]
Affected lints:
module-items-ordered-within-groupings
Whether the items within module groups should be ordered alphabetically or not.
This option can be configured to âallâ, ânoneâ, or a list of specific grouping names that should be checked (e.g. only âenumsâ).
Default Value: "none"
Affected lints:
msrv
The minimum rust version that the project supports. Defaults to the rust-version field in Cargo.toml
Default Value: current version
Affected lints:
allow_attributesallow_attributes_without_reasonalmost_complete_rangeapprox_constantassigning_clonesborrow_as_ptrcast_abs_to_unsignedchecked_conversionscloned_instead_of_copiedcollapsible_matchcollapsible_str_replacedeprecated_cfg_attrderivable_implserr_expectfilter_map_nextfrom_over_intoif_then_some_else_noneindex_refutable_sliceinefficient_to_stringio_other_erroriter_kv_maplegacy_numeric_constantslen_zerolines_filter_map_okmanual_abs_diffmanual_bitsmanual_c_str_literalsmanual_clampmanual_div_ceilmanual_flattenmanual_hash_onemanual_is_ascii_checkmanual_is_power_of_twomanual_isolate_lowest_onemanual_let_elsemanual_midpointmanual_non_exhaustivemanual_noop_wakermanual_option_as_slicemanual_pattern_char_comparisonmanual_range_containsmanual_rem_euclidmanual_repeat_nmanual_retainmanual_slice_fillmanual_slice_size_calculationmanual_split_oncemanual_str_repeatmanual_stripmanual_takemanual_try_foldmap_clonemap_unwrap_ormap_with_unused_argument_over_rangesmatch_like_matches_macromem_replace_option_with_somemem_replace_with_defaultmissing_const_for_fnneedless_borrownon_std_lazy_staticsoption_as_ref_derefor_fun_callptr_as_ptrquestion_markredundant_field_namesredundant_static_lifetimesrepeat_vec_with_capacitysame_item_pushseek_from_currentto_digit_is_sometransmute_ptr_to_reftuple_array_conversionstype_repetition_in_boundsunchecked_time_subtractionuninlined_format_argsunnecessary_lazy_evaluationsunnecessary_unwrapunnested_or_patternsunused_trait_namesuse_selfzero_ptr
pass-by-value-size-limit
The minimum size (in bytes) to consider a type for passing by reference instead of by value.
Default Value: 256
Affected lints:
pub-underscore-fields-behavior
Lint âpublicâ fields in a struct that are prefixed with an underscore based on their exported visibility, or whether they are marked as âpubâ.
Default Value: "PubliclyExported"
Affected lints:
recursive-self-in-type-definitions
Whether the type itself in a struct or enum should be replaced with Self when encountering recursive types.
Default Value: true
Affected lints:
semicolon-inside-block-ignore-singleline
Whether to lint only if itâs multiline.
Default Value: false
Affected lints:
semicolon-outside-block-ignore-multiline
Whether to lint only if itâs singleline.
Default Value: false
Affected lints:
single-char-binding-names-threshold
The maximum number of single char bindings a scope may have
Default Value: 4
Affected lints:
source-item-ordering
Which kind of elements should be ordered internally, possible values being enum, impl, module, struct, trait.
Default Value: ["enum", "impl", "module", "struct", "trait"]
Affected lints:
stack-size-threshold
The maximum allowed stack size for functions in bytes
Default Value: 512000
Affected lints:
standard-macro-braces
Enforce the named macros always use the braces specified.
A MacroMatcher can be added like so { name = "macro_name", brace = "(" }. If the macro
could be used with a full path two MacroMatchers have to be added one with the full path
crate_name::macro_name and one with just the macro name.
Default Value: []
Affected lints:
struct-field-name-threshold
The minimum number of struct fields for the lints about field names to trigger
Default Value: 3
Affected lints:
suppress-restriction-lint-in-const
Whether to suppress a restriction lint in constant code. In same cases the restructured operation might not be unavoidable, as the suggested counterparts are unavailable in constant code. This configuration will cause restriction lints to trigger even if no suggestion can be made.
Default Value: false
Affected lints:
too-large-for-stack
The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
Default Value: 200
Affected lints:
too-many-arguments-threshold
The maximum number of argument a function or method can have
Default Value: 7
Affected lints:
too-many-lines-threshold
The maximum number of lines a function or method can have
Default Value: 100
Affected lints:
trait-assoc-item-kinds-order
The order of associated items in traits.
Default Value: ["const", "type", "fn"]
Affected lints:
trivial-copy-size-limit
The maximum size (in bytes) to consider a Copy type for passing by value instead of by
reference.
Default Value: target_pointer_width
Affected lints:
type-complexity-threshold
The maximum complexity a type can have
Default Value: 250
Affected lints:
unnecessary-box-size
The byte size a T in Box<T> can have, below which it triggers the clippy::unnecessary_box lint
Default Value: 128
Affected lints:
unreadable-literal-lint-fractions
Should the fraction of a decimal be linted to include separators.
Default Value: true
Affected lints:
upper-case-acronyms-aggressive
Enables verbose mode. Triggers if there is more than one uppercase char next to each other
Default Value: false
Affected lints:
vec-box-size-threshold
The size of the boxed type in bytes, where boxing in a Vec is allowed
Default Value: 4096
Affected lints:
verbose-bit-mask-threshold
The maximum allowed size of a bit mask before suggesting to use âtrailing_zerosâ
Default Value: 1
Affected lints:
warn-on-all-wildcard-imports
Whether to emit warnings on all wildcard imports, including those from prelude, from super in tests,
or for pub use reexports.
Default Value: false
Affected lints:
warn-unsafe-macro-metavars-in-private-macros
Whether to also emit warnings for unsafe blocks with metavariable expansions in private macros.
Default Value: false
Affected lints:
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 provides details about 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. For an overview of these categories, see the introduction.
The different lint groups were defined in the Clippy 1.0 RFC.
Correctness
The clippy::correctness group is the only lint group in Clippy whose 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 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.
Nursery
The clippy::nursery group contains lints which are buggy or need more work. 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.
Deprecated
The clippy::deprecated is empty lints that exist to ensure that #[allow(lintname)] still
compiles after the lint was deprecated. Deprecation âremovesâ lints by removing their
functionality and marking them as deprecated, which may cause further warnings but cannot
cause a compiler error.
Attributes for Crate Authors
In some cases it is possible to extend Clippy coverage to 3rd party libraries. To do this, Clippy provides attributes that can be applied to items in the 3rd party crate.
#[clippy::format_args]
Available since Clippy v1.85
This attribute can be added to a macro that supports format!, println!, or similar syntax.
It tells Clippy that the macro is a formatting macro, and that the arguments to the macro
should be linted as if they were arguments to format!. Any lint that would apply to a
format! call will also apply to the macro call. The macro may have additional arguments
before the format string, and these will be ignored.
Example
#![allow(unused)]
fn main() {
/// A macro that prints a message if a condition is true.
#[macro_export]
#[clippy::format_args]
macro_rules! print_if {
($condition:expr, $($args:tt)+) => {{
if $condition {
println!($($args)+)
}
}};
}
}
#[clippy::has_significant_drop]
Available since Clippy v1.60
The clippy::has_significant_drop attribute can be added to types whose Drop impls have an important side effect,
such as unlocking a mutex, making it important for users to be able to accurately understand their lifetimes.
When a temporary is returned in a function call in a match scrutinee, its lifetime lasts until the end of the match
block, which may be surprising.
Example
#![allow(unused)]
fn main() {
#[clippy::has_significant_drop]
struct CounterWrapper<'a> {
counter: &'a Counter,
}
impl<'a> Drop for CounterWrapper<'a> {
fn drop(&mut self) {
self.counter.i.fetch_sub(1, Ordering::Relaxed);
}
}
}
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
nightlytoolchain. If you want to help with improving Clippy and have CI resources left, please consider adding anightlyClippy 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@v7
- name: Run Clippy
run: cargo clippy --all-targets --all-features
GitLab CI
You can add Clippy to GitLab CI by using the latest stable rust docker image,
as it is shown in the .gitlab-ci.yml CI configuration file below,
# Make sure CI fails on all warnings, including Clippy lints
variables:
RUSTFLAGS: "-Dwarnings"
clippy_check:
image: rust:latest
script:
- rustup component add clippy
- 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.
Additional Readings for Beginners
If a dear reader of this documentation has never taken a class on compilers and interpreters, it might be confusing as to why AST level deals with only the languageâs syntax. And some readers might not even understand what lexing, parsing, and AST mean.
This documentation serves by no means as a crash course on compilers or language design. And for details specifically related to Rust, the Rustc Development Guide is a far better choice to peruse.
The Syntax and AST chapter and the High-Level IR chapter are great introduction to the concepts mentioned in this chapter.
Some readers might also find the introductory chapter of Robert Nystromâs Crafting Interpreters a helpful overview of compiled and interpreted languages before jumping back to the Rustc guide.
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.mddocument. 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
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 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 RustRover
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
| Abbreviation | Meaning |
|---|---|
| UB | Undefined Behavior |
| FP | False Positive |
| FN | False Negative |
| ICE | Internal Compiler Error |
| AST | Abstract Syntax Tree |
| MIR | Mid-Level Intermediate Representation |
| HIR | High-Level Intermediate Representation |
| TCX | Type context |
This is a concise list of abbreviations that can come up during Clippy development. An extensive general list can be found in the rustc-dev-guide glossary. 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:
From the Clippy project root, run the following command to build the Clippy
binaries and copy them into the toolchain directory. This will create a new
toolchain called clippy by default, see cargo dev setup toolchain --help
for other options.
cargo dev setup toolchain
Now you may run cargo +clippy clippy in any project using the new toolchain.
cd my-project
cargo +clippy clippy
âŚor clippy-driver
clippy-driver +clippy <filename>
If you no longer need the toolchain it can be uninstalled using rustup:
rustup toolchain uninstall clippy
DO NOT install using
cargo install --path . --forcesince this will overwrite rustup proxies. That is,~/.cargo/bin/cargo-clippyand~/.cargo/bin/clippy-drivershould be hard or soft links to~/.cargo/bin/rustup. You can repair these by runningrustup 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.
- Adding a new lint
- Setup
- Getting Started
- Testing
- Rustfix tests
- Testing manually
- Lint declaration
- Lint registration
- Lint passes
- Emitting a lint
- Adding the lint logic
- Specifying the lintâs minimum supported Rust version (MSRV)
- Author lint
- Print HIR lint
- Documentation
- Running rustfmt
- Debugging
- Conflicting lints
- PR Checklist
- Adding configuration to a lint
- Cheat Sheet
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) {}
//~^ foo_functions
pub fn food(&self) {}
}
// Default trait methods
trait B {
fn fo(&self) {}
fn foo(&self) {}
//~^ foo_functions
fn food(&self) {}
}
// Plain functions
fn fo() {}
fn foo() {}
//~^ foo_functions
fn food() {}
fn main() {
// We also don't want to lint method calls
foo();
let a = A;
a.foo();
}
Note that we are adding comment annotations with the name of our lint to mark
lines where we expect an error. Except for very specific situations
(//@check-pass), at least one error marker must be present in a test file for
it to be accepted.
Once we have implemented our lint we can run TESTNAME=foo_functions cargo uibless to generate the .stderr file. If our lint makes use of structured
suggestions then this command will also generate the corresponding .fixed
file.
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 by checking the
.stderr file that gets updated on every test run.
Once we have implemented our lint running TESTNAME=foo_functions cargo uitest
should pass on its own. When we commit our lint, we need to commit the generated
.stderr and if applicable .fixed files, too. In general, you should only
commit files changed by cargo bless for the specific lint you are creating/editing.
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 --name=foo_categories --type=cargo --category=cargo 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
will create a .fixed file by running 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 bless to automatically generate the .fixed file while 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 the following from the Clippy directory:
cargo dev lint input.rs
To run Clippy on an existing project rather than a single file you can use
cargo dev lint /path/to/project
Or set up a rustup toolchain that points to the local Clippy binaries
cargo dev setup toolchain
# Then in `/path/to/project` you can run
cargo +clippy clippy
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;
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, runcargo 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 runningrustc -vVin the rust-clippy directory. The version is listed under release. (Use the version without the-nightly) suffix. FOO_FUNCTIONSis 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 withallow/warn/deny.pedanticsets the lint level toAllow. 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 by adding an entry to the
early_lint_methods! macro invocation in clippy_lints/src/lib.rs, at the
// add early passes here marker:
FooFunctions: foo_functions::FooFunctions = foo_functions::FooFunctions,
As one may expect, there is a corresponding late_lint_methods! macro available
as well. Without an entry in one of early_lint_methods! or late_lint_methods!,
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 listed 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.
EarlyLintPass runs before type checking and
HIR lowering, while LateLintPass
runs after these stages, providing access to type information. The cargo dev new_lint command
defaults to the recommended LateLintPass, but you can specify --pass=early if your lint
only needs AST level analysis.
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:
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:
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(_, _, 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 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 {
pub fn new(conf: &'static Conf) -> Self {
Self { msrv: conf.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(cx, msrvs::STR_STRIP_PREFIX) {
return;
}
}
Early lint passes should instead use MsrvStack coupled with
extract_msrv_attr!()
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.
...
#[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_config/src/conf.rs:
#![allow(unused)]
fn main() {
define_Conf! {
#[lints(
allow_attributes,
allow_attributes_without_reason,
..
<the newly added lint name>,
..
unused_trait_names,
use_self,
)]
msrv: Msrv = Msrv::default(),
...
}
}
Afterwards update the documentation for the book as described in Adding configuration to a lint.
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 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.
Print HIR lint
To implement a lint, itâs helpful to first understand the internal
representation that rustc uses. Clippy has the #[clippy::dump] attribute that
prints the High-Level Intermediate Representation (HIR) of the item,
statement, or expression that the attribute is attached to. To attach the
attribute to expressions you often need to enable
#![feature(stmt_expr_attributes)].
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
/// ```
///
/// Use instead:
/// ```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"
}
}
If the lint is in the restriction group because it lints things that are not
necessarily âbadâ but are more of a style choice, then replace the
âWhy is this bad?â section heading with âWhy restrict this?â, to avoid writing
âWhy is this bad? It isnât, but âŚâ.
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.
Conflicting lints
There are several lints that deal with the same pattern but suggest different approaches. In other words, some lints may suggest modifications that go in the opposite direction to what some other lints already propose for the same code, creating conflicting diagnostics.
When you are creating a lint that ends up in this scenario, the following tips should be encouraged to guide classification:
- The only case where they should be in the same category is if that category is
restriction. For example,semicolon_inside_blockandsemicolon_outside_block. - For all the other cases, they should be in different categories with different levels of allowance. For example,
implicit_return(restriction, allow) andneedless_return(style, warn).
For lints that are in different categories, it is also recommended that at least one of them should be in the
restriction category. The reason for this is that the restriction group is the only group where we donât
recommend to enable the entire set, but cherry pick lints out of.
PR Checklist
Before submitting your PR make sure you followed all the basic requirements:
- [ ] Followed lint naming conventions
- [ ] Added passing UI tests (including committed
.stderrfile) - [ ]
cargo testpasses 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 which is searched for in:
- The directory specified by the
CLIPPY_CONF_DIRenvironment variable, or - The directory specified by the CARGO_MANIFEST_DIR environment variable, or
- The current 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:
-
Adding a new configuration entry to
clippy_config::conflike this:/// 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
Debugimplementation of the type. -
Adding the configuration value to the lint impl struct:
-
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 pub struct StructName {} impl_lint_pass!(StructName => [ LINT_NAME ]); } -
Next add the configuration value and a corresponding creation method like this:
#![allow(unused)] fn main() { pub struct StructName { configuration_ident: Type, } // ... impl StructName { pub fn new(conf: &'static Conf) -> Self { Self { configuration_ident: conf.configuration_ident, } } } }
-
-
Passing the configuration value to the lint impl struct:
First find the struct construction in the
clippy_lintslib file. The configuration value is now cloned or copied into a local value that is then passed to the impl struct like this:// Default generated registration: store.register_*_pass(|| box module::StructName); // New registration with configuration value store.register_*_pass(move || box module::StructName::new(conf));Congratulations the work is almost done. The configuration value can now be accessed in the linting code via
self.configuration_ident. -
Adding tests:
- The default configured value can be tested like any normal lint in
tests/ui. - The configuration itself will be tested separately in
tests/ui-toml. Simply add a new subfolder with a fitting name. This folder contains aclippy.tomlfile with the configuration value and a rust file that should be linted by Clippy. The test can otherwise be written as usual.
- The default configured value can be tested like any normal lint in
-
Update Lint Configuration
Run
cargo bless --test config-metadatato generate documentation changes for the book.
Cheat Sheet
Here are some pointers to things you are likely going to need for every lint:
- Clippy utils - Various helper functions. Maybe the function you need
is already in here (
implements_trait,snippet, etc) - Clippy diagnostics
- Let chains
from_expansionandin_external_macroSpanApplicability- Common tools for writing lints helps with common operations
- The rustc-dev-guide explains a lot of internal compiler concepts
- The nightly rustc docs which has been linked to throughout this guide
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.
Define New Lints
The first step in the journey of a new lint is the definition and registration of the lint in Clippyâs codebase. We can use the Clippy dev tools to handle this step since setting up the lint involves some boilerplate code.
Lint types
A lint type is the category of items and expressions in which your lint focuses on.
As of the writing of this documentation update, there are 11 types of lints
besides the numerous standalone lints living under clippy_lints/src/:
cargocastsfunctionsloopsmatchesmethodsmisc_earlyoperatorstransmutetypesunit_types
These types group together lints that share some common behaviors. For instance,
functions groups together lints that deal with some aspects of functions in
Rust, like definitions, signatures and attributes.
For more information, feel free to compare the lint files under any category with All Clippy lints or ask one of the maintainers.
Lint name
A good lint name is important, make sure to check the lint naming guidelines. Donât worry, if the lint name doesnât fit, a Clippy team member will alert you in the PR process.
Weâll name our example lint that detects functions named âfooâ foo_functions.
Check the lint naming guidelines to see why this name makes
sense.
Add and Register the Lint
Now that a name is chosen, we shall register foo_functions as a lint to the
codebase. There are two ways to register a lint.
Standalone
If you believe that this new lint is a standalone lint (that doesnât belong to
any specific type like functions or loops), you can run the
following command in your Clippy project:
$ cargo dev new_lint --name=lint_name --pass=late --category=pedantic
There are two things to note here:
--pass: We set--pass=latein this command to do a late lint pass. The alternative is anearlylint pass. We will discuss this difference in the Lint Passes chapter.--category: If not provided, thecategoryof this new lint will default tonursery.
The cargo dev new_lint command will create a new file:
clippy_lints/src/foo_functions.rs as well as register the
lint.
Overall, you should notice that the following files are modified or created:
$ git status
On branch foo_functions
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: CHANGELOG.md
modified: clippy_lints/src/lib.register_lints.rs
modified: clippy_lints/src/lib.register_pedantic.rs
modified: clippy_lints/src/lib.rs
Untracked files:
(use "git add <file>..." to include in what will be committed)
clippy_lints/src/foo_functions.rs
tests/ui/foo_functions.rs
Specific Type
Note: Lint types are listed in the âLint typesâ section
If you believe that this new lint belongs to a specific type of lints,
you can run cargo dev new_lint with a --type option.
Since our foo_functions lint is related to function calls, one could
argue that we should put it into a group of lints that detect some behaviors
of functions, we can put it in the functions group.
Letâs run the following command in your Clippy project:
$ cargo dev new_lint --name=foo_functions --type=functions --category=pedantic
This command will create, among other things, a new file:
clippy_lints/src/{type}/foo_functions.rs.
In our case, the path will be clippy_lints/src/functions/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. Clippy groups together some lints that share common behaviors,
so if your lint falls into one, it would be best to add it to that type.
Overall, you should notice that the following files are modified or created:
$ git status
On branch foo_functions
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: CHANGELOG.md
modified: clippy_lints/src/declared_lints.rs
modified: clippy_lints/src/functions/mod.rs
Untracked files:
(use "git add <file>..." to include in what will be committed)
clippy_lints/src/functions/foo_functions.rs
tests/ui/foo_functions.rs
The declare_clippy_lint macro
After cargo dev new_lint, you should see a macro with the name
declare_clippy_lint. It will be in the same file if you defined a standalone
lint, and it will be in mod.rs if you defined a type-specific lint.
The macro looks something like this:
#![allow(unused)]
fn main() {
declare_clippy_lint! {
/// ### What it does
///
/// // Describe here what does the lint do.
///
/// Triggers when detects...
///
/// ### Why is this bad?
///
/// // Describe why this pattern would be bad
///
/// It can lead to...
///
/// ### Example
/// ```rust
/// // example code where Clippy issues a warning
/// ```
/// Use instead:
/// ```rust
/// // example code which does not raise Clippy warning
/// ```
#[clippy::version = "1.70.0"] // <- In which version was this implemented, keep it up to date!
pub LINT_NAME, // <- The lint name IN_ALL_CAPS
pedantic, // <- The lint group
"default lint description" // <- A lint description, e.g. "A function has an unit return type."
}
}
Lint registration
If we run the cargo dev new_lint command for a new lint, the lint will be
automatically registered and there is nothing more to do.
However, sometimes we might want to declare a new lint by hand. In this case,
weâd use cargo dev update_lints command afterwards.
When a lint is manually declared, we might need to register the lint pass
manually by adding an entry to the late_lint_methods! macro invocation in
clippy_lints/src/lib.rs, at the // add late passes here marker:
#![allow(unused)]
fn main() {
FooFunctions: foo_functions::FooFunctions = foo_functions::FooFunctions,
}
As you might have guessed, where thereâs something late, there is something
early: in Clippy there is an early_lint_methods! macro as well. More on early
vs. late passes in the Lint Passes chapter.
Without an entry in one of early_lint_methods! or late_lint_methods!, the
lint pass in question will not be run.
Testing
Developing lints for Clippy is a Test-Driven Development (TDD) process because our first task before implementing any logic for a new lint is to write some test cases.
Develop Lints with Tests
When we develop Clippy, we enter a complex and chaotic realm full of programmatic issues, stylistic errors, illogical code and non-adherence to convention. Tests are the first layer of order we can leverage to define when and where we want a new lint to trigger or not.
Moreover, writing tests first help Clippy developers to find a balance for the first iteration of and further enhancements for a lint. With test cases on our side, we will not have to worry about over-engineering a lint on its first version nor missing out some obvious edge cases of the lint. This approach empowers us to iteratively enhance each lint.
Clippy UI Tests
We use UI tests for testing in Clippy. These UI tests check that the output of Clippy is exactly as we expect it to be. Each test is just a plain Rust file that contains the code we want to check.
The output of Clippy is compared against a .stderr file. Note that you donât
have to create this file yourself. Weâll get to generating the .stderr files
with the command cargo bless (seen later on).
Write Test Cases
Let us now think about some tests for our imaginary foo_functions lint. We
start by opening the test file tests/ui/foo_functions.rs that was created by
cargo dev new_lint.
Update the file with some examples to get started:
#![warn(clippy::foo_functions)] // < Add this, so the lint is guaranteed to be enabled in this file
// Impl methods
struct A;
impl A {
pub fn fo(&self) {}
pub fn foo(&self) {}
//~^ foo_functions
pub fn food(&self) {}
}
// Default trait methods
trait B {
fn fo(&self) {}
fn foo(&self) {}
//~^ foo_functions
fn food(&self) {}
}
// Plain functions
fn fo() {}
fn foo() {}
//~^ foo_functions
fn food() {}
fn main() {
// We also don't want to lint method calls
foo();
let a = A;
a.foo();
}
Without actual lint logic to emit the lint when we see a foo function name,
this test will fail, because we expect errors at lines marked with
//~^ foo_functions. However, we can now run the test with the following command:
$ TESTNAME=foo_functions cargo uitest
Clippy will compile and it will fail complaining it didnât receive any errors:
...Clippy warnings and test outputs...
error: diagnostic code `clippy::foo_functions` not found on line 8
--> tests/ui/foo_functions.rs:9:10
|
9 | //~^ foo_functions
| ^^^^^^^^^^^^^ expected because of this pattern
|
error: diagnostic code `clippy::foo_functions` not found on line 16
--> tests/ui/foo_functions.rs:17:10
|
17 | //~^ foo_functions
| ^^^^^^^^^^^^^ expected because of this pattern
|
error: diagnostic code `clippy::foo_functions` not found on line 23
--> tests/ui/foo_functions.rs:24:6
|
24 | //~^ foo_functions
| ^^^^^^^^^^^^^ expected because of this pattern
|
This is normal. After all, we wrote a bunch of Rust code but we havenât really
implemented any logic for Clippy to detect foo functions and emit a lint.
As we gradually implement our lint logic, we will keep running this UI test command. Clippy will begin outputting information that allows us to check if the output is turning into what we want it to be.
Example output
As our foo_functions lint is tested, the output would look something like this:
failures:
---- compile_test stdout ----
normalized stderr:
error: function called "foo"
--> tests/ui/foo_functions.rs:6:12
|
LL | pub fn foo(&self) {}
| ^^^
|
= note: `-D clippy::foo-functions` implied by `-D warnings`
error: function called "foo"
--> tests/ui/foo_functions.rs:13:8
|
LL | fn foo(&self) {}
| ^^^
error: function called "foo"
--> tests/ui/foo_functions.rs:19:4
|
LL | fn foo() {}
| ^^^
error: aborting due to 3 previous errors
Note the failures label at the top of the fragment, weâll get rid of it (saving this output) in the next section.
Note: You can run multiple test files by specifying a comma separated list:
TESTNAME=foo_functions,bar_methods,baz_structs.
cargo bless
Once we are satisfied with the output, we need to run this command to
generate or update the .stderr file for our lint:
$ TESTNAME=foo_functions cargo uibless
This writes the emitted lint suggestions and fixes to the .stderr file, with
the reason for the lint, suggested fixes, and line numbers, etc.
Running TESTNAME=foo_functions cargo uitest should pass then. When we commit
our lint, we need to commit the generated .stderr files, too.
In general, you should only commit files changed by cargo bless for the
specific lint you are creating/editing.
Note: If the generated
.stderr, and.fixedfiles are empty, they should be removed.
toml Tests
Some lints can be configured through a clippy.toml file. Those configuration
values are tested in tests/ui-toml.
To add a new test there, create a new directory and add the files:
clippy.toml: Put here the configuration value you want to test.lint_name.rs: A test file where you put the testing code, that should see a different lint behavior according to the configuration set in theclippy.tomlfile.
The potential .stderr and .fixed files can again be generated with cargo bless.
Cargo Lints
The process of testing is different for Cargo lints in that now we are
interested in the Cargo.toml manifest file. In this case, we also need a
minimal crate associated with that manifest. Those tests are generated in
tests/ui-cargo.
Imagine we have a new example lint that is named foo_categories, we can run:
$ cargo dev new_lint --name=foo_categories --pass=late --category=cargo
After running cargo dev new_lint we will find by default two new crates,
each with its manifest file:
tests/ui-cargo/foo_categories/fail/Cargo.toml: this file should cause the new lint to raise an error.tests/ui-cargo/foo_categories/pass/Cargo.toml: this file should not trigger the lint.
If you need more cases, you can copy one of those crates (under
foo_categories) and rename it.
The process of generating the .stderr file is the same as for other lints
and prepending the TESTNAME variable to cargo uitest works for Cargo lints too.
Rustfix Tests
If the lint you are working on is making use of structured suggestions,
rustfix will apply the suggestions from the lint to the test file code and
compare that to the contents of a .fixed file.
Structured suggestions tell a user how to fix or re-write certain code that has
been linted with span_lint_and_sugg.
Should span_lint_and_sugg be used to generate a suggestion, but not all
suggestions lead to valid code, you can use the //@no-rustfix comment on top
of the test file, to not run rustfix on that file.
Weâll talk about suggestions more in depth in a later chapter.
Use cargo 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 from the working copy root.
$ cargo dev lint input.rs
Lint passes
Before working on the logic of a new lint, there is an important decision
that every Clippy developer must make: to use
EarlyLintPass or LateLintPass.
In short, the LateLintPass has access to type and symbol information while the
EarlyLintPass doesnât. If you donât need access to type information, use the
EarlyLintPass.
Let us expand on these two traits more below.
EarlyLintPass
If you examine the documentation on EarlyLintPass closely,
youâll see that every method defined for this trait utilizes a
EarlyContext. In EarlyContextâs documentation, it states:
Context for lint checking of the AST, after expansion, before lowering to HIR.
VoilĂ . EarlyLintPass works only on the Abstract Syntax Tree (AST) level.
And AST is generated during the lexing and parsing phase
of code compilation. Therefore, it doesnât know what a symbol means or information about types, and it should
be our trait choice for a new lint if the lint only deals with syntax-related issues.
While linting speed has not been a concern for Clippy,
the EarlyLintPass is faster, and it should be your choice
if you know for sure a lint does not need type information.
As a reminder, run the following command to generate boilerplate for lints
that use EarlyLintPass:
$ cargo dev new_lint --name=<your_new_lint> --pass=early --category=<your_category_choice>
Example for EarlyLintPass
Take a look at the following code:
#![allow(unused)]
fn main() {
let x = OurUndefinedType;
x.non_existing_method();
}
From the AST perspective, both lines are âgrammaticallyâ correct.
The assignment uses a let and ends with a semicolon. The invocation
of a method looks fine, too. As programmers, we might raise a few
questions already, but the parser is okay with it. This is what we
mean when we say EarlyLintPass deals with only syntax on the AST level.
Alternatively, think of the foo_functions lint we mentioned in
the Define New Lints chapter.
We want the foo_functions lint to detect functions with foo as their name.
Writing a lint that only checks for the name of a function means that we only
work with the AST and donât have to access the type system at all (the type system is where
LateLintPass comes into the picture).
LateLintPass
In contrast to EarlyLintPass, LateLintPass contains type information.
If you examine the documentation on LateLintPass closely,
you see that every method defined in this trait utilizes a
LateContext.
In LateContextâs documentation we will find methods that
deal with type-checking, which do not exist in EarlyContext, such as:
Example for LateLintPass
Let us take a look with the following example:
#![allow(unused)]
fn main() {
let x = OurUndefinedType;
x.non_existing_method();
}
These two lines of code are syntactically correct code from the perspective of the AST. We have an assignment and invoke a method on the variable that is of a type. Grammatically, everything is in order for the parser.
However, going down a level and looking at the type information,
the compiler will notice that both OurUndefinedType and non_existing_method()
are undefined.
As Clippy developers, to access such type information, we must implement
LateLintPass on our lint.
When you browse through Clippyâs lints, you will notice that almost every lint
is implemented in a LateLintPass, specifically because we often need to check
not only for syntactic issues but also type information.
Another limitation of the EarlyLintPass is that the nodes are only identified
by their position in the AST. This means that you canât just get an id and
request a certain node. For most lints that is fine, but we have some lints
that require the inspection of other nodes, which is easier at the HIR level.
In these cases, LateLintPass is the better choice.
As a reminder, run the following command to generate boilerplate for lints
that use LateLintPass:
$ cargo dev new_lint --name=<your_new_lint> --pass=late --category=<your_category_choice>
Emitting a lint
Once we have defined a lint, written UI tests and chosen the lint pass for the lint, we can begin the implementation of the lint logic so that we can emit it and gradually work towards a lint that behaves as expected.
Note that we will not go into concrete implementation of a lint logic in this chapter. We will go into details in later chapters as well as in two examples of real Clippy lints.
To emit a lint, we must implement a pass (see Lint Passes) for the lint that we have declared. In this example weâll implement a âlateâ lint, so take a look at the LateLintPass documentation, which provides an abundance of methods that we can implement for our lint.
#![allow(unused)]
fn main() {
pub trait LateLintPass<'tcx>: LintPass {
// Trait methods
}
}
By far the most common method used for Clippy lints is check_expr
method, this is because Rust is an expression language and,
more often than not, the lint we want to work on must examine expressions.
Note: If you donât fully understand what expressions are in Rust, take a look at the official documentation on expressions
Other common ones include the check_fn method and the
check_item method.
Emitting a lint
Inside the trait method that we implement, we can write down the lint logic and emit the lint with suggestions.
Clippyâs diagnostics provides quite a few diagnostic functions that we can use to emit lints. Take a look at the documentation to pick one that suits your lintâs needs the best. Some common ones you will encounter in the Clippy repository includes:
span_lint: Emits a lint without providing any other informationspan_lint_and_note: Emits a lint and adds a notespan_lint_and_help: Emits a lint and provides a helpful messagespan_lint_and_sugg: Emits a lint and provides a suggestion to fix the codespan_lint_and_then: Likespan_lint, but allows for a lot of output customization.
#![allow(unused)]
fn main() {
impl<'tcx> LateLintPass<'tcx> for LintName {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Imagine that `some_lint_expr_logic` checks for requirements for emitting the lint
if some_lint_expr_logic(expr) {
span_lint_and_help(
cx, // < The context
LINT_NAME, // < The name of the lint in ALL CAPS
expr.span, // < The span to lint
"message on why the lint is emitted",
None, // < An optional help span (to highlight something in the lint)
"message that provides a helpful suggestion",
);
}
}
}
}
Note: The message should be matter of fact and avoid capitalization and punctuation. If multiple sentences are needed, the messages should probably be split up into an error + a help / note / suggestion message.
Suggestions: Automatic fixes
Some lints know what to change in order to fix the code. For example, the lint
range_plus_one warns for ranges where the user wrote x..y + 1 instead of using an inclusive range (x..=y). The fix to
this code would be changing the x..y + 1 expression to x..=y. This is
where suggestions come in.
A suggestion is a change that the lint provides to fix the issue it is linting. The output looks something like this (from the example earlier):
error: an inclusive range would be more readable
--> tests/ui/range_plus_minus_one.rs:37:14
|
LL | for _ in 1..1 + 1 {}
| ^^^^^^^^ help: use: `1..=1`
Applicability
Not all suggestions are always right, some of them require human supervision, thatâs why we have Applicability.
Applicability indicates confidence in the correctness of the suggestion, some
are always right (Applicability::MachineApplicable), but we use
Applicability::MaybeIncorrect and others when talking about a suggestion that
may be incorrect.
Example
The same lint LINT_NAME but that emits a suggestion would look something like this:
#![allow(unused)]
fn main() {
impl<'tcx> LateLintPass<'tcx> for LintName {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Imagine that `some_lint_expr_logic` checks for requirements for emitting the lint
if some_lint_expr_logic(expr) {
span_lint_and_then( // < Note this change
cx,
LINT_NAME,
expr.span,
"message on why the lint is emitted",
|diag| {
// v Build and emit the suggestion
let mut app = Applicability::MachineApplicable;
let expr_snippet = snippet_with_applicability(cx, expr.span, "_", &mut app);
let sugg = format!("foo + {expr_snippet} * bar");
diag.span_suggestion(expr.span, "use", sugg, app);
}
);
}
}
}
}
Suggestions generally use the format! macro to interpolate the
old values with the new ones. For information on getting code snippets, see Snippets.
How to choose between notes, help messages and suggestions
Notes are presented separately from the main lint message, they provide useful information that the user needs to understand why the lint was activated. They are the most helpful when attached to a span.
Examples:
Notes
error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing.
--> tests/ui/drop_forget_ref.rs:10:5
|
10 | forget(&SomeStruct);
| ^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::forget-ref` implied by `-D warnings`
note: argument has type &SomeStruct
--> tests/ui/drop_forget_ref.rs:10:12
|
10 | forget(&SomeStruct);
| ^^^^^^^^^^^
Help Messages
Help messages are specifically to help the user. These are used in situation where you canât provide a specific machine applicable suggestion. They can also be attached to a span.
Example:
error: constant division of 0.0 with 0.0 will always result in NaN
--> tests/ui/zero_div_zero.rs:6:25
|
6 | let other_f64_nan = 0.0f64 / 0.0;
| ^^^^^^^^^^^^
|
= help: consider using `f64::NAN` if you would like a constant representing NaN
Suggestions
Suggestions are the most helpful, they are changes to the source code to fix the
error. The magic in suggestions is that tools like rustfix can detect them and
automatically fix your code.
Example:
error: This `.fold` can be more succinctly expressed as `.any`
--> tests/ui/methods.rs:390:13
|
390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`
|
Snippets
Snippets are pieces of the source code (as a string), they are extracted
generally using the various snippet_* functions from clippy_utils::source.
If youâre using the snippets not to build a suggestion, itâs usually
enough to use snippet â it accepts the span of the item, and
also a fallback string (see Fallback string).
For example, if you want to know how an item looks (and you know the itemâs
span), you could use snippet(cx, span, "_").
If you do use the snippet for a suggestion, itâs recommended to use
snippet_with_applicability instead. This is so that Clippy can reduce the
applicability of the suggestion in case it couldnât extract
the snippet âcleanlyâ â see the functionâs documentation for more information.
Itâs often necessary to create multiple snippets to build a suggestion, in which case youâll have the following pattern:
#![allow(unused)]
fn main() {
// inside `span_lint_and_then`
// 1. Create initial applicability.
let mut app = Applicability::MachineApplicable;
// 2. Use it to create all the snippets
let foo_snippet = snippet_with_applicability(cx, foo.span, "_", &mut app);
let bar_snippet = snippet_with_applicability(cx, bar.span, "_", &mut app);
let sugg = format!("{foo_snippet} + {bar_snippet}"); // or whatever
// 3. Use it to emit the final suggestion
diag.span_suggestion(span, msg, sugg, app);
}
Fallback string
This is the string that is used for the snippet when the source code that the span points to is unavailable. That mostly only happens when proc-macros mishandle spans â see the section on proc-macros.
Most of the time, the snippets in a suggestion come from a (span of a) single
expression, and so "_" is an appropriate fallback string. If you instead
find yourself suggesting to insert multiple statements, or a blockâbasically
anything âbigââthen consider using ".." instead.
Final: Run UI Tests to Emit the Lint
Now, if we run our UI test, we should see that Clippy now produces output that contains the lint message we designed.
The next step is to implement the logic properly, which is a detail that we will cover in the next chapters.
Type Checking
When we work on a new lint or improve an existing lint, we might want
to retrieve the type Ty of an expression Expr for a variety of
reasons. This can be achieved by utilizing the LateContext
that is available for LateLintPass.
LateContext and TypeckResults
The lint context LateContext and TypeckResults
(returned by LateContext::typeck_results) are the two most useful data structures
in LateLintPass. They allow us to jump to type definitions and other compilation
stages such as HIR.
Note:
LateContext.typeck_resultsâs return value isTypeckResultsand is created in the type checking step, it includes useful information such as types of expressions, ways to resolve methods and so on.
TypeckResults contains useful methods such as expr_ty,
which gives us access to the underlying structure Ty of a given expression.
#![allow(unused)]
fn main() {
pub fn expr_ty(&self, expr: &Expr<'_>) -> Ty<'tcx>
}
As a side note, besides expr_ty, TypeckResults contains a
pat_ty() method that is useful for retrieving a type from a pattern.
Ty
Ty struct contains the type information of an expression.
Letâs take a look at rustc_middleâs Ty struct to examine this struct:
#![allow(unused)]
fn main() {
pub struct Ty<'tcx>(Interned<'tcx, WithStableHash<TyS<'tcx>>>);
}
At a first glance, this struct looks quite esoteric. But at a closer look, we will see that this struct contains many useful methods for type checking.
For instance, is_char checks if the given Ty struct corresponds
to the primitive character type.
is_* Usage
In some scenarios, all we need to do is check if the Ty of an expression
is a specific type, such as char type, so we could write the following:
#![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);
// Check if the `Ty` of this expression is of character type
if ty.is_char() {
println!("Our expression is a char!");
}
}
}
}
Furthermore, if we examine the source code for is_char,
we find something very interesting:
#![allow(unused)]
fn main() {
#[inline]
pub fn is_char(self) -> bool {
matches!(self.kind(), Char)
}
}
Indeed, we just discovered Tyâs kind() method, which provides us
with TyKind of a Ty.
TyKind
TyKind defines the kinds of types in Rustâs type system.
Peeking into TyKind documentation, we will see that it is an
enum of over 25 variants, including items such as Bool, Int, Ref, etc.
kind Usage
The TyKind of Ty can be returned by calling Ty.kind() method.
We often use this method to perform pattern matching in Clippy.
For instance, if we want to check for a struct, we could examine if the
ty.kind corresponds to an Adt (algebraic data type) and if its
AdtDef is a struct:
#![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 the type
match ty.kind() {
ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"),
_ => ()
}
}
}
}
hir::Ty and ty::Ty
Weâve been talking about ty::Ty this whole time without addressing hir::Ty, but the latter
is also important to understand.
hir::Ty would represent what the user wrote, while ty::Ty is how the compiler sees the type and has more
information. Example:
#![allow(unused)]
fn main() {
fn foo(x: u32) -> u32 { x }
}
Here the HIR sees the types without âthinkingâ about them, it knows that the function takes an u32 and returns
an u32. As far as hir::Ty is concerned those might be different types. But at the ty::Ty level the compiler
understands that theyâre the same type, in-depth lifetimes, etcâŚ
To get from a hir::Ty to a ty::Ty, you can use the lower_ty function outside of bodies or
the TypeckResults::node_type() method inside of bodies.
Warning: Donât use
lower_tyinside of bodies, because this can cause ICEs.
Creating Types programmatically
A common usecase for creating types programmatically is when we want to check if a type implements a trait (see Trait Checking).
Hereâs an example of how to create a Ty for a slice of u8, i.e. [u