Merge commit '83e42a2337dadac915c956d125f1d69132f36425' into clippyup

This commit is contained in:
Philipp Krones 2023-04-11 15:31:08 +02:00
parent fe129a022a
commit 6b95029f17
145 changed files with 4737 additions and 1704 deletions

View File

@ -11,3 +11,6 @@ target-dir = "target"
[unstable]
binary-dep-depinfo = true
[profile.dev]
split-debuginfo = "unpacked"

View File

@ -11,6 +11,7 @@ trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4
max_line_length = 120
[*.md]
# double whitespace at end of line

View File

@ -180,6 +180,8 @@ jobs:
# Run
- name: Build Integration Test
env:
CARGO_PROFILE_DEV_SPLIT_DEBUGINFO: off
run: cargo test --test integration --features integration --no-run
# Upload

View File

@ -29,7 +29,7 @@ jobs:
- name: Install mdbook
run: |
mkdir mdbook
curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.4.18/mdbook-v0.4.18-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook
curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.4.28/mdbook-v0.4.28-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook
echo `pwd`/mdbook >> $GITHUB_PATH
# Run

View File

@ -4441,6 +4441,7 @@ Released 2018-09-13
[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
[`checked_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions
[`clear_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#clear_with_drain
[`clone_double_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref
[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy
[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr
@ -4632,6 +4633,7 @@ Released 2018-09-13
[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays
[`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups
[`large_enum_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
[`large_futures`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_futures
[`large_include_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_include_file
[`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays
[`large_types_passed_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value
@ -4645,6 +4647,7 @@ Released 2018-09-13
[`let_underscore_untyped`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_untyped
[`let_unit_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value
[`let_with_type_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_with_type_underscore
[`lines_filter_map_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok
[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
@ -4671,6 +4674,7 @@ Released 2018-09-13
[`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid
[`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain
[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
[`manual_slice_size_calculation`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_size_calculation
[`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once
[`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat
[`manual_string_new`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_string_new
@ -4921,6 +4925,7 @@ Released 2018-09-13
[`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl
[`suspicious_assignment_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_assignment_formatting
[`suspicious_command_arg_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_command_arg_space
[`suspicious_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_doc_comments
[`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting
[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map
[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
@ -4933,6 +4938,7 @@ Released 2018-09-13
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
[`tests_outside_test_module`]: https://rust-lang.github.io/rust-clippy/master/index.html#tests_outside_test_module
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args
@ -4974,6 +4980,7 @@ Released 2018-09-13
[`unit_hash`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_hash
[`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord
[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints
[`unnecessary_box_returns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map

View File

@ -11,7 +11,7 @@ Lints are divided into categories, each with a default [lint level](https://doc.
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** |
@ -130,7 +130,7 @@ for example.
You can add Clippy to Travis CI in the same way you use it locally:
```yml
```yaml
language: rust
rust:
- stable
@ -253,7 +253,7 @@ rust-version = "1.30"
The MSRV can also be specified as an attribute, like below.
```rust
```rust,ignore
#![feature(custom_inner_attributes)]
#![clippy::msrv = "1.30.0"]

View File

@ -14,7 +14,7 @@ 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** |

View File

@ -13,6 +13,7 @@
- [Development](development/README.md)
- [Basics](development/basics.md)
- [Adding Lints](development/adding_lints.md)
- [Type Checking](development/type_checking.md)
- [Common Tools](development/common_tools_writing_lints.md)
- [Infrastructure](development/infrastructure/README.md)
- [Syncing changes between Clippy and rust-lang/rust](development/infrastructure/sync.md)

View File

@ -3,7 +3,7 @@
> **Note:** The configuration file is unstable and may be deprecated in the future.
Some lints can be configured in a TOML file named `clippy.toml` or `.clippy.toml`. It contains a
basic `variable = value` mapping eg.
basic `variable = value` mapping e.g.
```toml
avoid-breaking-exported-api = false
@ -60,7 +60,7 @@ And to warn on `lint_name`, run
cargo clippy -- -W clippy::lint_name
```
This also works with lint groups. For example you can run Clippy with warnings for all lints enabled:
This also works with lint groups. For example, you can run Clippy with warnings for all lints enabled:
```terminal
cargo clippy -- -W clippy::pedantic
@ -84,7 +84,7 @@ msrv = "1.30.0"
The MSRV can also be specified as an attribute, like below.
```rust
```rust,ignore
#![feature(custom_inner_attributes)]
#![clippy::msrv = "1.30.0"]
@ -96,7 +96,28 @@ fn main() {
You can also omit the patch version when specifying the MSRV, so `msrv = 1.30`
is equivalent to `msrv = 1.30.0`.
Note: `custom_inner_attributes` is an unstable feature so it has to be enabled explicitly.
Note: `custom_inner_attributes` is an unstable feature, so it has to be enabled explicitly.
Lints that recognize this configuration option can be
found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv)
### 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](https://doc.rust-lang.org/reference/conditional-compilation.html) by checking that the
`cargo-clippy` feature is not set. You may need to provide a stub so that the code compiles:
```rust
#[cfg(not(feature = "cargo-clippy"))]
include!(concat!(env!("OUT_DIR"), "/my_big_function-generated.rs"));
#[cfg(feature = "cargo-clippy")]
fn my_big_function(_input: &str) -> Option<MyStruct> {
None
}
```
This feature is not actually part of your crate, so specifying `--all-features` to other tools, e.g. `cargo test
--all-features`, will not disable it.

View File

@ -5,7 +5,7 @@ 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.
> this point onward, and you should return to one of the earlier chapters.
## Getting started

View File

@ -18,6 +18,7 @@ because that's clearly a non-descriptive name.
- [Cargo lints](#cargo-lints)
- [Rustfix tests](#rustfix-tests)
- [Testing manually](#testing-manually)
- [Running directly](#running-directly)
- [Lint declaration](#lint-declaration)
- [Lint registration](#lint-registration)
- [Lint passes](#lint-passes)
@ -186,6 +187,15 @@ cargo dev lint input.rs
from the working copy root. With tests in place, let's have a look at
implementing our lint now.
## Running directly
While it's easier to just use `cargo dev lint`, it might be desirable to get
`target/release/cargo-clippy` and `target/release/clippy-driver` to work as well in some cases.
By default, they don't work because clippy dynamically links rustc. To help them find rustc,
add the path printed by`rustc --print target-libdir` (ran inside this workspace so that the rustc version matches)
to your library search path.
On linux, this can be done by setting the `LD_LIBRARY_PATH` environment variable to that path.
## Lint declaration
Let's start by opening the new file created in the `clippy_lints` crate at
@ -265,7 +275,7 @@ When declaring a new lint by hand and `cargo dev update_lints` is used, the lint
pass may have to be registered manually in the `register_plugins` function in
`clippy_lints/src/lib.rs`:
```rust
```rust,ignore
store.register_early_pass(|| Box::new(foo_functions::FooFunctions));
```
@ -291,7 +301,7 @@ either [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass].
In short, the `LateLintPass` has access to type information while the
`EarlyLintPass` doesn't. If you don't need access to type information, use the
`EarlyLintPass`. The `EarlyLintPass` is also faster. However linting speed
`EarlyLintPass`. The `EarlyLintPass` is also faster. However, linting speed
hasn't really been a concern with Clippy so far.
Since we don't need type information for checking the function name, we used
@ -308,7 +318,7 @@ implementation of the lint logic.
Let's start by implementing the `EarlyLintPass` for our `FooFunctions`:
```rust
```rust,ignore
impl EarlyLintPass for FooFunctions {
fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
// TODO: Emit lint here
@ -327,10 +337,10 @@ variety of lint emission functions. They can all be found in
[`clippy_utils/src/diagnostics.rs`][diagnostics].
`span_lint_and_help` seems most appropriate in this case. It allows us to
provide an extra help message and we can't really suggest a better name
provide an extra help message, and we can't really suggest a better name
automatically. This is how it looks:
```rust
```rust,ignore
impl EarlyLintPass for FooFunctions {
fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
span_lint_and_help(
@ -469,7 +479,7 @@ the value from `clippy.toml`. This can be accounted for using the
`extract_msrv_attr!(LintContext)` macro and passing
`LateContext`/`EarlyContext`.
```rust
```rust,ignore
impl<'tcx> LateLintPass<'tcx> for ManualStrip {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
...
@ -483,7 +493,7 @@ 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.
```rust
```rust,ignore
...
#[clippy::msrv = "1.44"]
@ -514,7 +524,7 @@ define_Conf! {
If you have trouble implementing your lint, there is also the internal `author`
lint to generate Clippy code that detects the offending pattern. It does not
work for all of the Rust syntax, but can give a good starting point.
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][author_example]. Put the code you want to lint into the
@ -607,7 +617,7 @@ output in the `stdout` part.
## PR Checklist
Before submitting your PR make sure you followed all of the basic requirements:
Before submitting your PR make sure you followed all the basic requirements:
<!-- Sync this with `.github/PULL_REQUEST_TEMPLATE` -->
@ -627,7 +637,7 @@ for some users. Adding a configuration is done in the following steps:
1. Adding a new configuration entry to [`clippy_lints::utils::conf`] like this:
```rust
```rust,ignore
/// Lint: LINT_NAME.
///
/// <The configuration field doc comment>
@ -680,7 +690,7 @@ for some users. Adding a configuration is done in the following steps:
configuration value is now cloned or copied into a local value that is then
passed to the impl struct like this:
```rust
```rust,ignore
// Default generated registration:
store.register_*_pass(|| box module::StructName);

View File

@ -4,8 +4,8 @@ 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].
[Adding Lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/adding_lints.md
[Common Tools]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/common_tools_writing_lints.md
[Adding Lints]: adding_lints.md
[Common Tools]: common_tools_writing_lints.md
- [Basics for hacking on Clippy](#basics-for-hacking-on-clippy)
- [Get the Code](#get-the-code)
@ -125,7 +125,7 @@ We follow a rustc no merge-commit policy. See
## Common Abbreviations
| Abbreviation | Meaning |
| ------------ | -------------------------------------- |
|--------------|----------------------------------------|
| UB | Undefined Behavior |
| FP | False Positive |
| FN | False Negative |

View File

@ -3,7 +3,7 @@
You may need following tooltips to catch up with common operations.
- [Common tools for writing lints](#common-tools-for-writing-lints)
- [Retrieving the type of an expression](#retrieving-the-type-of-an-expression)
- [Retrieving the type of expression](#retrieving-the-type-of-expression)
- [Checking if an expr is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method)
- [Checking for a specific type](#checking-for-a-specific-type)
- [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait)
@ -16,7 +16,7 @@ Useful Rustc dev guide links:
- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html)
- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html)
## Retrieving the type of an expression
## Retrieving the type of expression
Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for
example to answer following questions:
@ -45,7 +45,7 @@ impl LateLintPass<'_> for MyStructLint {
}
```
Similarly in [`TypeckResults`][TypeckResults] methods, you have the
Similarly, in [`TypeckResults`][TypeckResults] methods, you have the
[`pat_ty()`][pat_ty] method to retrieve a type from a pattern.
Two noticeable items here:
@ -192,7 +192,7 @@ functions to deal with macros:
- `span.from_expansion()`: detects if a span is from macro expansion or
desugaring. Checking this is a common first step in a lint.
```rust
```rust,ignore
if expr.span.from_expansion() {
// just forget it
return;
@ -203,11 +203,11 @@ functions to deal with macros:
if so, which macro call expanded it. It is sometimes useful to check if the
context of two spans are equal.
```rust
```rust,ignore
// expands to `1 + 0`, but don't lint
1 + mac!()
```
```rust
```rust,ignore
if left.span.ctxt() != right.span.ctxt() {
// the coder most likely cannot modify this expression
return;
@ -246,7 +246,7 @@ functions to deal with macros:
`macro_rules!` with `a == $b`, `$b` is expanded to some expression with a
different context from `a`.
```rust
```rust,ignore
macro_rules! m {
($a:expr, $b:expr) => {
if $a.is_some() {

View File

@ -13,7 +13,7 @@ guide to Clippy that you're reading right now. The Clippy book is formatted with
While not strictly necessary since the book source is simply Markdown text
files, having mdBook locally will allow you to build, test and serve the book
locally to view changes before you commit them to the repository. You likely
already have `cargo` installed, so the easiest option is to simply:
already have `cargo` installed, so the easiest option is to:
```shell
cargo install mdbook
@ -26,7 +26,7 @@ instructions for other options.
The book's
[src](https://github.com/rust-lang/rust-clippy/tree/master/book/src)
directory contains all of the markdown files used to generate the book. If you
directory contains all the markdown files used to generate the book. If you
want to see your changes in real time, you can use the mdBook `serve` command to
run a web server locally that will automatically update changes as they are
made. From the top level of your `rust-clippy` directory:

View File

@ -101,7 +101,7 @@ Look for the [`beta-accepted`] label and make sure to also include the PRs with
that label in the changelog. If you can, remove the `beta-accepted` labels
**after** the changelog PR was merged.
> _Note:_ Some of those PRs might even got backported to the previous `beta`.
> _Note:_ Some of those PRs might even get backported to the previous `beta`.
> Those have to be included in the changelog of the _previous_ release.
### 4. Update `clippy::version` attributes

View File

@ -44,7 +44,7 @@ $ git push origin backport_remerge # This can be pushed to your fork
```
After this, open a PR to the master branch. In this PR, the commit hash of the
`HEAD` of the `beta` branch must exists. In addition to that, no files should be
`HEAD` of the `beta` branch must exist. In addition to that, no files should be
changed by this PR.
## Update the `beta` branch

View File

@ -19,8 +19,7 @@ to beta. For reference, the first sync following this cadence was performed the
2020-08-27.
This process is described in detail in the following sections. For general
information about `subtree`s in the Rust repository see [Rust's
`CONTRIBUTING.md`][subtree].
information about `subtree`s in the Rust repository see [the rustc-dev-guide][subtree].
## Patching git-subtree to work with big repos
@ -47,7 +46,7 @@ sudo chown --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subt
> _Note:_ If you are a Debian user, `dash` is the shell used by default for
> scripts instead of `sh`. This shell has a hardcoded recursion limit set to
> 1000. In order to make this process work, you need to force the script to run
> 1,000. In order to make this process work, you need to force the script to run
> `bash` instead. You can do this by editing the first line of the `git-subtree`
> script and changing `sh` to `bash`.
@ -71,10 +70,10 @@ $ git remote add clippy-local /path/to/rust-clippy
## Performing the sync from [`rust-lang/rust`] to Clippy
Here is a TL;DR version of the sync process (all of the following commands have
Here is a TL;DR version of the sync process (all the following commands have
to be run inside the `rust` directory):
1. Clone the [`rust-lang/rust`] repository or make sure it is up to date.
1. Clone the [`rust-lang/rust`] repository or make sure it is up-to-date.
2. Checkout the commit from the latest available nightly. You can get it using
`rustup check`.
3. Sync the changes to the rust-copy of Clippy to your Clippy fork:
@ -107,7 +106,7 @@ to be run inside the `rust` directory):
## Performing the sync from Clippy to [`rust-lang/rust`]
All of the following commands have to be run inside the `rust` directory.
All the following commands have to be run inside the `rust` directory.
1. Make sure you have checked out the latest `master` of `rust-lang/rust`.
2. Sync the `rust-lang/rust-clippy` master to the rust-copy of Clippy:
@ -118,5 +117,5 @@ All of the following commands have to be run inside the `rust` directory.
3. Open a PR to [`rust-lang/rust`]
[gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493
[subtree]: https://rustc-dev-guide.rust-lang.org/contributing.html#external-dependencies-subtree
[subtree]: https://rustc-dev-guide.rust-lang.org/external-repos.html#external-dependencies-subtree
[`rust-lang/rust`]: https://github.com/rust-lang/rust

View File

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

View File

@ -52,8 +52,8 @@ In the following, plans to improve the usability are covered.
#### No Output After `cargo check`
Currently when `cargo clippy` is run after `cargo check`, it does not produce
any output. This is especially problematic since `rust-analyzer` is on the rise
Currently, when `cargo clippy` is run after `cargo check`, it does not produce
any output. This is especially problematic since `rust-analyzer` is on the rise,
and it uses `cargo check` for checking code. A fix is already implemented, but
it still has to be pushed over the finish line. This also includes the
stabilization of the `cargo clippy --fix` command or the support of multi-span
@ -221,7 +221,7 @@ regarding the user facing issues.
Rust's roadmap process was established by [RFC 1728] in 2016. Since then every
year a roadmap was published, that defined the bigger plans for the coming
years. This years roadmap can be found [here][Rust Roadmap 2021].
years. This year roadmap can be found [here][Rust Roadmap 2021].
[RFC 1728]: https://rust-lang.github.io/rfcs/1728-north-star.html

View File

@ -16,7 +16,7 @@ lints. For non-trivial lints, it often requires nested pattern matching of AST /
HIR nodes. For example, testing that an expression is a boolean literal requires
the following checks:
```rust
```rust,ignore
if let ast::ExprKind::Lit(lit) = &expr.node {
if let ast::LitKind::Bool(_) = &lit.node {
...
@ -28,7 +28,7 @@ Writing this kind of matching code quickly becomes a complex task and the
resulting code is often hard to comprehend. The code below shows a simplified
version of the pattern matching required by the `collapsible_if` lint:
```rust
```rust,ignore
// simplified version of the collapsible_if lint
if let ast::ExprKind::If(check, then, None) = &expr.node {
if then.stmts.len() == 1 {
@ -111,7 +111,7 @@ expressions that are boolean literals with value `false`.
The pattern can then be used to implement lints in the following way:
```rust
```rust,ignore
...
impl EarlyLintPass for MyAwesomeLint {
@ -346,7 +346,7 @@ pattern!{
one could get references to the nodes that matched the subpatterns in the
following way:
```rust
```rust,ignore
...
fn check_expr(expr: &syntax::ast::Expr) {
if let Some(result) = my_pattern(expr) {
@ -372,7 +372,7 @@ matches arrays that consist of any number of literal expressions. Because those
expressions are named `foo`, the result struct contains a `foo` attribute which
is a vector of expressions:
```rust
```rust,ignore
...
if let Some(result) = my_pattern_seq(expr) {
result.foo // type: Vec<&syntax::ast::Expr>
@ -394,7 +394,7 @@ In the pattern above, the `bar` name is only defined if the pattern matches a
boolean literal. If it matches an integer literal, the name isn't set. To
account for this, the result struct's `bar` attribute is an option type:
```rust
```rust,ignore
...
if let Some(result) = my_pattern_alt(expr) {
result.bar // type: Option<&bool>
@ -404,7 +404,7 @@ if let Some(result) = my_pattern_alt(expr) {
It's also possible to use a name in multiple alternation branches if they have
compatible types:
```rust
```rust,ignore
pattern!{
// matches if expression is a boolean or integer literal
my_pattern_mult: Expr =
@ -519,7 +519,7 @@ The `Alt`, `Seq` and `Opt` structs look like these:
> Note: The current implementation can be found
> [here](https://github.com/fkohlgrueber/pattern-matching/blob/dfb3bc9fbab69cec7c91e72564a63ebaa2ede638/pattern-match/src/matchers.rs#L35-L60).
```rust
```rust,ignore
pub enum Alt<T> {
Any,
Elmt(Box<T>),
@ -580,7 +580,7 @@ implementations is the `IsMatch` trait. It defines how to match *PatternTree*
nodes against specific syntax tree nodes. A simplified implementation of the
`IsMatch` trait is shown below:
```rust
```rust,ignore
pub trait IsMatch<O> {
fn is_match(&self, other: &'o O) -> bool;
}
@ -619,7 +619,7 @@ approach (matching against the coarse pattern first and checking for additional
properties later) might be slower than the current practice of checking for
structure and additional properties in one pass. For example, the following lint
```rust
```rust,ignore
pattern!{
pat_if_without_else: Expr =
If(
@ -644,7 +644,7 @@ first matches against the pattern and then checks that the `then` block doesn't
start with a comment. Using clippy's current approach, it's possible to check
for these conditions earlier:
```rust
```rust,ignore
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
if_chain! {
if let ast::ExprKind::If(ref check, ref then, None) = expr.node;
@ -708,7 +708,7 @@ is similar to actual Rust syntax (probably like the `quote!` macro). For
example, a pattern that matches `if` expressions that have `false` in their
condition could look like this:
```rust
```rust,ignore
if false {
#[*]
}
@ -742,7 +742,7 @@ affects the structure of the resulting AST. `1 + 0 + 0` is parsed as `(1 + 0) +
Another example of a problem would be named submatches. Take a look at this
pattern:
```rust
```rust,ignore
fn test() {
1 #foo
}
@ -862,7 +862,7 @@ op b` and recommends changing it to `a op= b` requires that both occurrences of
`a` are the same. Using `=#...` as syntax for backreferences, the lint could be
implemented like this:
```rust
```rust,ignore
pattern!{
assign_op_pattern: Expr =
Assign(_#target, Binary(_, =#target, _)

View File

@ -0,0 +1,144 @@
# 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`][LateContext]
that is available for [`LateLintPass`][LateLintPass].
## `LateContext` and `TypeckResults`
The lint context [`LateContext`][LateContext] and [`TypeckResults`][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 is [`TypeckResults`][TypeckResults]
> and 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`][expr_ty],
which gives us access to the underlying structure [`Ty`][Ty] of a given expression.
```rust
pub fn expr_ty(&self, expr: &Expr<'_>) -> Ty<'tcx>
```
As a side note, besides `expr_ty`, [`TypeckResults`][TypeckResults] contains a
[`pat_ty()`][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`][Ty] struct to examine this struct:
```rust
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`][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:
```rust
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][is_char_source] for `is_char`,
we find something very interesting:
```rust
#[inline]
pub fn is_char(self) -> bool {
matches!(self.kind(), Char)
}
```
Indeed, we just discovered `Ty`'s [`kind` method][kind], which provides us
with [`TyKind`][TyKind] of a `Ty`.
## `TyKind`
`TyKind` defines the kinds of types in Rust's type system.
Peeking into [`TyKind` documentation][TyKind], we will see that it is an
enum of 27 variants, including items such as `Bool`, `Int`, `Ref`, etc.
### `kind` Usage
The `TyKind` of `Ty` can be returned by calling [`Ty.kind` method][kind].
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`][Adt] (algebraic data type) and if its
[`AdtDef`][AdtDef] is a struct:
```rust
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`][middle_ty] this whole time without addressing [`hir::Ty`][hir_ty], but the latter
is also important to understand.
`hir::Ty` would represent *what* an user wrote, while `ty::Ty` would understand the meaning of it (because it has more
information).
**Example: `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`. But at the `ty::Ty` level the compiler understands that they're the same type, in-depth lifetimes, etc...
you can use the [`hir_ty_to_ty`][hir_ty_to_ty] function to convert from a `hir::Ty` to a `ty::Ty`
## Useful Links
Below are some useful links to further explore the concepts covered
in this chapter:
- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation)
- [Diagnostic items](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html)
- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html)
- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html)
[Adt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html#variant.Adt
[AdtDef]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/adt/struct.AdtDef.html
[expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty
[is_char]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html#method.is_char
[is_char_source]: https://doc.rust-lang.org/nightly/nightly-rustc/src/rustc_middle/ty/sty.rs.html#1831-1834
[kind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html#method.kind
[LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html
[LateLintPass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html
[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty
[Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html
[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html
[TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html
[middle_ty]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/ty/struct.Ty.html
[hir_ty]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/struct.Ty.html
[hir_ty_to_ty]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir_analysis/fn.hir_ty_to_ty.html

View File

@ -17,8 +17,8 @@ $ 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.
Take a look at the [Basics] chapter in the Clippy developer guide to find step-by-step
instructions on how to build and install Clippy from source.
[Basics]: development/basics.md#install-from-source
[Usage]: usage.md

View File

@ -54,6 +54,7 @@ Please use that command to update the file and do not edit it by hand.
| [allow-mixed-uninlined-format-args](#allow-mixed-uninlined-format-args) | `true` |
| [suppress-restriction-lint-in-const](#suppress-restriction-lint-in-const) | `false` |
| [missing-docs-in-crate-items](#missing-docs-in-crate-items) | `false` |
| [future-size-threshold](#future-size-threshold) | `16384` |
### arithmetic-side-effects-allowed
Suppress checking of the passed type names in all types of operations.
@ -130,6 +131,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat
* [option_option](https://rust-lang.github.io/rust-clippy/master/index.html#option_option)
* [linkedlist](https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist)
* [rc_mutex](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex)
* [unnecessary_box_returns](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns)
### msrv
@ -193,7 +195,7 @@ The maximum cognitive complexity a function can have
### 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 configuration of Clippy. By default, any configuration will replace the default value.
**Default Value:** `["foo", "baz", "quux"]` (`Vec<String>`)
@ -203,7 +205,7 @@ default configuration of Clippy. By default any configuration will replace the d
### 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 configuraction will replace the default value. For example:
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 append `ClipPy` to the default list.
@ -413,7 +415,7 @@ For internal testing only, ignores the current `publish` settings in the Cargo m
Enforce the named macros always use the braces specified.
A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
is could be used with a full path two `MacroMatcher`s have to be added one with the full path
could be used with a full path two `MacroMatcher`s have to be added one with the full path
`crate_name::macro_name` and one with just the macro name.
**Default Value:** `[]` (`Vec<crate::nonstandard_macro_braces::MacroMatcher>`)
@ -447,7 +449,7 @@ Whether to apply the raw pointer heuristic to determine if a type is `Send`.
### 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 would be necessary, the lint is suppressed.
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` (`u64`)
@ -551,4 +553,12 @@ crate. For example, `pub(crate)` items.
* [missing_docs_in_private_items](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items)
### future-size-threshold
The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
**Default Value:** `16384` (`u64`)
* [large_futures](https://rust-lang.github.io/rust-clippy/master/index.html#large_futures)

View File

@ -17,7 +17,7 @@ The different lint groups were defined in the [Clippy 1.0 RFC].
The `clippy::correctness` group is the only lint group in Clippy which lints are
deny-by-default and abort the compilation when triggered. This is for good
reason: If you see a `correctness` lint, it means that your code is outright
wrong or useless and you should try to fix it.
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.
@ -41,7 +41,7 @@ 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
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.
@ -50,9 +50,9 @@ complexity lints on a case-by-case basis.
The `clippy::perf` group gives you suggestions on how you can increase the
performance of your code. Those lints are mostly about code that the compiler
can't trivially optimize, but has to be written in a slightly different way to
make the optimizer's job easier.
make the optimizer job easier.
Perf lints are usually easy to apply and it is recommended to do so.
Perf lints are usually easy to apply, and it is recommended to do so.
## Style
@ -91,7 +91,7 @@ and your use case.
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
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

View File

@ -19,7 +19,7 @@ cargo clippy
### Lint configuration
The above command will run the default set of lints, which are included in the
lint group `clippy::all`. You might want to use even more lints or you might not
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.
@ -98,7 +98,7 @@ other of Clippy's lint groups.
You can configure lint levels in source code the same way you can configure
`rustc` lints:
```rust
```rust,ignore
#![allow(clippy::style)]
#[warn(clippy::double_neg)]

View File

@ -1,3 +1,4 @@
#![feature(lazy_cell)]
#![feature(let_chains)]
#![feature(rustc_private)]
#![cfg_attr(feature = "deny-warnings", deny(warnings))]

View File

@ -369,9 +369,7 @@ pub(super) fn check(cx: &{context_import}, msrv: &Msrv) {{
}}
todo!();
}}
"#,
context_import = context_import,
name_upper = name_upper,
"#
);
} else {
let _: fmt::Result = writedoc!(
@ -385,9 +383,7 @@ pub(super) fn check(cx: &{context_import}, msrv: &Msrv) {{
pub(super) fn check(cx: &{context_import}) {{
todo!();
}}
"#,
context_import = context_import,
name_upper = name_upper,
"#
);
}

View File

@ -537,17 +537,13 @@ fn declare_deprecated(name: &str, path: &Path, reason: &str) -> io::Result<()> {
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// {}
#[clippy::version = \"{}\"]
pub {},
\"{}\"
/// {deprecation_reason}
#[clippy::version = \"{version}\"]
pub {name},
\"{reason}\"
}}
",
deprecation_reason,
version,
name,
reason,
"
)
}

View File

@ -9,6 +9,7 @@ keywords = ["clippy", "lint", "plugin"]
edition = "2021"
[dependencies]
arrayvec = { version = "0.7", default-features = false }
cargo_metadata = "0.15.3"
clippy_utils = { path = "../clippy_utils" }
declare_clippy_lint = { path = "../declare_clippy_lint" }

View File

@ -7,7 +7,7 @@
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint::{LateContext, LateLintPass, Level};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::def_id::LocalDefId;
use rustc_span::source_map::Span;
@ -430,23 +430,25 @@ fn bool_expr(&self, e: &'tcx Expr<'_>) {
}
}
let nonminimal_bool_lint = |suggestions: Vec<_>| {
span_lint_hir_and_then(
self.cx,
NONMINIMAL_BOOL,
e.hir_id,
e.span,
"this boolean expression can be simplified",
|diag| {
diag.span_suggestions(
e.span,
"try",
suggestions.into_iter(),
// nonminimal_bool can produce minimal but
// not human readable expressions (#3141)
Applicability::Unspecified,
);
},
);
if self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, e.hir_id).0 != Level::Allow {
span_lint_hir_and_then(
self.cx,
NONMINIMAL_BOOL,
e.hir_id,
e.span,
"this boolean expression can be simplified",
|diag| {
diag.span_suggestions(
e.span,
"try",
suggestions.into_iter(),
// nonminimal_bool can produce minimal but
// not human readable expressions (#3141)
Applicability::Unspecified,
);
},
);
}
};
if improvements.is_empty() {
let mut visitor = NotSimplificationVisitor { cx: self.cx };
@ -498,6 +500,7 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind &&
!inner.span.from_expansion() &&
let Some(suggestion) = simplify_not(self.cx, inner)
&& self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow
{
span_lint_and_sugg(
self.cx,

View File

@ -2,8 +2,9 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::expr_or_init;
use clippy_utils::source::snippet;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize};
use rustc_errors::{Applicability, SuggestionStyle};
use rustc_errors::{Applicability, Diagnostic, SuggestionStyle};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
@ -163,19 +164,34 @@ pub(super) fn check(
_ => return,
};
let name_of_cast_from = snippet(cx, cast_expr.span, "..");
let cast_to_snip = snippet(cx, cast_to_span, "..");
let suggestion = format!("{cast_to_snip}::try_from({name_of_cast_from})");
span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg, |diag| {
diag.help("if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...");
diag.span_suggestion_with_style(
expr.span,
"... or use `try_from` and handle the error accordingly",
suggestion,
Applicability::Unspecified,
// always show the suggestion in a separate line
SuggestionStyle::ShowAlways,
);
if !cast_from.is_floating_point() {
offer_suggestion(cx, expr, cast_expr, cast_to_span, diag);
}
});
}
fn offer_suggestion(
cx: &LateContext<'_>,
expr: &Expr<'_>,
cast_expr: &Expr<'_>,
cast_to_span: Span,
diag: &mut Diagnostic,
) {
let cast_to_snip = snippet(cx, cast_to_span, "..");
let suggestion = if cast_to_snip == "_" {
format!("{}.try_into()", Sugg::hir(cx, cast_expr, "..").maybe_par())
} else {
format!("{cast_to_snip}::try_from({})", Sugg::hir(cx, cast_expr, ".."))
};
diag.span_suggestion_with_style(
expr.span,
"... or use `try_from` and handle the error accordingly",
suggestion,
Applicability::Unspecified,
// always show the suggestion in a separate line
SuggestionStyle::ShowAlways,
);
}

View File

@ -1,9 +1,9 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::visitors::for_each_expr_with_closures;
use clippy_utils::{get_enclosing_block, get_parent_node, path_to_local_id};
use core::ops::ControlFlow;
use rustc_hir::{Block, ExprKind, HirId, Local, Node, PatKind};
use rustc_hir::{Block, ExprKind, HirId, LangItem, Local, Node, PatKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::sym;
@ -44,7 +44,8 @@
}
declare_lint_pass!(CollectionIsNeverRead => [COLLECTION_IS_NEVER_READ]);
static COLLECTIONS: [Symbol; 10] = [
// Add `String` here when it is added to diagnostic items
static COLLECTIONS: [Symbol; 9] = [
sym::BTreeMap,
sym::BTreeSet,
sym::BinaryHeap,
@ -52,7 +53,6 @@
sym::HashSet,
sym::LinkedList,
sym::Option,
sym::String,
sym::Vec,
sym::VecDeque,
];
@ -60,8 +60,7 @@
impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead {
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
// Look for local variables whose type is a container. Search surrounding bock for read access.
let ty = cx.typeck_results().pat_ty(local.pat);
if COLLECTIONS.iter().any(|&sym| is_type_diagnostic_item(cx, ty, sym))
if match_acceptable_type(cx, local, &COLLECTIONS)
&& let PatKind::Binding(_, local_id, _, _) = local.pat.kind
&& let Some(enclosing_block) = get_enclosing_block(cx, local.hir_id)
&& has_no_read_access(cx, local_id, enclosing_block)
@ -71,6 +70,13 @@ fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
}
}
fn match_acceptable_type(cx: &LateContext<'_>, local: &Local<'_>, collections: &[rustc_span::Symbol]) -> bool {
let ty = cx.typeck_results().pat_ty(local.pat);
collections.iter().any(|&sym| is_type_diagnostic_item(cx, ty, sym))
// String type is a lang item but not a diagnostic item for now so we need a separate check
|| is_type_lang_item(cx, ty, LangItem::String)
}
fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Block<'tcx>) -> bool {
let mut has_access = false;
let mut has_read_access = false;
@ -95,9 +101,9 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc
return ControlFlow::Continue(());
}
// Method call on `id` in a statement ignores any return value, so it's not a read access:
// Look for method call with receiver `id`. It might be a non-read access:
//
// id.foo(...); // Not reading `id`.
// id.foo(args)
//
// Only assuming this for "official" methods defined on the type. For methods defined in extension
// traits (identified as local, based on the orphan rule), pessimistically assume that they might
@ -105,11 +111,24 @@ fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Bloc
if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id)
&& let ExprKind::MethodCall(_, receiver, _, _) = parent.kind
&& path_to_local_id(receiver, id)
&& let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id)
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
&& !method_def_id.is_local()
{
return ControlFlow::Continue(());
// The method call is a statement, so the return value is not used. That's not a read access:
//
// id.foo(args);
if let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id) {
return ControlFlow::Continue(());
}
// The method call is not a statement, so its return value is used somehow but its type is the
// unit type, so this is not a real read access. Examples:
//
// let y = x.clear();
// println!("{:?}", x.clear());
if cx.typeck_results().expr_ty(parent).is_unit() {
return ControlFlow::Continue(());
}
}
// Any other access to `id` is a read access. Stop searching.

View File

@ -218,6 +218,7 @@
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,
crate::large_const_arrays::LARGE_CONST_ARRAYS_INFO,
crate::large_enum_variant::LARGE_ENUM_VARIANT_INFO,
crate::large_futures::LARGE_FUTURES_INFO,
crate::large_include_file::LARGE_INCLUDE_FILE_INFO,
crate::large_stack_arrays::LARGE_STACK_ARRAYS_INFO,
crate::len_zero::COMPARISON_TO_EMPTY_INFO,
@ -231,6 +232,7 @@
crate::let_with_type_underscore::LET_WITH_TYPE_UNDERSCORE_INFO,
crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO,
crate::lifetimes::NEEDLESS_LIFETIMES_INFO,
crate::lines_filter_map_ok::LINES_FILTER_MAP_OK_INFO,
crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO,
crate::literal_representation::INCONSISTENT_DIGIT_GROUPING_INFO,
crate::literal_representation::LARGE_DIGIT_GROUPS_INFO,
@ -267,6 +269,7 @@
crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO,
crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO,
crate::manual_retain::MANUAL_RETAIN_INFO,
crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,
crate::manual_string_new::MANUAL_STRING_NEW_INFO,
crate::manual_strip::MANUAL_STRIP_INFO,
crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO,
@ -307,6 +310,7 @@
crate::methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS_INFO,
crate::methods::CHARS_LAST_CMP_INFO,
crate::methods::CHARS_NEXT_CMP_INFO,
crate::methods::CLEAR_WITH_DRAIN_INFO,
crate::methods::CLONED_INSTEAD_OF_COPIED_INFO,
crate::methods::CLONE_DOUBLE_REF_INFO,
crate::methods::CLONE_ON_COPY_INFO,
@ -565,6 +569,7 @@
crate::strings::STR_TO_STRING_INFO,
crate::strings::TRIM_SPLIT_WHITESPACE_INFO,
crate::strlen_on_c_strings::STRLEN_ON_C_STRINGS_INFO,
crate::suspicious_doc_comments::SUSPICIOUS_DOC_COMMENTS_INFO,
crate::suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS_INFO,
crate::suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL_INFO,
crate::suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL_INFO,
@ -574,6 +579,7 @@
crate::swap_ptr_to_ref::SWAP_PTR_TO_REF_INFO,
crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO,
crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO,
crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO,
crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO,
@ -616,6 +622,7 @@
crate::unit_types::UNIT_CMP_INFO,
crate::unnamed_address::FN_ADDRESS_COMPARISONS_INFO,
crate::unnamed_address::VTABLE_ADDRESS_COMPARISONS_INFO,
crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO,
crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO,
crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO,
crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO,

View File

@ -32,7 +32,7 @@
/// ### Example
/// ```rust
/// // Assuming that `clippy.toml` contains the following line:
/// // allowed-locales = ["Latin", "Cyrillic"]
/// // allowed-scripts = ["Latin", "Cyrillic"]
/// let counter = 10; // OK, latin is allowed.
/// let счётчик = 10; // OK, cyrillic is allowed.
/// let zähler = 10; // OK, it's still latin.

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::FormatArgsExpn;
use clippy_utils::macros::{find_format_args, format_args_inputs_span};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_expn_of, match_function_call, paths};
use if_chain::if_chain;
@ -8,7 +8,7 @@
use rustc_hir::{BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
use rustc_span::{sym, ExpnId};
declare_clippy_lint! {
/// ### What it does
@ -43,23 +43,22 @@
impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! {
// match call to unwrap
if let ExprKind::MethodCall(unwrap_fun, write_call, [], _) = expr.kind;
if unwrap_fun.ident.name == sym::unwrap;
// match call to unwrap
if let ExprKind::MethodCall(unwrap_fun, write_call, [], _) = expr.kind
&& unwrap_fun.ident.name == sym::unwrap
// match call to write_fmt
if let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = look_in_block(cx, &write_call.kind);
if write_fun.ident.name == sym!(write_fmt);
&& let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = look_in_block(cx, &write_call.kind)
&& write_fun.ident.name == sym!(write_fmt)
// match calls to std::io::stdout() / std::io::stderr ()
if let Some(dest_name) = if match_function_call(cx, write_recv, &paths::STDOUT).is_some() {
&& let Some(dest_name) = if match_function_call(cx, write_recv, &paths::STDOUT).is_some() {
Some("stdout")
} else if match_function_call(cx, write_recv, &paths::STDERR).is_some() {
Some("stderr")
} else {
None
};
if let Some(format_args) = FormatArgsExpn::parse(cx, write_arg);
then {
}
{
find_format_args(cx, write_arg, ExpnId::root(), |format_args| {
let calling_macro =
// ordering is important here, since `writeln!` uses `write!` internally
if is_expn_of(write_call.span, "writeln").is_some() {
@ -92,7 +91,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let mut applicability = Applicability::MachineApplicable;
let inputs_snippet = snippet_with_applicability(
cx,
format_args.inputs_span(),
format_args_inputs_span(format_args),
"..",
&mut applicability,
);
@ -104,8 +103,8 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
"try this",
format!("{prefix}{sugg_mac}!({inputs_snippet})"),
applicability,
)
}
);
});
}
}
}

View File

@ -1,10 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
use clippy_utils::trait_ref_of_method;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::MultiSpan;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_impl_item, walk_item, walk_param_bound, walk_ty, Visitor};
use rustc_hir::{
BodyId, ExprKind, GenericBound, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind,
BodyId, ExprKind, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind,
PredicateOrigin, Ty, TyKind, WherePredicate,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -53,13 +53,19 @@ pub fn new(avoid_breaking_exported_api: bool) -> Self {
}
}
/// Don't lint external macros or functions with empty bodies. Also, don't lint public items if
/// the `avoid_breaking_exported_api` config option is set.
fn check_false_positive(&self, cx: &LateContext<'_>, span: Span, def_id: LocalDefId, body_id: BodyId) -> bool {
/// Don't lint external macros or functions with empty bodies. Also, don't lint exported items
/// if the `avoid_breaking_exported_api` config option is set.
fn is_empty_exported_or_macro(
&self,
cx: &LateContext<'_>,
span: Span,
def_id: LocalDefId,
body_id: BodyId,
) -> bool {
let body = cx.tcx.hir().body(body_id).value;
let fn_empty = matches!(&body.kind, ExprKind::Block(blk, None) if blk.stmts.is_empty() && blk.expr.is_none());
let is_exported = cx.effective_visibilities.is_exported(def_id);
in_external_macro(cx.sess(), span) || (self.avoid_breaking_exported_api && is_exported) || fn_empty
in_external_macro(cx.sess(), span) || fn_empty || (is_exported && self.avoid_breaking_exported_api)
}
}
@ -69,85 +75,129 @@ fn check_false_positive(&self, cx: &LateContext<'_>, span: Span, def_id: LocalDe
/// trait bounds those parameters have.
struct TypeWalker<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>,
/// Collection of all the function's type parameters.
/// Collection of the function's type parameters. Once the function has been walked, this will
/// contain only unused type parameters.
ty_params: FxHashMap<DefId, Span>,
/// Collection of any (inline) trait bounds corresponding to each type parameter.
bounds: FxHashMap<DefId, Span>,
/// Collection of any inline trait bounds corresponding to each type parameter.
inline_bounds: FxHashMap<DefId, Span>,
/// Collection of any type parameters with trait bounds that appear in a where clause.
where_bounds: FxHashSet<DefId>,
/// The entire `Generics` object of the function, useful for querying purposes.
generics: &'tcx Generics<'tcx>,
/// The value of this will remain `true` if *every* parameter:
/// 1. Is a type parameter, and
/// 2. Goes unused in the function.
/// Otherwise, if any type parameters end up being used, or if any lifetime or const-generic
/// parameters are present, this will be set to `false`.
all_params_unused: bool,
}
impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> {
fn new(cx: &'cx LateContext<'tcx>, generics: &'tcx Generics<'tcx>) -> Self {
let mut all_params_unused = true;
let ty_params = generics
.params
.iter()
.filter_map(|param| {
if let GenericParamKind::Type { synthetic, .. } = param.kind {
(!synthetic).then_some((param.def_id.into(), param.span))
} else {
if !param.is_elided_lifetime() {
all_params_unused = false;
}
None
}
.filter_map(|param| match param.kind {
GenericParamKind::Type { synthetic, .. } if !synthetic => Some((param.def_id.into(), param.span)),
_ => None,
})
.collect();
Self {
cx,
ty_params,
bounds: FxHashMap::default(),
inline_bounds: FxHashMap::default(),
where_bounds: FxHashSet::default(),
generics,
all_params_unused,
}
}
fn mark_param_used(&mut self, def_id: DefId) {
if self.ty_params.remove(&def_id).is_some() {
self.all_params_unused = false;
}
fn get_bound_span(&self, param: &'tcx GenericParam<'tcx>) -> Span {
self.inline_bounds
.get(&param.def_id.to_def_id())
.map_or(param.span, |bound_span| param.span.with_hi(bound_span.hi()))
}
fn emit_help(&self, spans: Vec<Span>, msg: &str, help: &'static str) {
span_lint_and_help(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, spans, msg, None, help);
}
fn emit_sugg(&self, spans: Vec<Span>, msg: &str, help: &'static str) {
let suggestions: Vec<(Span, String)> = spans.iter().copied().zip(std::iter::repeat(String::new())).collect();
span_lint_and_then(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, spans, msg, |diag| {
diag.multipart_suggestion(help, suggestions, Applicability::MachineApplicable);
});
}
fn emit_lint(&self) {
let (msg, help) = match self.ty_params.len() {
let explicit_params = self
.generics
.params
.iter()
.filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait())
.collect::<Vec<_>>();
let extra_params = explicit_params
.iter()
.enumerate()
.filter(|(_, param)| self.ty_params.contains_key(&param.def_id.to_def_id()))
.collect::<Vec<_>>();
let (msg, help) = match extra_params.len() {
0 => return,
1 => (
"type parameter goes unused in function definition",
format!(
"type parameter `{}` goes unused in function definition",
extra_params[0].1.name.ident()
),
"consider removing the parameter",
),
_ => (
"type parameters go unused in function definition",
format!(
"type parameters go unused in function definition: {}",
extra_params
.iter()
.map(|(_, param)| param.name.ident().to_string())
.collect::<Vec<_>>()
.join(", ")
),
"consider removing the parameters",
),
};
let source_map = self.cx.sess().source_map();
let span = if self.all_params_unused {
self.generics.span.into() // Remove the entire list of generics
// If any parameters are bounded in where clauses, don't try to form a suggestion.
// Otherwise, the leftover where bound would produce code that wouldn't compile.
if extra_params
.iter()
.any(|(_, param)| self.where_bounds.contains(&param.def_id.to_def_id()))
{
let spans = extra_params
.iter()
.map(|(_, param)| self.get_bound_span(param))
.collect::<Vec<_>>();
self.emit_help(spans, &msg, help);
} else {
MultiSpan::from_spans(
self.ty_params
let spans = if explicit_params.len() == extra_params.len() {
vec![self.generics.span] // Remove the entire list of generics
} else {
let mut end: Option<LocalDefId> = None;
extra_params
.iter()
.map(|(def_id, &span)| {
// Extend the span past any trait bounds, and include the comma at the end.
let span_to_extend = self.bounds.get(def_id).copied().map_or(span, Span::shrink_to_hi);
let comma_range = source_map.span_extend_to_next_char(span_to_extend, '>', false);
let comma_span = source_map.span_through_char(comma_range, ',');
span.with_hi(comma_span.hi())
})
.collect(),
)
};
.rev()
.map(|(idx, param)| {
if let Some(next) = explicit_params.get(idx + 1) && end != Some(next.def_id) {
// Extend the current span forward, up until the next param in the list.
param.span.until(next.span)
} else {
// Extend the current span back to include the comma following the previous
// param. If the span of the next param in the list has already been
// extended, we continue the chain. This is why we're iterating in reverse.
end = Some(param.def_id);
span_lint_and_help(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, span, msg, None, help);
// idx will never be 0, else we'd be removing the entire list of generics
let prev = explicit_params[idx - 1];
let prev_span = self.get_bound_span(prev);
self.get_bound_span(param).with_lo(prev_span.hi())
}
})
.collect()
};
self.emit_sugg(spans, &msg, help);
};
}
}
@ -162,7 +212,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) {
if let Some((def_id, _)) = t.peel_refs().as_generic_param() {
self.mark_param_used(def_id);
self.ty_params.remove(&def_id);
} else if let TyKind::OpaqueDef(id, _, _) = t.kind {
// Explicitly walk OpaqueDef. Normally `walk_ty` would do the job, but it calls
// `visit_nested_item`, which checks that `Self::NestedFilter::INTER` is set. We're
@ -176,9 +226,18 @@ fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) {
fn visit_where_predicate(&mut self, predicate: &'tcx WherePredicate<'tcx>) {
if let WherePredicate::BoundPredicate(predicate) = predicate {
// Collect spans for any bounds on type parameters. We only keep bounds that appear in
// the list of generics (not in a where-clause).
// Collect spans for any bounds on type parameters.
if let Some((def_id, _)) = predicate.bounded_ty.peel_refs().as_generic_param() {
match predicate.origin {
PredicateOrigin::GenericParam => {
self.inline_bounds.insert(def_id, predicate.span);
},
PredicateOrigin::WhereClause => {
self.where_bounds.insert(def_id);
},
PredicateOrigin::ImplTrait => (),
}
// If the bound contains non-public traits, err on the safe side and don't lint the
// corresponding parameter.
if !predicate
@ -187,12 +246,10 @@ fn visit_where_predicate(&mut self, predicate: &'tcx WherePredicate<'tcx>) {
.filter_map(bound_to_trait_def_id)
.all(|id| self.cx.effective_visibilities.is_exported(id))
{
self.mark_param_used(def_id);
} else if let PredicateOrigin::GenericParam = predicate.origin {
self.bounds.insert(def_id, predicate.span);
self.ty_params.remove(&def_id);
}
}
// Only walk the right-hand side of where-bounds
// Only walk the right-hand side of where bounds
for bound in predicate.bounds {
walk_param_bound(self, bound);
}
@ -207,7 +264,7 @@ fn nested_visit_map(&mut self) -> Self::Map {
impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if let ItemKind::Fn(_, generics, body_id) = item.kind
&& !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id)
&& !self.is_empty_exported_or_macro(cx, item.span, item.owner_id.def_id, body_id)
{
let mut walker = TypeWalker::new(cx, generics);
walk_item(&mut walker, item);
@ -219,7 +276,7 @@ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'tcx>
// Only lint on inherent methods, not trait methods.
if let ImplItemKind::Fn(.., body_id) = item.kind
&& trait_ref_of_method(cx, item.owner_id.def_id).is_none()
&& !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id)
&& !self.is_empty_exported_or_macro(cx, item.span, item.owner_id.def_id, body_id)
{
let mut walker = TypeWalker::new(cx, item.generics);
walk_impl_item(&mut walker, item);

View File

@ -1,14 +1,13 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
use clippy_utils::source::snippet_with_context;
use clippy_utils::macros::{find_format_arg_expr, find_format_args, root_macro_call_first_node};
use clippy_utils::source::{snippet_opt, snippet_with_context};
use clippy_utils::sugg::Sugg;
use if_chain::if_chain;
use rustc_ast::{FormatArgsPiece, FormatOptions, FormatTrait};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::kw;
use rustc_span::{sym, Span};
declare_clippy_lint! {
@ -44,55 +43,53 @@
impl<'tcx> LateLintPass<'tcx> for UselessFormat {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (format_args, call_site) = if_chain! {
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id);
if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, macro_call.expn);
then {
(format_args, macro_call.span)
} else {
return
}
};
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
return;
}
let mut applicability = Applicability::MachineApplicable;
if format_args.args.is_empty() {
match *format_args.format_string.parts {
[] => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability),
[_] => {
find_format_args(cx, expr, macro_call.expn, |format_args| {
let mut applicability = Applicability::MachineApplicable;
let call_site = macro_call.span;
match (format_args.arguments.all_args(), &format_args.template[..]) {
([], []) => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability),
([], [_]) => {
// Simulate macro expansion, converting {{ and }} to { and }.
let s_expand = format_args.format_string.snippet.replace("{{", "{").replace("}}", "}");
let Some(snippet) = snippet_opt(cx, format_args.span) else { return };
let s_expand = snippet.replace("{{", "{").replace("}}", "}");
let sugg = format!("{s_expand}.to_string()");
span_useless_format(cx, call_site, sugg, applicability);
},
[..] => {},
([arg], [piece]) => {
if let Ok(value) = find_format_arg_expr(expr, arg)
&& let FormatArgsPiece::Placeholder(placeholder) = piece
&& placeholder.format_trait == FormatTrait::Display
&& placeholder.format_options == FormatOptions::default()
&& match cx.typeck_results().expr_ty(value).peel_refs().kind() {
ty::Adt(adt, _) => Some(adt.did()) == cx.tcx.lang_items().string(),
ty::Str => true,
_ => false,
}
{
let is_new_string = match value.kind {
ExprKind::Binary(..) => true,
ExprKind::MethodCall(path, ..) => path.ident.name == sym::to_string,
_ => false,
};
let sugg = if is_new_string {
snippet_with_context(cx, value.span, call_site.ctxt(), "..", &mut applicability).0.into_owned()
} else {
let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "<arg>", &mut applicability);
format!("{}.to_string()", sugg.maybe_par())
};
span_useless_format(cx, call_site, sugg, applicability);
}
},
_ => {},
}
} else if let [arg] = &*format_args.args {
let value = arg.param.value;
if_chain! {
if format_args.format_string.parts == [kw::Empty];
if arg.format.is_default();
if match cx.typeck_results().expr_ty(value).peel_refs().kind() {
ty::Adt(adt, _) => Some(adt.did()) == cx.tcx.lang_items().string(),
ty::Str => true,
_ => false,
};
then {
let is_new_string = match value.kind {
ExprKind::Binary(..) => true,
ExprKind::MethodCall(path, ..) => path.ident.name == sym::to_string,
_ => false,
};
let sugg = if is_new_string {
snippet_with_context(cx, value.span, call_site.ctxt(), "..", &mut applicability).0.into_owned()
} else {
let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "<arg>", &mut applicability);
format!("{}.to_string()", sugg.maybe_par())
};
span_useless_format(cx, call_site, sugg, applicability);
}
}
};
});
}
}

View File

@ -1,27 +1,31 @@
use arrayvec::ArrayVec;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::is_diag_trait_item;
use clippy_utils::macros::FormatParamKind::{Implicit, Named, NamedInline, Numbered, Starred};
use clippy_utils::macros::{
is_assert_macro, is_format_macro, is_panic, root_macro_call, Count, FormatArg, FormatArgsExpn, FormatParam,
FormatParamUsage,
find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro,
is_format_macro, is_panic, root_macro_call, root_macro_call_first_node, FormatParamUsage,
};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_lang_item};
use if_chain::if_chain;
use itertools::Itertools;
use rustc_ast::{
FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions,
FormatPlaceholder, FormatTrait,
};
use rustc_errors::{
Applicability,
SuggestionStyle::{CompletelyHidden, ShowCode},
};
use rustc_hir::{Expr, ExprKind, HirId, LangItem, QPath};
use rustc_hir::{Expr, ExprKind, LangItem};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::Ty;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::DefId;
use rustc_span::edition::Edition::Edition2021;
use rustc_span::{sym, ExpnData, ExpnKind, Span, Symbol};
use rustc_span::{sym, Span, Symbol};
declare_clippy_lint! {
/// ### What it does
@ -184,72 +188,79 @@ pub fn new(msrv: Msrv, allow_mixed_uninlined_format_args: bool) -> Self {
impl<'tcx> LateLintPass<'tcx> for FormatArgs {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let Some(format_args) = FormatArgsExpn::parse(cx, expr)
&& let expr_expn_data = expr.span.ctxt().outer_expn_data()
&& let outermost_expn_data = outermost_expn_data(expr_expn_data)
&& let Some(macro_def_id) = outermost_expn_data.macro_def_id
&& is_format_macro(cx, macro_def_id)
&& let ExpnKind::Macro(_, name) = outermost_expn_data.kind
{
for arg in &format_args.args {
check_unused_format_specifier(cx, arg);
if !arg.format.is_default() {
continue;
}
if is_aliased(&format_args, arg.param.value.hir_id) {
continue;
}
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.param.value);
check_to_string_in_format_args(cx, name, arg.param.value);
}
if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) {
check_uninlined_args(cx, &format_args, outermost_expn_data.call_site, macro_def_id, self.ignore_mixed);
}
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
if !is_format_macro(cx, macro_call.def_id) {
return;
}
let name = cx.tcx.item_name(macro_call.def_id);
find_format_args(cx, expr, macro_call.expn, |format_args| {
for piece in &format_args.template {
if let FormatArgsPiece::Placeholder(placeholder) = piece
&& let Ok(index) = placeholder.argument.index
&& let Some(arg) = format_args.arguments.all_args().get(index)
{
let arg_expr = find_format_arg_expr(expr, arg);
check_unused_format_specifier(cx, placeholder, arg_expr);
if placeholder.format_trait != FormatTrait::Display
|| placeholder.format_options != FormatOptions::default()
|| is_aliased(format_args, index)
{
continue;
}
if let Ok(arg_hir_expr) = arg_expr {
check_format_in_format_args(cx, macro_call.span, name, arg_hir_expr);
check_to_string_in_format_args(cx, name, arg_hir_expr);
}
}
}
if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) {
check_uninlined_args(cx, format_args, macro_call.span, macro_call.def_id, self.ignore_mixed);
}
});
}
extract_msrv_attr!(LateContext);
}
fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) {
let param_ty = cx.typeck_results().expr_ty(arg.param.value).peel_refs();
fn check_unused_format_specifier(
cx: &LateContext<'_>,
placeholder: &FormatPlaceholder,
arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>,
) {
let ty_or_ast_expr = arg_expr.map(|expr| cx.typeck_results().expr_ty(expr).peel_refs());
if let Count::Implied(Some(mut span)) = arg.format.precision
&& !span.is_empty()
let is_format_args = match ty_or_ast_expr {
Ok(ty) => is_type_lang_item(cx, ty, LangItem::FormatArguments),
Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)),
};
let options = &placeholder.format_options;
let arg_span = match arg_expr {
Ok(expr) => expr.span,
Err(expr) => expr.span,
};
if let Some(placeholder_span) = placeholder.span
&& is_format_args
&& *options != FormatOptions::default()
{
span_lint_and_then(
cx,
UNUSED_FORMAT_SPECS,
span,
"empty precision specifier has no effect",
|diag| {
if param_ty.is_floating_point() {
diag.note("a precision specifier is not required to format floats");
}
if arg.format.is_default() {
// If there's no other specifiers remove the `:` too
span = arg.format_span();
}
diag.span_suggestion_verbose(span, "remove the `.`", "", Applicability::MachineApplicable);
},
);
}
if is_type_lang_item(cx, param_ty, LangItem::FormatArguments) && !arg.format.is_default_for_trait() {
span_lint_and_then(
cx,
UNUSED_FORMAT_SPECS,
arg.span,
placeholder_span,
"format specifiers have no effect on `format_args!()`",
|diag| {
let mut suggest_format = |spec, span| {
let mut suggest_format = |spec| {
let message = format!("for the {spec} to apply consider using `format!()`");
if let Some(mac_call) = root_macro_call(arg.param.value.span)
if let Some(mac_call) = root_macro_call(arg_span)
&& cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id)
&& arg.span.eq_ctxt(mac_call.span)
{
diag.span_suggestion(
cx.sess().source_map().span_until_char(mac_call.span, '!'),
@ -257,25 +268,27 @@ fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) {
"format",
Applicability::MaybeIncorrect,
);
} else if let Some(span) = span {
diag.span_help(span, message);
} else {
diag.help(message);
}
};
if !arg.format.width.is_implied() {
suggest_format("width", arg.format.width.span());
if options.width.is_some() {
suggest_format("width");
}
if !arg.format.precision.is_implied() {
suggest_format("precision", arg.format.precision.span());
if options.precision.is_some() {
suggest_format("precision");
}
diag.span_suggestion_verbose(
arg.format_span(),
"if the current behavior is intentional, remove the format specifiers",
"",
Applicability::MaybeIncorrect,
);
if let Some(format_span) = format_placeholder_format_span(placeholder) {
diag.span_suggestion_verbose(
format_span,
"if the current behavior is intentional, remove the format specifiers",
"",
Applicability::MaybeIncorrect,
);
}
},
);
}
@ -283,12 +296,12 @@ fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) {
fn check_uninlined_args(
cx: &LateContext<'_>,
args: &FormatArgsExpn<'_>,
args: &rustc_ast::FormatArgs,
call_site: Span,
def_id: DefId,
ignore_mixed: bool,
) {
if args.format_string.span.from_expansion() {
if args.span.from_expansion() {
return;
}
if call_site.edition() < Edition2021 && (is_panic(cx, def_id) || is_assert_macro(cx, def_id)) {
@ -303,7 +316,13 @@ fn check_uninlined_args(
// we cannot remove any other arguments in the format string,
// because the index numbers might be wrong after inlining.
// Example of an un-inlinable format: print!("{}{1}", foo, 2)
if !args.params().all(|p| check_one_arg(args, &p, &mut fixes, ignore_mixed)) || fixes.is_empty() {
for (pos, usage) in format_arg_positions(args) {
if !check_one_arg(args, pos, usage, &mut fixes, ignore_mixed) {
return;
}
}
if fixes.is_empty() {
return;
}
@ -332,47 +351,40 @@ fn check_uninlined_args(
}
fn check_one_arg(
args: &FormatArgsExpn<'_>,
param: &FormatParam<'_>,
args: &rustc_ast::FormatArgs,
pos: &FormatArgPosition,
usage: FormatParamUsage,
fixes: &mut Vec<(Span, String)>,
ignore_mixed: bool,
) -> bool {
if matches!(param.kind, Implicit | Starred | Named(_) | Numbered)
&& let ExprKind::Path(QPath::Resolved(None, path)) = param.value.kind
&& let [segment] = path.segments
let index = pos.index.unwrap();
let arg = &args.arguments.all_args()[index];
if !matches!(arg.kind, FormatArgumentKind::Captured(_))
&& let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind
&& let [segment] = path.segments.as_slice()
&& segment.args.is_none()
&& let Some(arg_span) = args.value_with_prev_comma_span(param.value.hir_id)
&& let Some(arg_span) = format_arg_removal_span(args, index)
&& let Some(pos_span) = pos.span
{
let replacement = match param.usage {
let replacement = match usage {
FormatParamUsage::Argument => segment.ident.name.to_string(),
FormatParamUsage::Width => format!("{}$", segment.ident.name),
FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
};
fixes.push((param.span, replacement));
fixes.push((pos_span, replacement));
fixes.push((arg_span, String::new()));
true // successful inlining, continue checking
} else {
// Do not continue inlining (return false) in case
// * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)`
// * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already
param.kind != Numbered && (!ignore_mixed || matches!(param.kind, NamedInline(_)))
pos.kind != FormatArgPositionKind::Number
&& (!ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_)))
}
}
fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
if expn_data.call_site.from_expansion() {
outermost_expn_data(expn_data.call_site.ctxt().outer_expn_data())
} else {
expn_data
}
}
fn check_format_in_format_args(
cx: &LateContext<'_>,
call_site: Span,
name: Symbol,
arg: &Expr<'_>,
) {
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &Expr<'_>) {
let expn_data = arg.span.ctxt().outer_expn_data();
if expn_data.call_site.from_expansion() {
return;
@ -443,9 +455,33 @@ fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Ex
}
}
/// Returns true if `hir_id` is referred to by multiple format params
fn is_aliased(args: &FormatArgsExpn<'_>, hir_id: HirId) -> bool {
args.params().filter(|param| param.value.hir_id == hir_id).at_most_one().is_err()
fn format_arg_positions(
format_args: &rustc_ast::FormatArgs,
) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> {
format_args.template.iter().flat_map(|piece| match piece {
FormatArgsPiece::Placeholder(placeholder) => {
let mut positions = ArrayVec::<_, 3>::new();
positions.push((&placeholder.argument, FormatParamUsage::Argument));
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width {
positions.push((position, FormatParamUsage::Width));
}
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision {
positions.push((position, FormatParamUsage::Precision));
}
positions
},
FormatArgsPiece::Literal(_) => ArrayVec::new(),
})
}
/// Returns true if the format argument at `index` is referred to by multiple format params
fn is_aliased(format_args: &rustc_ast::FormatArgs, index: usize) -> bool {
format_arg_positions(format_args)
.filter(|(position, _)| position.index == Ok(index))
.at_most_one()
.is_err()
}
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
@ -455,7 +491,11 @@ fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tc
let mut n_total = 0;
let mut n_needed = 0;
loop {
if let Some(Adjustment { kind: Adjust::Deref(overloaded_deref), target }) = iter.next() {
if let Some(Adjustment {
kind: Adjust::Deref(overloaded_deref),
target,
}) = iter.next()
{
n_total += 1;
if overloaded_deref.is_some() {
n_needed = n_total;

View File

@ -1,11 +1,13 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArg, FormatArgsExpn};
use clippy_utils::macros::{find_format_arg_expr, find_format_args, is_format_macro, root_macro_call_first_node};
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
use if_chain::if_chain;
use rustc_ast::{FormatArgsPiece, FormatTrait};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
use rustc_span::{sym, symbol::kw, Symbol};
declare_clippy_lint! {
@ -89,7 +91,7 @@
}
#[derive(Clone, Copy)]
struct FormatTrait {
struct FormatTraitNames {
/// e.g. `sym::Display`
name: Symbol,
/// `f` in `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {}`
@ -99,7 +101,7 @@ struct FormatTrait {
#[derive(Default)]
pub struct FormatImpl {
// Whether we are inside Display or Debug trait impl - None for neither
format_trait_impl: Option<FormatTrait>,
format_trait_impl: Option<FormatTraitNames>,
}
impl FormatImpl {
@ -161,43 +163,57 @@ fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) {
}
}
fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTrait) {
fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTraitNames) {
// Check each arg in format calls - do we ever use Display on self (directly or via deref)?
if_chain! {
if let Some(outer_macro) = root_macro_call_first_node(cx, expr);
if let macro_def_id = outer_macro.def_id;
if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn);
if is_format_macro(cx, macro_def_id);
then {
for arg in format_args.args {
if arg.format.r#trait != impl_trait.name {
continue;
if let Some(outer_macro) = root_macro_call_first_node(cx, expr)
&& let macro_def_id = outer_macro.def_id
&& is_format_macro(cx, macro_def_id)
{
find_format_args(cx, expr, outer_macro.expn, |format_args| {
for piece in &format_args.template {
if let FormatArgsPiece::Placeholder(placeholder) = piece
&& let trait_name = match placeholder.format_trait {
FormatTrait::Display => sym::Display,
FormatTrait::Debug => sym::Debug,
FormatTrait::LowerExp => sym!(LowerExp),
FormatTrait::UpperExp => sym!(UpperExp),
FormatTrait::Octal => sym!(Octal),
FormatTrait::Pointer => sym::Pointer,
FormatTrait::Binary => sym!(Binary),
FormatTrait::LowerHex => sym!(LowerHex),
FormatTrait::UpperHex => sym!(UpperHex),
}
&& trait_name == impl_trait.name
&& let Ok(index) = placeholder.argument.index
&& let Some(arg) = format_args.arguments.all_args().get(index)
&& let Ok(arg_expr) = find_format_arg_expr(expr, arg)
{
check_format_arg_self(cx, expr.span, arg_expr, impl_trait);
}
check_format_arg_self(cx, expr, &arg, impl_trait);
}
}
});
}
}
fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArg<'_>, impl_trait: FormatTrait) {
fn check_format_arg_self(cx: &LateContext<'_>, span: Span, arg: &Expr<'_>, impl_trait: FormatTraitNames) {
// Handle multiple dereferencing of references e.g. &&self
// Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl)
// Since the argument to fmt is itself a reference: &self
let reference = peel_ref_operators(cx, arg.param.value);
let reference = peel_ref_operators(cx, arg);
let map = cx.tcx.hir();
// Is the reference self?
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
let FormatTrait { name, .. } = impl_trait;
let FormatTraitNames { name, .. } = impl_trait;
span_lint(
cx,
RECURSIVE_FORMAT_IMPL,
expr.span,
span,
&format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
);
}
}
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTrait) {
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTraitNames) {
if_chain! {
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id);
@ -227,7 +243,7 @@ fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait:
}
}
fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTrait> {
fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTraitNames> {
if_chain! {
if impl_item.ident.name == sym::fmt;
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
@ -241,7 +257,7 @@ fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Optio
.and_then(|param| param.pat.simple_ident())
.map(|ident| ident.name);
Some(FormatTrait {
Some(FormatTraitNames {
name,
formatter_name,
})

View File

@ -1,7 +1,9 @@
use hir::FnSig;
use rustc_ast::ast::Attribute;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefIdSet;
use rustc_hir::{self as hir, def::Res, QPath};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LintContext};
use rustc_middle::{
lint::in_external_macro,
@ -27,7 +29,7 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>
let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id);
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
if let Some(attr) = attr {
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr);
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, sig);
} else if is_public && !is_proc_macro(attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) {
check_must_use_candidate(
cx,
@ -49,7 +51,7 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp
let attrs = cx.tcx.hir().attrs(item.hir_id());
let attr = cx.tcx.get_attr(item.owner_id, sym::must_use);
if let Some(attr) = attr {
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr);
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, sig);
} else if is_public && !is_proc_macro(attrs) && trait_ref_of_method(cx, item.owner_id.def_id).is_none() {
check_must_use_candidate(
cx,
@ -72,7 +74,7 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr
let attrs = cx.tcx.hir().attrs(item.hir_id());
let attr = cx.tcx.get_attr(item.owner_id, sym::must_use);
if let Some(attr) = attr {
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr);
check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, sig);
} else if let hir::TraitFn::Provided(eid) = *eid {
let body = cx.tcx.hir().body(eid);
if attr.is_none() && is_public && !is_proc_macro(attrs) {
@ -97,6 +99,7 @@ fn check_needless_must_use(
item_span: Span,
fn_header_span: Span,
attr: &Attribute,
sig: &FnSig<'_>,
) {
if in_external_macro(cx.sess(), item_span) {
return;
@ -112,6 +115,15 @@ fn check_needless_must_use(
},
);
} else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) {
// Ignore async functions unless Future::Output type is a must_use type
if sig.header.is_async() {
let infcx = cx.tcx.infer_ctxt().build();
if let Some(future_ty) = infcx.get_impl_future_output_ty(return_ty(cx, item_id))
&& !is_must_use_ty(cx, future_ty) {
return;
}
}
span_lint_and_help(
cx,
DOUBLE_MUST_USE,

View File

@ -1,8 +1,8 @@
//! lint when items are used after statements
use clippy_utils::diagnostics::span_lint;
use rustc_ast::ast::{Block, ItemKind, StmtKind};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use clippy_utils::diagnostics::span_lint_hir;
use rustc_hir::{Block, ItemKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -52,33 +52,34 @@
declare_lint_pass!(ItemsAfterStatements => [ITEMS_AFTER_STATEMENTS]);
impl EarlyLintPass for ItemsAfterStatements {
fn check_block(&mut self, cx: &EarlyContext<'_>, item: &Block) {
if in_external_macro(cx.sess(), item.span) {
impl LateLintPass<'_> for ItemsAfterStatements {
fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) {
if in_external_macro(cx.sess(), block.span) {
return;
}
// skip initial items and trailing semicolons
let stmts = item
// skip initial items
let stmts = block
.stmts
.iter()
.map(|stmt| &stmt.kind)
.skip_while(|s| matches!(**s, StmtKind::Item(..) | StmtKind::Empty));
.skip_while(|stmt| matches!(stmt.kind, StmtKind::Item(..)));
// lint on all further items
for stmt in stmts {
if let StmtKind::Item(ref it) = *stmt {
if in_external_macro(cx.sess(), it.span) {
if let StmtKind::Item(item_id) = stmt.kind {
let item = cx.tcx.hir().item(item_id);
if in_external_macro(cx.sess(), item.span) || !item.span.eq_ctxt(block.span) {
return;
}
if let ItemKind::MacroDef(..) = it.kind {
if let ItemKind::Macro(..) = item.kind {
// do not lint `macro_rules`, but continue processing further statements
continue;
}
span_lint(
span_lint_hir(
cx,
ITEMS_AFTER_STATEMENTS,
it.span,
item.hir_id(),
item.span,
"adding items after statements is confusing, since items exist from the \
start of the scope",
);

View File

@ -0,0 +1,87 @@
use clippy_utils::source::snippet;
use clippy_utils::{diagnostics::span_lint_and_sugg, ty::implements_trait};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_target::abi::Size;
declare_clippy_lint! {
/// ### What it does
/// It checks for the size of a `Future` created by `async fn` or `async {}`.
///
/// ### Why is this bad?
/// Due to the current [unideal implemention](https://github.com/rust-lang/rust/issues/69826) of `Generator`,
/// large size of a `Future` may cause stack overflows.
///
/// ### Example
/// ```rust
/// async fn wait(f: impl std::future::Future<Output = ()>) {}
///
/// async fn big_fut(arg: [u8; 1024]) {}
///
/// pub async fn test() {
/// let fut = big_fut([0u8; 1024]);
/// wait(fut).await;
/// }
/// ```
///
/// `Box::pin` the big future instead.
///
/// ```rust
/// async fn wait(f: impl std::future::Future<Output = ()>) {}
///
/// async fn big_fut(arg: [u8; 1024]) {}
///
/// pub async fn test() {
/// let fut = Box::pin(big_fut([0u8; 1024]));
/// wait(fut).await;
/// }
/// ```
#[clippy::version = "1.68.0"]
pub LARGE_FUTURES,
pedantic,
"large future may lead to unexpected stack overflows"
}
#[derive(Copy, Clone)]
pub struct LargeFuture {
future_size_threshold: u64,
}
impl LargeFuture {
pub fn new(future_size_threshold: u64) -> Self {
Self { future_size_threshold }
}
}
impl_lint_pass!(LargeFuture => [LARGE_FUTURES]);
impl<'tcx> LateLintPass<'tcx> for LargeFuture {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if matches!(expr.span.ctxt().outer_expn_data().kind, rustc_span::ExpnKind::Macro(..)) {
return;
}
if let ExprKind::Match(expr, _, MatchSource::AwaitDesugar) = expr.kind {
if let ExprKind::Call(func, [expr, ..]) = expr.kind
&& let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind
&& let ty = cx.typeck_results().expr_ty(expr)
&& let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
&& implements_trait(cx, ty, future_trait_def_id, &[])
&& let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty))
&& let size = layout.layout.size()
&& size >= Size::from_bytes(self.future_size_threshold)
{
span_lint_and_sugg(
cx,
LARGE_FUTURES,
expr.span,
&format!("large future with a size of {} bytes", size.bytes()),
"consider `Box::pin` on it",
format!("Box::pin({})", snippet(cx, expr.span, "..")),
Applicability::Unspecified,
);
}
}
}
}

View File

@ -1,7 +1,6 @@
#![feature(array_windows)]
#![feature(binary_heap_into_iter_sorted)]
#![feature(box_patterns)]
#![feature(drain_filter)]
#![feature(if_let_guard)]
#![feature(iter_intersperse)]
#![feature(let_chains)]
@ -162,6 +161,7 @@
mod iter_not_returning_iterator;
mod large_const_arrays;
mod large_enum_variant;
mod large_futures;
mod large_include_file;
mod large_stack_arrays;
mod len_zero;
@ -169,6 +169,7 @@
mod let_underscore;
mod let_with_type_underscore;
mod lifetimes;
mod lines_filter_map_ok;
mod literal_representation;
mod loops;
mod macro_use;
@ -183,6 +184,7 @@
mod manual_non_exhaustive;
mod manual_rem_euclid;
mod manual_retain;
mod manual_slice_size_calculation;
mod manual_string_new;
mod manual_strip;
mod map_unit_fn;
@ -281,6 +283,7 @@
mod std_instead_of_core;
mod strings;
mod strlen_on_c_strings;
mod suspicious_doc_comments;
mod suspicious_operation_groupings;
mod suspicious_trait_impl;
mod suspicious_xor_used_as_pow;
@ -288,6 +291,7 @@
mod swap_ptr_to_ref;
mod tabs_in_doc_comments;
mod temporary_assignment;
mod tests_outside_test_module;
mod to_digit_is_some;
mod trailing_empty_array;
mod trait_bounds;
@ -299,6 +303,7 @@
mod unit_return_expecting_ord;
mod unit_types;
mod unnamed_address;
mod unnecessary_box_returns;
mod unnecessary_owned_empty_strings;
mod unnecessary_self_imports;
mod unnecessary_struct_initialization;
@ -344,13 +349,17 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Se
}
#[doc(hidden)]
pub fn read_conf(sess: &Session, path: &io::Result<Option<PathBuf>>) -> Conf {
pub fn read_conf(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> Conf {
if let Ok((_, warnings)) = path {
for warning in warnings {
sess.warn(warning);
}
}
let file_name = match path {
Ok(Some(path)) => path,
Ok(None) => return Conf::default(),
Ok((Some(path), _)) => path,
Ok((None, _)) => return Conf::default(),
Err(error) => {
sess.struct_err(format!("error finding Clippy's configuration file: {error}"))
.emit();
sess.err(format!("error finding Clippy's configuration file: {error}"));
return Conf::default();
},
};
@ -746,7 +755,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
store.register_late_pass(|_| Box::new(returns::Return));
store.register_early_pass(|| Box::new(collapsible_if::CollapsibleIf));
store.register_early_pass(|| Box::new(items_after_statements::ItemsAfterStatements));
store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements));
store.register_early_pass(|| Box::new(precedence::Precedence));
store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals));
store.register_early_pass(|| Box::new(needless_continue::NeedlessContinue));
@ -808,6 +817,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move |_| Box::new(dereference::Dereferencing::new(msrv())));
store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
let future_size_threshold = conf.future_size_threshold;
store.register_late_pass(move |_| Box::new(large_futures::LargeFuture::new(future_size_threshold)));
store.register_late_pass(|_| Box::new(if_let_mutex::IfLetMutex));
store.register_late_pass(|_| Box::new(if_not_else::IfNotElse));
store.register_late_pass(|_| Box::new(equatable_if_let::PatternEquality));
@ -934,11 +945,20 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi));
store.register_late_pass(|_| Box::new(collection_is_never_read::CollectionIsNeverRead));
store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage));
store.register_early_pass(|| Box::new(redundant_async_block::RedundantAsyncBlock));
store.register_late_pass(|_| Box::new(redundant_async_block::RedundantAsyncBlock));
store.register_late_pass(|_| Box::new(let_with_type_underscore::UnderscoreTyped));
store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute));
store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv())));
store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct));
store.register_late_pass(move |_| {
Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(
avoid_breaking_exported_api,
))
});
store.register_late_pass(|_| Box::new(lines_filter_map_ok::LinesFilterMapOk));
store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule));
store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation));
store.register_early_pass(|| Box::new(suspicious_doc_comments::SuspiciousDocComments));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View File

@ -0,0 +1,100 @@
use clippy_utils::{
diagnostics::span_lint_and_then, is_diag_item_method, is_trait_method, match_def_path, path_to_local_id, paths,
ty::match_type,
};
use rustc_errors::Applicability;
use rustc_hir::{Body, Closure, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Detect uses of `lines.filter_map(Result::ok)` or `lines.flat_map(Result::ok)`
/// when `lines` has type `std::io::Lines`.
///
/// ### Why is this bad?
/// `Lines` instances might produce a never-ending stream of `Err`, in which case
/// `filter_map(Result::ok)` will enter an infinite loop while waiting for an
/// `Ok` variant. Calling `next()` once is sufficient to enter the infinite loop,
/// even in the absence of explicit loops in the user code.
///
/// This situation can arise when working with user-provided paths. On some platforms,
/// `std::fs::File::open(path)` might return `Ok(fs)` even when `path` is a directory,
/// but any later attempt to read from `fs` will return an error.
///
/// ### Known problems
/// This lint suggests replacing `filter_map()` or `flat_map()` applied to a `Lines`
/// instance in all cases. There two cases where the suggestion might not be
/// appropriate or necessary:
///
/// - If the `Lines` instance can never produce any error, or if an error is produced
/// only once just before terminating the iterator, using `map_while()` is not
/// necessary but will not do any harm.
/// - If the `Lines` instance can produce intermittent errors then recover and produce
/// successful results, using `map_while()` would stop at the first error.
///
/// ### Example
/// ```rust
/// # use std::{fs::File, io::{self, BufRead, BufReader}};
/// # let _ = || -> io::Result<()> {
/// let mut lines = BufReader::new(File::open("some-path")?).lines().filter_map(Result::ok);
/// // If "some-path" points to a directory, the next statement never terminates:
/// let first_line: Option<String> = lines.next();
/// # Ok(()) };
/// ```
/// Use instead:
/// ```rust
/// # use std::{fs::File, io::{self, BufRead, BufReader}};
/// # let _ = || -> io::Result<()> {
/// let mut lines = BufReader::new(File::open("some-path")?).lines().map_while(Result::ok);
/// let first_line: Option<String> = lines.next();
/// # Ok(()) };
/// ```
#[clippy::version = "1.70.0"]
pub LINES_FILTER_MAP_OK,
suspicious,
"filtering `std::io::Lines` with `filter_map()` or `flat_map()` might cause an infinite loop"
}
declare_lint_pass!(LinesFilterMapOk => [LINES_FILTER_MAP_OK]);
impl LateLintPass<'_> for LinesFilterMapOk {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::MethodCall(fm_method, fm_receiver, [fm_arg], fm_span) = expr.kind &&
is_trait_method(cx, expr, sym::Iterator) &&
(fm_method.ident.as_str() == "filter_map" || fm_method.ident.as_str() == "flat_map") &&
match_type(cx, cx.typeck_results().expr_ty_adjusted(fm_receiver), &paths::STD_IO_LINES)
{
let lint = match &fm_arg.kind {
// Detect `Result::ok`
ExprKind::Path(qpath) =>
cx.qpath_res(qpath, fm_arg.hir_id).opt_def_id().map(|did|
match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD)).unwrap_or_default(),
// Detect `|x| x.ok()`
ExprKind::Closure(Closure { body, .. }) =>
if let Body { params: [param], value, .. } = cx.tcx.hir().body(*body) &&
let ExprKind::MethodCall(method, receiver, [], _) = value.kind &&
path_to_local_id(receiver, param.pat.hir_id) &&
let Some(method_did) = cx.typeck_results().type_dependent_def_id(value.hir_id)
{
is_diag_item_method(cx, method_did, sym::Result) && method.ident.as_str() == "ok"
} else {
false
}
_ => false,
};
if lint {
span_lint_and_then(cx,
LINES_FILTER_MAP_OK,
fm_span,
&format!("`{}()` will run forever if the iterator repeatedly produces an `Err`", fm_method.ident),
|diag| {
diag.span_note(
fm_receiver.span,
"this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error");
diag.span_suggestion(fm_span, "replace with", "map_while(Result::ok)", Applicability::MaybeIncorrect);
});
}
}
}
}

View File

@ -0,0 +1,93 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{expr_or_init, in_constant};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::sym;
declare_clippy_lint! {
/// ### What it does
/// When `a` is `&[T]`, detect `a.len() * size_of::<T>()` and suggest `size_of_val(a)`
/// instead.
///
/// ### Why is this better?
/// * Shorter to write
/// * Removes the need for the human and the compiler to worry about overflow in the
/// multiplication
/// * Potentially faster at runtime as rust emits special no-wrapping flags when it
/// calculates the byte length
/// * Less turbofishing
///
/// ### Example
/// ```rust
/// # let data : &[i32] = &[1, 2, 3];
/// let newlen = data.len() * std::mem::size_of::<i32>();
/// ```
/// Use instead:
/// ```rust
/// # let data : &[i32] = &[1, 2, 3];
/// let newlen = std::mem::size_of_val(data);
/// ```
#[clippy::version = "1.70.0"]
pub MANUAL_SLICE_SIZE_CALCULATION,
complexity,
"manual slice size calculation"
}
declare_lint_pass!(ManualSliceSizeCalculation => [MANUAL_SLICE_SIZE_CALCULATION]);
impl<'tcx> LateLintPass<'tcx> for ManualSliceSizeCalculation {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
// Does not apply inside const because size_of_value is not cost in stable.
if !in_constant(cx, expr.hir_id)
&& let ExprKind::Binary(ref op, left, right) = expr.kind
&& BinOpKind::Mul == op.node
&& let Some(_receiver) = simplify(cx, left, right)
{
span_lint_and_help(
cx,
MANUAL_SLICE_SIZE_CALCULATION,
expr.span,
"manual slice size calculation",
None,
"consider using std::mem::size_of_value instead");
}
}
}
fn simplify<'tcx>(
cx: &LateContext<'tcx>,
expr1: &'tcx Expr<'tcx>,
expr2: &'tcx Expr<'tcx>,
) -> Option<&'tcx Expr<'tcx>> {
let expr1 = expr_or_init(cx, expr1);
let expr2 = expr_or_init(cx, expr2);
simplify_half(cx, expr1, expr2).or_else(|| simplify_half(cx, expr2, expr1))
}
fn simplify_half<'tcx>(
cx: &LateContext<'tcx>,
expr1: &'tcx Expr<'tcx>,
expr2: &'tcx Expr<'tcx>,
) -> Option<&'tcx Expr<'tcx>> {
if
// expr1 is `[T1].len()`?
let ExprKind::MethodCall(method_path, receiver, _, _) = expr1.kind
&& method_path.ident.name == sym::len
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
&& let ty::Slice(ty1) = receiver_ty.peel_refs().kind()
// expr2 is `size_of::<T2>()`?
&& let ExprKind::Call(func, _) = expr2.kind
&& let ExprKind::Path(ref func_qpath) = func.kind
&& let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id)
&& let Some(ty2) = cx.typeck_results().node_substs(func.hir_id).types().next()
// T1 == T2?
&& *ty1 == ty2
{
Some(receiver)
} else {
None
}
}

View File

@ -1,12 +1,13 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_non_aggregate_primitive_type;
use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res};
use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res, peel_ref_operators};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
@ -101,40 +102,26 @@
impl_lint_pass!(MemReplace =>
[MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]);
fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
// Check that second argument is `Option::None`
if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
// Since this is a late pass (already type-checked),
// and we already know that the second argument is an
// `Option`, we do not need to check the first
// argument's type. All that's left is to get
// replacee's path.
let replaced_path = match dest.kind {
ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, replaced) => {
if let ExprKind::Path(QPath::Resolved(None, replaced_path)) = replaced.kind {
replaced_path
} else {
return;
}
},
ExprKind::Path(QPath::Resolved(None, replaced_path)) => replaced_path,
_ => return,
};
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
MEM_REPLACE_OPTION_WITH_NONE,
expr_span,
"replacing an `Option` with `None`",
"consider `Option::take()` instead",
format!(
"{}.take()",
snippet_with_applicability(cx, replaced_path.span, "", &mut applicability)
),
applicability,
);
}
fn check_replace_option_with_none(cx: &LateContext<'_>, dest: &Expr<'_>, expr_span: Span) {
// Since this is a late pass (already type-checked),
// and we already know that the second argument is an
// `Option`, we do not need to check the first
// argument's type. All that's left is to get
// the replacee's expr after peeling off the `&mut`
let sugg_expr = peel_ref_operators(cx, dest);
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
MEM_REPLACE_OPTION_WITH_NONE,
expr_span,
"replacing an `Option` with `None`",
"consider `Option::take()` instead",
format!(
"{}.take()",
Sugg::hir_with_context(cx, sugg_expr, expr_span.ctxt(), "", &mut applicability).maybe_par()
),
applicability,
);
}
fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
@ -200,10 +187,6 @@ fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<
if is_non_aggregate_primitive_type(expr_type) {
return;
}
// disable lint for Option since it is covered in another lint
if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
return;
}
if is_default_equivalent(cx, src) && !in_external_macro(cx.tcx.sess, expr_span) {
span_lint_and_then(
cx,
@ -246,11 +229,13 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::mem_replace, def_id);
then {
check_replace_option_with_none(cx, src, dest, expr.span);
check_replace_with_uninit(cx, src, dest, expr.span);
if self.msrv.meets(msrvs::MEM_TAKE) {
// Check that second argument is `Option::None`
if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
check_replace_option_with_none(cx, dest, expr.span);
} else if self.msrv.meets(msrvs::MEM_TAKE) {
check_replace_with_default(cx, src, dest, expr.span);
}
check_replace_with_uninit(cx, src, dest, expr.span);
}
}
}

View File

@ -0,0 +1,53 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_range_full;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{Expr, ExprKind, LangItem, QPath};
use rustc_lint::LateContext;
use rustc_span::symbol::sym;
use rustc_span::Span;
use super::CLEAR_WITH_DRAIN;
// Add `String` here when it is added to diagnostic items
const ACCEPTABLE_TYPES_WITH_ARG: [rustc_span::Symbol; 2] = [sym::Vec, sym::VecDeque];
const ACCEPTABLE_TYPES_WITHOUT_ARG: [rustc_span::Symbol; 3] = [sym::BinaryHeap, sym::HashMap, sym::HashSet];
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: Option<&Expr<'_>>) {
if let Some(arg) = arg {
if match_acceptable_type(cx, recv, &ACCEPTABLE_TYPES_WITH_ARG)
&& let ExprKind::Path(QPath::Resolved(None, container_path)) = recv.kind
&& is_range_full(cx, arg, Some(container_path))
{
suggest(cx, expr, recv, span);
}
} else if match_acceptable_type(cx, recv, &ACCEPTABLE_TYPES_WITHOUT_ARG) {
suggest(cx, expr, recv, span);
}
}
fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, types: &[rustc_span::Symbol]) -> bool {
let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs();
types.iter().any(|&ty| is_type_diagnostic_item(cx, expr_ty, ty))
// String type is a lang item but not a diagnostic item for now so we need a separate check
|| is_type_lang_item(cx, expr_ty, LangItem::String)
}
fn suggest(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span) {
if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
// Use `opt_item_name` while `String` is not a diagnostic item
&& let Some(ty_name) = cx.tcx.opt_item_name(adt.did())
{
span_lint_and_sugg(
cx,
CLEAR_WITH_DRAIN,
span.with_hi(expr.span.hi()),
&format!("`drain` used to clear a `{ty_name}`"),
"try",
"clear()".to_string(),
Applicability::MachineApplicable,
);
}
}

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
use clippy_utils::macros::{find_format_args, format_args_inputs_span, root_macro_call_first_node};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use rustc_errors::Applicability;
@ -136,18 +136,19 @@ fn is_call(node: &hir::ExprKind<'_>) -> bool {
if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
return;
}
let Some(format_args) = FormatArgsExpn::find_nested(cx, arg_root, macro_call.expn) else { return };
let span = format_args.inputs_span();
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
span_lint_and_sugg(
cx,
EXPECT_FUN_CALL,
span_replace_word,
&format!("use of `{name}` followed by a function call"),
"try this",
format!("unwrap_or_else({closure_args} panic!({sugg}))"),
applicability,
);
find_format_args(cx, arg_root, macro_call.expn, |format_args| {
let span = format_args_inputs_span(format_args);
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
span_lint_and_sugg(
cx,
EXPECT_FUN_CALL,
span_replace_word,
&format!("use of `{name}` followed by a function call"),
"try this",
format!("unwrap_or_else({closure_args} panic!({sugg}))"),
applicability,
);
});
return;
}

View File

@ -1,7 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::Range;
use clippy_utils::is_integer_const;
use rustc_ast::ast::RangeLimits;
use clippy_utils::is_range_full;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::LateContext;
@ -15,8 +13,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span
&& let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
&& let Some(ty_name) = cx.tcx.get_diagnostic_name(adt.did())
&& matches!(ty_name, sym::Vec | sym::VecDeque)
&& let Some(range) = Range::hir(arg)
&& is_full_range(cx, recv, range)
&& let ExprKind::Path(QPath::Resolved(None, container_path)) = recv.kind
&& is_range_full(cx, arg, Some(container_path))
{
span_lint_and_sugg(
cx,
@ -29,19 +27,3 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span
);
};
}
fn is_full_range(cx: &LateContext<'_>, container: &Expr<'_>, range: Range<'_>) -> bool {
range.start.map_or(true, |e| is_integer_const(cx, e, 0))
&& range.end.map_or(true, |e| {
if range.limits == RangeLimits::HalfOpen
&& let ExprKind::Path(QPath::Resolved(None, container_path)) = container.kind
&& let ExprKind::MethodCall(name, self_arg, [], _) = e.kind
&& name.ident.name == sym::len
&& let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind
{
container_path.res == path.res
} else {
false
}
})
}

View File

@ -9,6 +9,7 @@
mod chars_last_cmp_with_unwrap;
mod chars_next_cmp;
mod chars_next_cmp_with_unwrap;
mod clear_with_drain;
mod clone_on_copy;
mod clone_on_ref_ptr;
mod cloned_instead_of_copied;
@ -110,7 +111,7 @@
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, return_ty};
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_hir::{Expr, ExprKind, TraitItem, TraitItemKind};
use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
@ -3190,6 +3191,31 @@
"single command line argument that looks like it should be multiple arguments"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `.drain(..)` for the sole purpose of clearing a container.
///
/// ### Why is this bad?
/// This creates an unnecessary iterator that is dropped immediately.
///
/// Calling `.clear()` also makes the intent clearer.
///
/// ### Example
/// ```rust
/// let mut v = vec![1, 2, 3];
/// v.drain(..);
/// ```
/// Use instead:
/// ```rust
/// let mut v = vec![1, 2, 3];
/// v.clear();
/// ```
#[clippy::version = "1.69.0"]
pub CLEAR_WITH_DRAIN,
nursery,
"calling `drain` in order to `clear` a container"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
@ -3318,6 +3344,7 @@ pub fn new(
SEEK_TO_START_INSTEAD_OF_REWIND,
NEEDLESS_COLLECT,
SUSPICIOUS_COMMAND_ARG_SPACE,
CLEAR_WITH_DRAIN,
]);
/// Extracts a method call name, args, and `Span` of the method name.
@ -3562,8 +3589,15 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
Some(("bytes", recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2),
_ => {},
},
("drain", [arg]) => {
iter_with_drain::check(cx, expr, recv, span, arg);
("drain", ..) => {
if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.hir().get_parent(expr.hir_id)
&& matches!(kind, StmtKind::Semi(_))
&& args.len() <= 1
{
clear_with_drain::check(cx, expr, recv, span, args.first());
} else if let [arg] = args {
iter_with_drain::check(cx, expr, recv, span, arg);
}
},
("ends_with", [arg]) => {
if let ExprKind::MethodCall(.., span) = expr.kind {

View File

@ -41,6 +41,7 @@
/// can't be const as it calls a non-const function. Making `a` const and running Clippy again,
/// will suggest to make `b` const, too.
///
/// If you are marking a public function with `const`, removing it again will break API compatibility.
/// ### Example
/// ```rust
/// # struct Foo {

View File

@ -154,10 +154,18 @@ fn manage_bin_ops<'tcx>(
Self::literal_integer(cx, actual_rhs),
) {
(None, None) => false,
(None, Some(n)) | (Some(n), None) => match (&op.node, n) {
(None, Some(n)) => match (&op.node, n) {
// Division and module are always valid if applied to non-zero integers
(hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => true,
// Addition or subtracting zeros is always a no-op
// Adding or subtracting zeros is always a no-op
(hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
// Multiplication by 1 or 0 will never overflow
| (hir::BinOpKind::Mul, 0 | 1)
=> true,
_ => false,
},
(Some(n), None) => match (&op.node, n) {
// Adding or subtracting zeros is always a no-op
(hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
// Multiplication by 1 or 0 will never overflow
| (hir::BinOpKind::Mul, 0 | 1)

View File

@ -1,8 +1,15 @@
use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet};
use rustc_ast::ast::{Expr, ExprKind, Stmt, StmtKind};
use rustc_ast::visit::Visitor as AstVisitor;
use std::ops::ControlFlow;
use clippy_utils::{
diagnostics::span_lint_and_sugg,
peel_blocks,
source::{snippet, walk_span_to_context},
visitors::for_each_expr,
};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_hir::{AsyncGeneratorKind, Closure, Expr, ExprKind, GeneratorKind, MatchSource};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::{lint::in_external_macro, ty::UpvarCapture};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
@ -14,106 +21,88 @@
///
/// ### Example
/// ```rust
/// async fn f() -> i32 {
/// 1 + 2
/// }
///
/// let f = async {
/// 1 + 2
/// };
/// let fut = async {
/// f().await
/// f.await
/// };
/// ```
/// Use instead:
/// ```rust
/// async fn f() -> i32 {
/// 1 + 2
/// }
///
/// let fut = f();
/// let f = async {
/// 1 + 2
/// };
/// let fut = f;
/// ```
#[clippy::version = "1.69.0"]
pub REDUNDANT_ASYNC_BLOCK,
nursery,
complexity,
"`async { future.await }` can be replaced by `future`"
}
declare_lint_pass!(RedundantAsyncBlock => [REDUNDANT_ASYNC_BLOCK]);
impl EarlyLintPass for RedundantAsyncBlock {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if expr.span.from_expansion() {
return;
}
if let ExprKind::Async(_, block) = &expr.kind && block.stmts.len() == 1 &&
let Some(Stmt { kind: StmtKind::Expr(last), .. }) = block.stmts.last() &&
let ExprKind::Await(future) = &last.kind &&
!future.span.from_expansion() &&
!await_in_expr(future)
impl<'tcx> LateLintPass<'tcx> for RedundantAsyncBlock {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let span = expr.span;
if !in_external_macro(cx.tcx.sess, span) &&
let Some(body_expr) = desugar_async_block(cx, expr) &&
let Some(expr) = desugar_await(peel_blocks(body_expr)) &&
// The await prefix must not come from a macro as its content could change in the future.
expr.span.ctxt() == body_expr.span.ctxt() &&
// An async block does not have immediate side-effects from a `.await` point-of-view.
(!expr.can_have_side_effects() || desugar_async_block(cx, expr).is_some()) &&
let Some(shortened_span) = walk_span_to_context(expr.span, span.ctxt())
{
if captures_value(last) {
// If the async block captures variables then there is no equivalence.
return;
}
span_lint_and_sugg(
cx,
REDUNDANT_ASYNC_BLOCK,
expr.span,
span,
"this async expression only awaits a single future",
"you can reduce it to",
snippet(cx, future.span, "..").into_owned(),
snippet(cx, shortened_span, "..").into_owned(),
Applicability::MachineApplicable,
);
}
}
}
/// Check whether an expression contains `.await`
fn await_in_expr(expr: &Expr) -> bool {
let mut detector = AwaitDetector::default();
detector.visit_expr(expr);
detector.await_found
}
#[derive(Default)]
struct AwaitDetector {
await_found: bool,
}
impl<'ast> AstVisitor<'ast> for AwaitDetector {
fn visit_expr(&mut self, ex: &'ast Expr) {
match (&ex.kind, self.await_found) {
(ExprKind::Await(_), _) => self.await_found = true,
(_, false) => rustc_ast::visit::walk_expr(self, ex),
_ => (),
}
/// If `expr` is a desugared `async` block, return the original expression if it does not capture
/// any variable by ref.
fn desugar_async_block<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if let ExprKind::Closure(Closure { body, def_id, .. }) = expr.kind &&
let body = cx.tcx.hir().body(*body) &&
matches!(body.generator_kind, Some(GeneratorKind::Async(AsyncGeneratorKind::Block)))
{
cx
.typeck_results()
.closure_min_captures
.get(def_id)
.map_or(true, |m| {
m.values().all(|places| {
places
.iter()
.all(|place| matches!(place.info.capture_kind, UpvarCapture::ByValue))
})
})
.then_some(body.value)
} else {
None
}
}
/// Check whether an expression may have captured a local variable.
/// This is done by looking for paths with only one segment, except as
/// a prefix of `.await` since this would be captured by value.
///
/// This function will sometimes return `true` even tough there are no
/// captures happening: at the AST level, it is impossible to
/// dinstinguish a function call from a call to a closure which comes
/// from the local environment.
fn captures_value(expr: &Expr) -> bool {
let mut detector = CaptureDetector::default();
detector.visit_expr(expr);
detector.capture_found
}
#[derive(Default)]
struct CaptureDetector {
capture_found: bool,
}
impl<'ast> AstVisitor<'ast> for CaptureDetector {
fn visit_expr(&mut self, ex: &'ast Expr) {
match (&ex.kind, self.capture_found) {
(ExprKind::Await(fut), _) if matches!(fut.kind, ExprKind::Path(..)) => (),
(ExprKind::Path(_, path), _) if path.segments.len() == 1 => self.capture_found = true,
(_, false) => rustc_ast::visit::walk_expr(self, ex),
_ => (),
}
/// If `expr` is a desugared `.await`, return the original expression if it does not come from a
/// macro expansion.
fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if let ExprKind::Match(match_value, _, MatchSource::AwaitDesugar) = expr.kind &&
let ExprKind::Call(_, [into_future_arg]) = match_value.kind &&
let ctxt = expr.span.ctxt() &&
for_each_expr(into_future_arg, |e|
walk_span_to_context(e.span, ctxt)
.map_or(ControlFlow::Break(()), |_| ControlFlow::Continue(()))).is_none()
{
Some(into_future_arg)
} else {
None
}
}

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet;
use rustc_ast::ast::{Item, ItemKind, Ty, TyKind, StaticItem, ConstItem};
use rustc_ast::ast::{ConstItem, Item, ItemKind, StaticItem, Ty, TyKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
@ -106,7 +106,7 @@ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
// #2438)
}
if let ItemKind::Static(box StaticItem { ty: ref var_type,.. }) = item.kind {
if let ItemKind::Static(box StaticItem { ty: ref var_type, .. }) = item.kind {
Self::visit_type(var_type, cx, "statics have by default a `'static` lifetime");
}
}

View File

@ -9,7 +9,7 @@
use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, LangItem, MatchSource, PatKind, QPath, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::def_id::LocalDefId;
use rustc_span::source_map::Span;
@ -175,7 +175,7 @@ fn check_fn(
} else {
RetReplacement::Empty
};
check_final_expr(cx, body.value, vec![], replacement);
check_final_expr(cx, body.value, vec![], replacement, None);
},
FnKind::ItemFn(..) | FnKind::Method(..) => {
check_block_return(cx, &body.value.kind, sp, vec![]);
@ -188,11 +188,11 @@ fn check_fn(
fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, sp: Span, mut semi_spans: Vec<Span>) {
if let ExprKind::Block(block, _) = expr_kind {
if let Some(block_expr) = block.expr {
check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty);
check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty, None);
} else if let Some(stmt) = block.stmts.iter().last() {
match stmt.kind {
StmtKind::Expr(expr) => {
check_final_expr(cx, expr, semi_spans, RetReplacement::Empty);
check_final_expr(cx, expr, semi_spans, RetReplacement::Empty, None);
},
StmtKind::Semi(semi_expr) => {
// Remove ending semicolons and any whitespace ' ' in between.
@ -202,7 +202,7 @@ fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>,
span_find_starting_semi(cx.sess().source_map(), semi_span.with_hi(sp.hi()));
semi_spans.push(semi_span_to_remove);
}
check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty);
check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty, None);
},
_ => (),
}
@ -216,6 +216,7 @@ fn check_final_expr<'tcx>(
semi_spans: Vec<Span>, /* containing all the places where we would need to remove semicolons if finding an
* needless return */
replacement: RetReplacement<'tcx>,
match_ty_opt: Option<Ty<'_>>,
) {
let peeled_drop_expr = expr.peel_drop_temps();
match &peeled_drop_expr.kind {
@ -244,7 +245,22 @@ fn check_final_expr<'tcx>(
RetReplacement::Expr(snippet, applicability)
}
} else {
replacement
match match_ty_opt {
Some(match_ty) => {
match match_ty.kind() {
// If the code got till here with
// tuple not getting detected before it,
// then we are sure it's going to be Unit
// type
ty::Tuple(_) => RetReplacement::Unit,
// We don't want to anything in this case
// cause we can't predict what the user would
// want here
_ => return,
}
},
None => replacement,
}
};
if !cx.tcx.hir().attrs(expr.hir_id).is_empty() {
@ -268,8 +284,9 @@ fn check_final_expr<'tcx>(
// note, if without else is going to be a type checking error anyways
// (except for unit type functions) so we don't match it
ExprKind::Match(_, arms, MatchSource::Normal) => {
let match_ty = cx.typeck_results().expr_ty(peeled_drop_expr);
for arm in arms.iter() {
check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit);
check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit, Some(match_ty));
}
},
// if it's a whole block, check it
@ -293,6 +310,7 @@ fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, semi_spans: Vec<Span>,
if ret_span.from_expansion() {
return;
}
let applicability = replacement.applicability().unwrap_or(Applicability::MachineApplicable);
let return_replacement = replacement.to_string();
let sugg_help = replacement.sugg_help();

View File

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use rustc_ast::node_id::{NodeId, NodeMap};
use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind};
use rustc_ast::visit::{walk_expr, Visitor};
use rustc_ast::{ptr::P, Crate, Expr, ExprKind, Item, ItemKind, MacroDef, ModKind, Ty, TyKind, UseTreeKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_session::{declare_tool_lint, impl_lint_pass};
@ -55,7 +56,7 @@ fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) {
return;
}
self.check_mod(cx, &krate.items);
self.check_mod(&krate.items);
}
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
@ -84,8 +85,43 @@ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
}
}
#[derive(Default)]
struct ImportUsageVisitor {
// keep track of imports reused with `self` keyword, such as `self::std` in the example below.
// Removing the `use std;` would make this a compile error (#10549)
// ```
// use std;
//
// fn main() {
// let _ = self::std::io::stdout();
// }
// ```
imports_referenced_with_self: Vec<Symbol>,
}
impl<'tcx> Visitor<'tcx> for ImportUsageVisitor {
fn visit_expr(&mut self, expr: &Expr) {
if let ExprKind::Path(_, path) = &expr.kind
&& path.segments.len() > 1
&& path.segments[0].ident.name == kw::SelfLower
{
self.imports_referenced_with_self.push(path.segments[1].ident.name);
}
walk_expr(self, expr);
}
fn visit_ty(&mut self, ty: &Ty) {
if let TyKind::Path(_, path) = &ty.kind
&& path.segments.len() > 1
&& path.segments[0].ident.name == kw::SelfLower
{
self.imports_referenced_with_self.push(path.segments[1].ident.name);
}
}
}
impl SingleComponentPathImports {
fn check_mod(&mut self, cx: &EarlyContext<'_>, items: &[P<Item>]) {
fn check_mod(&mut self, items: &[P<Item>]) {
// keep track of imports reused with `self` keyword, such as `self::crypto_hash` in the example
// below. Removing the `use crypto_hash;` would make this a compile error
// ```
@ -108,18 +144,16 @@ fn check_mod(&mut self, cx: &EarlyContext<'_>, items: &[P<Item>]) {
// ```
let mut macros = Vec::new();
let mut import_usage_visitor = ImportUsageVisitor::default();
for item in items {
self.track_uses(
cx,
item,
&mut imports_reused_with_self,
&mut single_use_usages,
&mut macros,
);
self.track_uses(item, &mut imports_reused_with_self, &mut single_use_usages, &mut macros);
import_usage_visitor.visit_item(item);
}
for usage in single_use_usages {
if !imports_reused_with_self.contains(&usage.name) {
if !imports_reused_with_self.contains(&usage.name)
&& !import_usage_visitor.imports_referenced_with_self.contains(&usage.name)
{
self.found.entry(usage.item_id).or_default().push(usage);
}
}
@ -127,7 +161,6 @@ fn check_mod(&mut self, cx: &EarlyContext<'_>, items: &[P<Item>]) {
fn track_uses(
&mut self,
cx: &EarlyContext<'_>,
item: &Item,
imports_reused_with_self: &mut Vec<Symbol>,
single_use_usages: &mut Vec<SingleUse>,
@ -139,7 +172,7 @@ fn track_uses(
match &item.kind {
ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => {
self.check_mod(cx, items);
self.check_mod(items);
},
ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => {
macros.push(item.ident.name);

View File

@ -0,0 +1,94 @@
use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_then};
use if_chain::if_chain;
use rustc_ast::{token::CommentKind, AttrKind, AttrStyle, Attribute, Item};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Detects the use of outer doc comments (`///`, `/**`) followed by a bang (`!`): `///!`
///
/// ### Why is this bad?
/// Triple-slash comments (known as "outer doc comments") apply to items that follow it.
/// An outer doc comment followed by a bang (i.e. `///!`) has no specific meaning.
///
/// The user most likely meant to write an inner doc comment (`//!`, `/*!`), which
/// applies to the parent item (i.e. the item that the comment is contained in,
/// usually a module or crate).
///
/// ### Known problems
/// Inner doc comments can only appear before items, so there are certain cases where the suggestion
/// made by this lint is not valid code. For example:
/// ```rs
/// fn foo() {}
/// ///!
/// fn bar() {}
/// ```
/// This lint detects the doc comment and suggests changing it to `//!`, but an inner doc comment
/// is not valid at that position.
///
/// ### Example
/// In this example, the doc comment is attached to the *function*, rather than the *module*.
/// ```rust
/// pub mod util {
/// ///! This module contains utility functions.
///
/// pub fn dummy() {}
/// }
/// ```
///
/// Use instead:
/// ```rust
/// pub mod util {
/// //! This module contains utility functions.
///
/// pub fn dummy() {}
/// }
/// ```
#[clippy::version = "1.70.0"]
pub SUSPICIOUS_DOC_COMMENTS,
suspicious,
"suspicious usage of (outer) doc comments"
}
declare_lint_pass!(SuspiciousDocComments => [SUSPICIOUS_DOC_COMMENTS]);
const WARNING: &str = "this is an outer doc comment and does not apply to the parent module or crate";
const HELP: &str = "use an inner doc comment to document the parent module or crate";
impl EarlyLintPass for SuspiciousDocComments {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
let replacements = collect_doc_comment_replacements(&item.attrs);
if let Some(((lo_span, _), (hi_span, _))) = replacements.first().zip(replacements.last()) {
let span = lo_span.to(*hi_span);
span_lint_and_then(cx, SUSPICIOUS_DOC_COMMENTS, span, WARNING, |diag| {
multispan_sugg_with_applicability(diag, HELP, Applicability::MaybeIncorrect, replacements);
});
}
}
}
fn collect_doc_comment_replacements(attrs: &[Attribute]) -> Vec<(Span, String)> {
attrs
.iter()
.filter_map(|attr| {
if_chain! {
if let AttrKind::DocComment(com_kind, sym) = attr.kind;
if let AttrStyle::Outer = attr.style;
if let Some(com) = sym.as_str().strip_prefix('!');
then {
let sugg = match com_kind {
CommentKind::Line => format!("//!{com}"),
CommentKind::Block => format!("/*!{com}*/")
};
Some((attr.span, sugg))
} else {
None
}
}
})
.collect()
}

View File

@ -0,0 +1,71 @@
use clippy_utils::{diagnostics::span_lint_and_note, is_in_cfg_test, is_in_test_function};
use rustc_hir::{intravisit::FnKind, Body, FnDecl};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{def_id::LocalDefId, Span};
declare_clippy_lint! {
/// ### What it does
/// Triggers when a testing function (marked with the `#[test]` attribute) isn't inside a testing module
/// (marked with `#[cfg(test)]`).
/// ### Why is this bad?
/// The idiomatic (and more performant) way of writing tests is inside a testing module (flagged with `#[cfg(test)]`),
/// having test functions outside of this module is confusing and may lead to them being "hidden".
/// ### Example
/// ```rust
/// #[test]
/// fn my_cool_test() {
/// // [...]
/// }
///
/// #[cfg(test)]
/// mod tests {
/// // [...]
/// }
///
/// ```
/// Use instead:
/// ```rust
/// #[cfg(test)]
/// mod tests {
/// #[test]
/// fn my_cool_test() {
/// // [...]
/// }
/// }
/// ```
#[clippy::version = "1.70.0"]
pub TESTS_OUTSIDE_TEST_MODULE,
restriction,
"A test function is outside the testing module."
}
declare_lint_pass!(TestsOutsideTestModule => [TESTS_OUTSIDE_TEST_MODULE]);
impl LateLintPass<'_> for TestsOutsideTestModule {
fn check_fn(
&mut self,
cx: &LateContext<'_>,
kind: FnKind<'_>,
_: &FnDecl<'_>,
body: &Body<'_>,
sp: Span,
_: LocalDefId,
) {
if_chain! {
if !matches!(kind, FnKind::Closure);
if is_in_test_function(cx.tcx, body.id().hir_id);
if !is_in_cfg_test(cx.tcx, body.id().hir_id);
then {
span_lint_and_note(
cx,
TESTS_OUTSIDE_TEST_MODULE,
sp,
"this function marked with #[test] is outside a #[cfg(test)] module",
None,
"move it to a testing module marked with #[cfg(test)]",
);
}
}
}
}

View File

@ -2,8 +2,9 @@
use super::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use rustc_ast::ExprPrecedence;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_hir::{Expr, Node};
use rustc_lint::LateContext;
use rustc_middle::ty::{cast::CastKind, Ty};
@ -19,7 +20,7 @@ pub(super) fn check<'tcx>(
) -> bool {
use CastKind::{AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast};
let mut app = Applicability::MachineApplicable;
let sugg = match check_cast(cx, e, from_ty, to_ty) {
let mut sugg = match check_cast(cx, e, from_ty, to_ty) {
Some(PtrPtrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast) => {
Sugg::hir_with_context(cx, arg, e.span.ctxt(), "..", &mut app)
.as_ty(to_ty.to_string())
@ -39,6 +40,12 @@ pub(super) fn check<'tcx>(
_ => return false,
};
if let Node::Expr(parent) = cx.tcx.hir().get_parent(e.hir_id)
&& parent.precedence().order() > ExprPrecedence::Cast.order()
{
sugg = format!("({sugg})");
}
span_lint_and_sugg(
cx,
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,

View File

@ -0,0 +1,120 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_errors::Applicability;
use rustc_hir::{def_id::LocalDefId, FnDecl, FnRetTy, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Symbol;
declare_clippy_lint! {
/// ### What it does
///
/// Checks for a return type containing a `Box<T>` where `T` implements `Sized`
///
/// ### Why is this bad?
///
/// It's better to just return `T` in these cases. The caller may not need
/// the value to be boxed, and it's expensive to free the memory once the
/// `Box<T>` been dropped.
///
/// ### Example
/// ```rust
/// fn foo() -> Box<String> {
/// Box::new(String::from("Hello, world!"))
/// }
/// ```
/// Use instead:
/// ```rust
/// fn foo() -> String {
/// String::from("Hello, world!")
/// }
/// ```
#[clippy::version = "1.70.0"]
pub UNNECESSARY_BOX_RETURNS,
pedantic,
"Needlessly returning a Box"
}
pub struct UnnecessaryBoxReturns {
avoid_breaking_exported_api: bool,
}
impl_lint_pass!(UnnecessaryBoxReturns => [UNNECESSARY_BOX_RETURNS]);
impl UnnecessaryBoxReturns {
pub fn new(avoid_breaking_exported_api: bool) -> Self {
Self {
avoid_breaking_exported_api,
}
}
fn check_fn_item(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId, name: Symbol) {
// we don't want to tell someone to break an exported function if they ask us not to
if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) {
return;
}
// functions which contain the word "box" are exempt from this lint
if name.as_str().contains("box") {
return;
}
let FnRetTy::Return(return_ty_hir) = &decl.output else { return };
let return_ty = cx
.tcx
.erase_late_bound_regions(cx.tcx.fn_sig(def_id).skip_binder())
.output();
if !return_ty.is_box() {
return;
}
let boxed_ty = return_ty.boxed_ty();
// it's sometimes useful to return Box<T> if T is unsized, so don't lint those
if boxed_ty.is_sized(cx.tcx, cx.param_env) {
span_lint_and_then(
cx,
UNNECESSARY_BOX_RETURNS,
return_ty_hir.span,
format!("boxed return of the sized type `{boxed_ty}`").as_str(),
|diagnostic| {
diagnostic.span_suggestion(
return_ty_hir.span,
"try",
boxed_ty.to_string(),
// the return value and function callers also needs to
// be changed, so this can't be MachineApplicable
Applicability::Unspecified,
);
diagnostic.help("changing this also requires a change to the return expressions in this function");
},
);
}
}
}
impl LateLintPass<'_> for UnnecessaryBoxReturns {
fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) {
let TraitItemKind::Fn(signature, _) = &item.kind else { return };
self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name);
}
fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::ImplItem<'_>) {
// Ignore implementations of traits, because the lint should be on the
// trait, not on the implmentation of it.
let Node::Item(parent) = cx.tcx.hir().get_parent(item.hir_id()) else { return };
let ItemKind::Impl(parent) = parent.kind else { return };
if parent.of_trait.is_some() {
return;
}
let ImplItemKind::Fn(signature, ..) = &item.kind else { return };
self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name);
}
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
let ItemKind::Fn(signature, ..) = &item.kind else { return };
self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name);
}
}

View File

@ -9,7 +9,7 @@
/// any field.
///
/// ### Why is this bad?
/// Readibility suffers from unnecessary struct building.
/// Readability suffers from unnecessary struct building.
///
/// ### Example
/// ```rust
@ -25,9 +25,13 @@
/// let a = S { s: String::from("Hello, world!") };
/// let b = a;
/// ```
///
/// ### Known Problems
/// Has false positives when the base is a place expression that cannot be
/// moved out of, see [#10547](https://github.com/rust-lang/rust-clippy/issues/10547).
#[clippy::version = "1.70.0"]
pub UNNECESSARY_STRUCT_INITIALIZATION,
complexity,
nursery,
"struct built from a base that can be written mode concisely"
}
declare_lint_pass!(UnnecessaryStruct => [UNNECESSARY_STRUCT_INITIALIZATION]);

View File

@ -10,8 +10,8 @@
def::{CtorOf, DefKind, Res},
def_id::LocalDefId,
intravisit::{walk_inf, walk_ty, Visitor},
Expr, ExprKind, FnRetTy, FnSig, GenericArg, GenericArgsParentheses, GenericParam, GenericParamKind, HirId, Impl, ImplItemKind, Item,
ItemKind, Pat, PatKind, Path, QPath, Ty, TyKind,
Expr, ExprKind, FnRetTy, FnSig, GenericArg, GenericArgsParentheses, GenericParam, GenericParamKind, HirId, Impl,
ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath, Ty, TyKind,
};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};

View File

@ -249,7 +249,7 @@ pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
/// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"]
/// ```
(arithmetic_side_effects_allowed_unary: rustc_data_structures::fx::FxHashSet<String> = <_>::default()),
/// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX.
/// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS.
///
/// Suppress lints whenever the suggested change would cause breakage for other crates.
(avoid_breaking_exported_api: bool = true),
@ -275,13 +275,13 @@ pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
///
/// 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 configuration of Clippy. By default, any configuration will replace the default value.
(disallowed_names: Vec<String> = super::DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect()),
/// Lint: DOC_MARKDOWN.
///
/// The list of words this lint should not consider as identifiers needing ticks. The value
/// `".."` can be used as part of the list to indicate, that the configured values should be appended to the
/// default configuration of Clippy. By default any configuraction will replace the default value. For example:
/// 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 append `ClipPy` to the default list.
///
@ -390,7 +390,7 @@ pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
/// Enforce the named macros always use the braces specified.
///
/// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
/// is could be used with a full path two `MacroMatcher`s have to be added one with the full path
/// could be used with a full path two `MacroMatcher`s have to be added one with the full path
/// `crate_name::macro_name` and one with just the macro name.
(standard_macro_braces: Vec<crate::nonstandard_macro_braces::MacroMatcher> = Vec::new()),
/// Lint: MISSING_ENFORCED_IMPORT_RENAMES.
@ -408,7 +408,7 @@ pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
/// Lint: INDEX_REFUTABLE_SLICE.
///
/// 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 would be necessary, the lint is suppressed.
/// 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.
(max_suggested_slice_pattern_length: u64 = 3),
/// Lint: AWAIT_HOLDING_INVALID_TYPE.
@ -459,6 +459,10 @@ pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
/// Whether to **only** check for missing documentation in items visible within the current
/// crate. For example, `pub(crate)` items.
(missing_docs_in_crate_items: bool = false),
/// Lint: LARGE_FUTURES.
///
/// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
(future_size_threshold: u64 = 16 * 1024),
}
/// Search for the configuration file.
@ -466,7 +470,7 @@ pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
/// # Errors
///
/// Returns any unexpected filesystem error encountered when searching for the config file
pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
/// Possible filename to search for.
const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
@ -474,9 +478,11 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
// If neither of those exist, use ".".
let mut current = env::var_os("CLIPPY_CONF_DIR")
.or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
.map_or_else(|| PathBuf::from("."), PathBuf::from);
.map_or_else(|| PathBuf::from("."), PathBuf::from)
.canonicalize()?;
let mut found_config: Option<PathBuf> = None;
let mut warnings = vec![];
loop {
for config_file_name in &CONFIG_FILE_NAMES {
@ -487,12 +493,12 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
Ok(md) if md.is_dir() => {},
Ok(_) => {
// warn if we happen to find two config files #8323
if let Some(ref found_config_) = found_config {
eprintln!(
"Using config file `{}`\nWarning: `{}` will be ignored.",
found_config_.display(),
config_file.display(),
);
if let Some(ref found_config) = found_config {
warnings.push(format!(
"using config file `{}`, `{}` will be ignored",
found_config.display(),
config_file.display()
));
} else {
found_config = Some(config_file);
}
@ -502,12 +508,12 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
}
if found_config.is_some() {
return Ok(found_config);
return Ok((found_config, warnings));
}
// If the current directory has no parent, we're done searching.
if !current.pop() {
return Ok(None);
return Ok((None, warnings));
}
}
}

View File

@ -1,7 +1,12 @@
use clippy_utils::macros::collect_ast_format_args;
use rustc_ast::{Expr, ExprKind};
use clippy_utils::source::snippet_opt;
use itertools::Itertools;
use rustc_ast::{Expr, ExprKind, FormatArgs};
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::hygiene;
use std::iter::once;
declare_clippy_lint! {
/// ### What it does
@ -15,9 +20,79 @@
declare_lint_pass!(FormatArgsCollector => [FORMAT_ARGS_COLLECTOR]);
impl EarlyLintPass for FormatArgsCollector {
fn check_expr(&mut self, _: &EarlyContext<'_>, expr: &Expr) {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if let ExprKind::FormatArgs(args) = &expr.kind {
if has_span_from_proc_macro(cx, args) {
return;
}
collect_ast_format_args(expr.span, args);
}
}
}
/// Detects if the format string or an argument has its span set by a proc macro to something inside
/// a macro callsite, e.g.
///
/// ```ignore
/// println!(some_proc_macro!("input {}"), a);
/// ```
///
/// Where `some_proc_macro` expands to
///
/// ```ignore
/// println!("output {}", a);
/// ```
///
/// But with the span of `"output {}"` set to the macro input
///
/// ```ignore
/// println!(some_proc_macro!("input {}"), a);
/// // ^^^^^^^^^^
/// ```
fn has_span_from_proc_macro(cx: &EarlyContext<'_>, args: &FormatArgs) -> bool {
let ctxt = args.span.ctxt();
// `format!("{} {} {c}", "one", "two", c = "three")`
// ^^^^^ ^^^^^ ^^^^^^^
let argument_span = args
.arguments
.explicit_args()
.iter()
.map(|argument| hygiene::walk_chain(argument.expr.span, ctxt));
// `format!("{} {} {c}", "one", "two", c = "three")`
// ^^ ^^ ^^^^^^
let between_spans = once(args.span)
.chain(argument_span)
.tuple_windows()
.map(|(start, end)| start.between(end));
for between_span in between_spans {
let mut seen_comma = false;
let Some(snippet) = snippet_opt(cx, between_span) else { return true };
for token in tokenize(&snippet) {
match token.kind {
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {},
TokenKind::Comma if !seen_comma => seen_comma = true,
// named arguments, `start_val, name = end_val`
// ^^^^^^^^^ between_span
TokenKind::Ident | TokenKind::Eq if seen_comma => {},
// An unexpected token usually indicates that we crossed a macro boundary
//
// `println!(some_proc_macro!("input {}"), a)`
// ^^^ between_span
// `println!("{}", val!(x))`
// ^^^^^^^ between_span
_ => return true,
}
}
if !seen_comma {
return true;
}
}
false
}

View File

@ -463,12 +463,18 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
&& let Some(value_string) = snippet_opt(cx, arg.expr.span)
{
let (replacement, replace_raw) = match lit.kind {
LitKind::Str | LitKind::StrRaw(_) => extract_str_literal(&value_string),
LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) {
Some(extracted) => extracted,
None => return,
},
LitKind::Char => (
match lit.symbol.as_str() {
"\"" => "\\\"",
"\\'" => "'",
_ => &value_string[1..value_string.len() - 1],
_ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
Some(stripped) => stripped,
None => return,
},
}
.to_string(),
false,
@ -533,13 +539,13 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
/// `r#"a"#` -> (`a`, true)
///
/// `"b"` -> (`b`, false)
fn extract_str_literal(literal: &str) -> (String, bool) {
fn extract_str_literal(literal: &str) -> Option<(String, bool)> {
let (literal, raw) = match literal.strip_prefix('r') {
Some(stripped) => (stripped.trim_matches('#'), true),
None => (literal, false),
};
(literal[1..literal.len() - 1].to_string(), raw)
Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw))
}
enum UnescapeErr {

View File

@ -286,8 +286,30 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
match (l, r) {
(ExternCrate(l), ExternCrate(r)) => l == r,
(Use(l), Use(r)) => eq_use_tree(l, r),
(Static(box ast::StaticItem { ty: lt, mutability: lm, expr: le}), Static(box ast::StaticItem { ty: rt, mutability: rm, expr: re})) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Const(box ast::ConstItem { defaultness: ld, ty: lt, expr: le}), Const(box ast::ConstItem { defaultness: rd, ty: rt, expr: re} )) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(
Static(box ast::StaticItem {
ty: lt,
mutability: lm,
expr: le,
}),
Static(box ast::StaticItem {
ty: rt,
mutability: rm,
expr: re,
}),
) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
(
Const(box ast::ConstItem {
defaultness: ld,
ty: lt,
expr: le,
}),
Const(box ast::ConstItem {
defaultness: rd,
ty: rt,
expr: re,
}),
) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(
Fn(box ast::Fn {
defaultness: ld,
@ -451,7 +473,18 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
use AssocItemKind::*;
match (l, r) {
(Const(box ast::ConstItem { defaultness: ld, ty: lt, expr: le}), Const(box ast::ConstItem { defaultness: rd, ty: rt, expr: re})) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(
Const(box ast::ConstItem {
defaultness: ld,
ty: lt,
expr: le,
}),
Const(box ast::ConstItem {
defaultness: rd,
ty: rt,
expr: re,
}),
) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(
Fn(box ast::Fn {
defaultness: ld,

View File

@ -32,7 +32,6 @@
extern crate rustc_lint;
extern crate rustc_middle;
extern crate rustc_mir_dataflow;
extern crate rustc_parse_format;
extern crate rustc_session;
extern crate rustc_span;
extern crate rustc_target;
@ -77,7 +76,7 @@
use std::sync::{Mutex, MutexGuard};
use if_chain::if_chain;
use rustc_ast::ast::{self, LitKind};
use rustc_ast::ast::{self, LitKind, RangeLimits};
use rustc_ast::Attribute;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::unhash::UnhashMap;
@ -95,6 +94,7 @@
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::place::PlaceBase;
use rustc_middle::mir::ConstantKind;
use rustc_middle::ty as rustc_ty;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
use rustc_middle::ty::binding::BindingMode;
@ -113,7 +113,8 @@
use rustc_span::Span;
use rustc_target::abi::Integer;
use crate::consts::{constant, Constant};
use crate::consts::{constant, miri_to_const, Constant};
use crate::higher::Range;
use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param};
use crate::visitors::for_each_expr;
@ -1490,6 +1491,68 @@ pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
}
}
/// Checks whether the given `Expr` is a range equivalent to a `RangeFull`.
/// For the lower bound, this means that:
/// - either there is none
/// - or it is the smallest value that can be represented by the range's integer type
/// For the upper bound, this means that:
/// - either there is none
/// - or it is the largest value that can be represented by the range's integer type and is
/// inclusive
/// - or it is a call to some container's `len` method and is exclusive, and the range is passed to
/// a method call on that same container (e.g. `v.drain(..v.len())`)
/// If the given `Expr` is not some kind of range, the function returns `false`.
pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Option<&Path<'_>>) -> bool {
let ty = cx.typeck_results().expr_ty(expr);
if let Some(Range { start, end, limits }) = Range::hir(expr) {
let start_is_none_or_min = start.map_or(true, |start| {
if let rustc_ty::Adt(_, subst) = ty.kind()
&& let bnd_ty = subst.type_at(0)
&& let Some(min_val) = bnd_ty.numeric_min_val(cx.tcx)
&& let const_val = cx.tcx.valtree_to_const_val((bnd_ty, min_val.to_valtree()))
&& let min_const_kind = ConstantKind::from_value(const_val, bnd_ty)
&& let Some(min_const) = miri_to_const(cx.tcx, min_const_kind)
&& let Some((start_const, _)) = constant(cx, cx.typeck_results(), start)
{
start_const == min_const
} else {
false
}
});
let end_is_none_or_max = end.map_or(true, |end| {
match limits {
RangeLimits::Closed => {
if let rustc_ty::Adt(_, subst) = ty.kind()
&& let bnd_ty = subst.type_at(0)
&& let Some(max_val) = bnd_ty.numeric_max_val(cx.tcx)
&& let const_val = cx.tcx.valtree_to_const_val((bnd_ty, max_val.to_valtree()))
&& let max_const_kind = ConstantKind::from_value(const_val, bnd_ty)
&& let Some(max_const) = miri_to_const(cx.tcx, max_const_kind)
&& let Some((end_const, _)) = constant(cx, cx.typeck_results(), end)
{
end_const == max_const
} else {
false
}
},
RangeLimits::HalfOpen => {
if let Some(container_path) = container_path
&& let ExprKind::MethodCall(name, self_arg, [], _) = end.kind
&& name.ident.name == sym::len
&& let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind
{
container_path.res == path.res
} else {
false
}
},
}
});
return start_is_none_or_min && end_is_none_or_max;
}
false
}
/// Checks whether the given expression is a constant integer of the given value.
/// unlike `is_integer_literal`, this version does const folding
pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool {
@ -2104,8 +2167,7 @@ pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool {
.filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
traits::impossible_predicates(
cx.tcx,
traits::elaborate(cx.tcx, predicates)
.collect::<Vec<_>>(),
traits::elaborate(cx.tcx, predicates).collect::<Vec<_>>(),
)
}

View File

@ -1,24 +1,16 @@
#![allow(clippy::similar_names)] // `expr` and `expn`
use crate::source::snippet_opt;
use crate::visitors::{for_each_expr, Descend};
use arrayvec::ArrayVec;
use itertools::{izip, Either, Itertools};
use rustc_ast::ast::LitKind;
use rustc_ast::FormatArgs;
use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, LangItem, Node, QPath, TyKind};
use rustc_lexer::unescape::unescape_literal;
use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
use rustc_lint::LateContext;
use rustc_parse_format::{self as rpf, Alignment};
use rustc_span::def_id::DefId;
use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Pos, Span, SpanData, Symbol};
use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, Symbol};
use std::cell::RefCell;
use std::iter::{once, zip};
use std::ops::ControlFlow;
use std::sync::atomic::{AtomicBool, Ordering};
@ -226,11 +218,11 @@ pub enum PanicExpn<'a> {
/// A single argument that implements `Display` - `panic!("{}", object)`
Display(&'a Expr<'a>),
/// Anything else - `panic!("error {}: {}", a, b)`
Format(FormatArgsExpn<'a>),
Format(&'a Expr<'a>),
}
impl<'a> PanicExpn<'a> {
pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> {
pub fn parse(expr: &'a Expr<'a>) -> Option<Self> {
let ExprKind::Call(callee, [arg, rest @ ..]) = &expr.kind else { return None };
let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None };
let result = match path.segments.last().unwrap().ident.as_str() {
@ -240,7 +232,7 @@ pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> {
let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None };
Self::Display(e)
},
"panic_fmt" => Self::Format(FormatArgsExpn::parse(cx, arg)?),
"panic_fmt" => Self::Format(arg),
// Since Rust 1.52, `assert_{eq,ne}` macros expand to use:
// `core::panicking::assert_failed(.., left_val, right_val, None | Some(format_args!(..)));`
"assert_failed" => {
@ -252,7 +244,7 @@ pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> {
// `msg_arg` is either `None` (no custom message) or `Some(format_args!(..))` (custom message)
let msg_arg = &rest[2];
match msg_arg.kind {
ExprKind::Call(_, [fmt_arg]) => Self::Format(FormatArgsExpn::parse(cx, fmt_arg)?),
ExprKind::Call(_, [fmt_arg]) => Self::Format(fmt_arg),
_ => Self::Empty,
}
},
@ -304,7 +296,7 @@ fn find_assert_args_inner<'a, const N: usize>(
let mut args = ArrayVec::new();
let panic_expn = for_each_expr(expr, |e| {
if args.is_full() {
match PanicExpn::parse(cx, e) {
match PanicExpn::parse(e) {
Some(expn) => ControlFlow::Break(expn),
None => ControlFlow::Continue(Descend::Yes),
}
@ -391,30 +383,82 @@ pub fn collect_ast_format_args(span: Span, format_args: &FormatArgs) {
});
}
/// Calls `callback` with an AST [`FormatArgs`] node if one is found
/// Calls `callback` with an AST [`FormatArgs`] node if a `format_args` expansion is found as a
/// descendant of `expn_id`
pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId, callback: impl FnOnce(&FormatArgs)) {
let format_args_expr = for_each_expr(start, |expr| {
let ctxt = expr.span.ctxt();
if ctxt == start.span.ctxt() {
ControlFlow::Continue(Descend::Yes)
} else if ctxt.outer_expn().is_descendant_of(expn_id)
&& macro_backtrace(expr.span)
if ctxt.outer_expn().is_descendant_of(expn_id) {
if macro_backtrace(expr.span)
.map(|macro_call| cx.tcx.item_name(macro_call.def_id))
.any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))
{
ControlFlow::Break(expr)
{
ControlFlow::Break(expr)
} else {
ControlFlow::Continue(Descend::Yes)
}
} else {
ControlFlow::Continue(Descend::No)
}
});
if let Some(format_args_expr) = format_args_expr {
if let Some(expr) = format_args_expr {
AST_FORMAT_ARGS.with(|ast_format_args| {
ast_format_args.borrow().get(&format_args_expr.span).map(callback);
ast_format_args.borrow().get(&expr.span).map(callback);
});
}
}
/// Attempt to find the [`rustc_hir::Expr`] that corresponds to the [`FormatArgument`]'s value, if
/// it cannot be found it will return the [`rustc_ast::Expr`].
pub fn find_format_arg_expr<'hir, 'ast>(
start: &'hir Expr<'hir>,
target: &'ast FormatArgument,
) -> Result<&'hir rustc_hir::Expr<'hir>, &'ast rustc_ast::Expr> {
for_each_expr(start, |expr| {
if expr.span == target.expr.span {
ControlFlow::Break(expr)
} else {
ControlFlow::Continue(())
}
})
.ok_or(&target.expr)
}
/// Span of the `:` and format specifiers
///
/// ```ignore
/// format!("{:.}"), format!("{foo:.}")
/// ^^ ^^
/// ```
pub fn format_placeholder_format_span(placeholder: &FormatPlaceholder) -> Option<Span> {
let base = placeholder.span?.data();
// `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
// brace `{...|}`
Some(Span::new(
placeholder.argument.span?.hi(),
base.hi - BytePos(1),
base.ctxt,
base.parent,
))
}
/// Span covering the format string and values
///
/// ```ignore
/// format("{}.{}", 10, 11)
/// // ^^^^^^^^^^^^^^^
/// ```
pub fn format_args_inputs_span(format_args: &FormatArgs) -> Span {
match format_args.arguments.explicit_args() {
[] => format_args.span,
[.., last] => format_args
.span
.to(hygiene::walk_chain(last.expr.span, format_args.span.ctxt())),
}
}
/// Returns the [`Span`] of the value at `index` extended to the previous comma, e.g. for the value
/// `10`
///
@ -436,251 +480,6 @@ pub fn format_arg_removal_span(format_args: &FormatArgs, index: usize) -> Option
Some(current.with_lo(prev.hi()))
}
/// The format string doesn't exist in the HIR, so we reassemble it from source code
#[derive(Debug)]
pub struct FormatString {
/// Span of the whole format string literal, including `[r#]"`.
pub span: Span,
/// Snippet of the whole format string literal, including `[r#]"`.
pub snippet: String,
/// If the string is raw `r"..."`/`r#""#`, how many `#`s does it have on each side.
pub style: Option<usize>,
/// The unescaped value of the format string, e.g. `"val {}"` for the literal
/// `"val \u{2013} {}"`.
pub unescaped: String,
/// The format string split by format args like `{..}`.
pub parts: Vec<Symbol>,
}
impl FormatString {
fn new(cx: &LateContext<'_>, pieces: &Expr<'_>) -> Option<Self> {
// format_args!(r"a {} b \", 1);
//
// expands to
//
// ::core::fmt::Arguments::new_v1(&["a ", " b \\"],
// &[::core::fmt::ArgumentV1::new_display(&1)]);
//
// where `pieces` is the expression `&["a ", " b \\"]`. It has the span of `r"a {} b \"`
let span = pieces.span;
let snippet = snippet_opt(cx, span)?;
let (inner, style) = match tokenize(&snippet).next()?.kind {
TokenKind::Literal { kind, .. } => {
let style = match kind {
LiteralKind::Str { .. } => None,
LiteralKind::RawStr { n_hashes: Some(n), .. } => Some(n.into()),
_ => return None,
};
let start = style.map_or(1, |n| 2 + n);
let end = snippet.len() - style.map_or(1, |n| 1 + n);
(&snippet[start..end], style)
},
_ => return None,
};
let mode = if style.is_some() {
unescape::Mode::RawStr
} else {
unescape::Mode::Str
};
let mut unescaped = String::with_capacity(inner.len());
// Sometimes the original string comes from a macro which accepts a malformed string, such as in a
// #[display(""somestring)] attribute (accepted by the `displaythis` crate). Reconstructing the
// string from the span will not be possible, so we will just return None here.
let mut unparsable = false;
unescape_literal(inner, mode, &mut |_, ch| match ch {
Ok(ch) => unescaped.push(ch),
Err(e) if !e.is_fatal() => (),
Err(_) => unparsable = true,
});
if unparsable {
return None;
}
let mut parts = Vec::new();
let _: Option<!> = for_each_expr(pieces, |expr| {
if let ExprKind::Lit(lit) = &expr.kind
&& let LitKind::Str(symbol, _) = lit.node
{
parts.push(symbol);
}
ControlFlow::Continue(())
});
Some(Self {
span,
snippet,
style,
unescaped,
parts,
})
}
}
struct FormatArgsValues<'tcx> {
/// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
/// `format!("{x} {} {}", 1, z + 2)`.
value_args: Vec<&'tcx Expr<'tcx>>,
/// Maps an `rt::v1::Argument::position` or an `rt::v1::Count::Param` to its index in
/// `value_args`
pos_to_value_index: Vec<usize>,
/// Used to check if a value is declared inline & to resolve `InnerSpan`s.
format_string_span: SpanData,
}
impl<'tcx> FormatArgsValues<'tcx> {
fn new_empty(format_string_span: SpanData) -> Self {
Self {
value_args: Vec::new(),
pos_to_value_index: Vec::new(),
format_string_span,
}
}
fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self {
let mut pos_to_value_index = Vec::new();
let mut value_args = Vec::new();
let _: Option<!> = for_each_expr(args, |expr| {
if expr.span.ctxt() == args.span.ctxt() {
// ArgumentV1::new_<format_trait>(<val>)
// ArgumentV1::from_usize(<val>)
if let ExprKind::Call(callee, [val]) = expr.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, _)) = callee.kind
&& let TyKind::Path(QPath::LangItem(LangItem::FormatArgument, _, _)) = ty.kind
{
let val_idx = if val.span.ctxt() == expr.span.ctxt()
&& let ExprKind::Field(_, field) = val.kind
&& let Ok(idx) = field.name.as_str().parse()
{
// tuple index
idx
} else {
// assume the value expression is passed directly
pos_to_value_index.len()
};
pos_to_value_index.push(val_idx);
}
ControlFlow::Continue(Descend::Yes)
} else {
// assume that any expr with a differing span is a value
value_args.push(expr);
ControlFlow::Continue(Descend::No)
}
});
Self {
value_args,
pos_to_value_index,
format_string_span,
}
}
}
/// The positions of a format argument's value, precision and width
///
/// A position is an index into the second argument of `Arguments::new_v1[_formatted]`
#[derive(Debug, Default, Copy, Clone)]
struct ParamPosition {
/// The position stored in `rt::v1::Argument::position`.
value: usize,
/// The position stored in `rt::v1::FormatSpec::width` if it is a `Count::Param`.
width: Option<usize>,
/// The position stored in `rt::v1::FormatSpec::precision` if it is a `Count::Param`.
precision: Option<usize>,
}
impl<'tcx> Visitor<'tcx> for ParamPosition {
fn visit_expr_field(&mut self, field: &'tcx ExprField<'tcx>) {
match field.ident.name {
sym::position => {
if let ExprKind::Lit(lit) = &field.expr.kind
&& let LitKind::Int(pos, _) = lit.node
{
self.value = pos as usize;
}
},
sym::precision => {
self.precision = parse_count(field.expr);
},
sym::width => {
self.width = parse_count(field.expr);
},
_ => walk_expr(self, field.expr),
}
}
}
fn parse_count(expr: &Expr<'_>) -> Option<usize> {
// <::core::fmt::rt::v1::Count>::Param(1usize),
if let ExprKind::Call(ctor, [val]) = expr.kind
&& let ExprKind::Path(QPath::TypeRelative(_, path)) = ctor.kind
&& path.ident.name == sym::Param
&& let ExprKind::Lit(lit) = &val.kind
&& let LitKind::Int(pos, _) = lit.node
{
Some(pos as usize)
} else {
None
}
}
/// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)`
fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option<impl Iterator<Item = ParamPosition> + 'tcx> {
if let ExprKind::AddrOf(.., array) = fmt_arg.kind
&& let ExprKind::Array(specs) = array.kind
{
Some(specs.iter().map(|spec| {
if let ExprKind::Call(f, args) = spec.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, f)) = f.kind
&& let TyKind::Path(QPath::LangItem(LangItem::FormatPlaceholder, _, _)) = ty.kind
&& f.ident.name == sym::new
&& let [position, _fill, _align, _flags, precision, width] = args
&& let ExprKind::Lit(position) = &position.kind
&& let LitKind::Int(position, _) = position.node {
ParamPosition {
value: position as usize,
width: parse_count(width),
precision: parse_count(precision),
}
} else {
ParamPosition::default()
}
}))
} else {
None
}
}
/// `Span::from_inner`, but for `rustc_parse_format`'s `InnerSpan`
fn span_from_inner(base: SpanData, inner: rpf::InnerSpan) -> Span {
Span::new(
base.lo + BytePos::from_usize(inner.start),
base.lo + BytePos::from_usize(inner.end),
base.ctxt,
base.parent,
)
}
/// How a format parameter is used in the format string
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FormatParamKind {
/// An implicit parameter , such as `{}` or `{:?}`.
Implicit,
/// A parameter with an explicit number, e.g. `{1}`, `{0:?}`, or `{:.0$}`
Numbered,
/// A parameter with an asterisk precision. e.g. `{:.*}`.
Starred,
/// A named parameter with a named `value_arg`, such as the `x` in `format!("{x}", x = 1)`.
Named(Symbol),
/// An implicit named parameter, such as the `y` in `format!("{y}")`.
NamedInline(Symbol),
}
/// Where a format parameter is being used in the format string
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FormatParamUsage {
@ -692,467 +491,6 @@ pub enum FormatParamUsage {
Precision,
}
/// A `FormatParam` is any place in a `FormatArgument` that refers to a supplied value, e.g.
///
/// ```
/// let precision = 2;
/// format!("{:.precision$}", 0.1234);
/// ```
///
/// has two `FormatParam`s, a [`FormatParamKind::Implicit`] `.kind` with a `.value` of `0.1234`
/// and a [`FormatParamKind::NamedInline("precision")`] `.kind` with a `.value` of `2`
#[derive(Debug, Copy, Clone)]
pub struct FormatParam<'tcx> {
/// The expression this parameter refers to.
pub value: &'tcx Expr<'tcx>,
/// How this parameter refers to its `value`.
pub kind: FormatParamKind,
/// Where this format param is being used - argument/width/precision
pub usage: FormatParamUsage,
/// Span of the parameter, may be zero width. Includes the whitespace of implicit parameters.
///
/// ```text
/// format!("{}, { }, {0}, {name}", ...);
/// ^ ~~ ~ ~~~~
/// ```
pub span: Span,
}
impl<'tcx> FormatParam<'tcx> {
fn new(
mut kind: FormatParamKind,
usage: FormatParamUsage,
position: usize,
inner: rpf::InnerSpan,
values: &FormatArgsValues<'tcx>,
) -> Option<Self> {
let value_index = *values.pos_to_value_index.get(position)?;
let value = *values.value_args.get(value_index)?;
let span = span_from_inner(values.format_string_span, inner);
// if a param is declared inline, e.g. `format!("{x}")`, the generated expr's span points
// into the format string
if let FormatParamKind::Named(name) = kind && values.format_string_span.contains(value.span.data()) {
kind = FormatParamKind::NamedInline(name);
}
Some(Self {
value,
kind,
usage,
span,
})
}
}
/// Used by [width](https://doc.rust-lang.org/std/fmt/#width) and
/// [precision](https://doc.rust-lang.org/std/fmt/#precision) specifiers.
#[derive(Debug, Copy, Clone)]
pub enum Count<'tcx> {
/// Specified with a literal number, stores the value.
Is(usize, Span),
/// Specified using `$` and `*` syntaxes. The `*` format is still considered to be
/// `FormatParamKind::Numbered`.
Param(FormatParam<'tcx>),
/// Not specified.
Implied(Option<Span>),
}
impl<'tcx> Count<'tcx> {
fn new(
usage: FormatParamUsage,
count: rpf::Count<'_>,
position: Option<usize>,
inner: Option<rpf::InnerSpan>,
values: &FormatArgsValues<'tcx>,
) -> Option<Self> {
let span = inner.map(|inner| span_from_inner(values.format_string_span, inner));
Some(match count {
rpf::Count::CountIs(val) => Self::Is(val, span?),
rpf::Count::CountIsName(name, _) => Self::Param(FormatParam::new(
FormatParamKind::Named(Symbol::intern(name)),
usage,
position?,
inner?,
values,
)?),
rpf::Count::CountIsParam(_) => Self::Param(FormatParam::new(
FormatParamKind::Numbered,
usage,
position?,
inner?,
values,
)?),
rpf::Count::CountIsStar(_) => Self::Param(FormatParam::new(
FormatParamKind::Starred,
usage,
position?,
inner?,
values,
)?),
rpf::Count::CountImplied => Self::Implied(span),
})
}
pub fn is_implied(self) -> bool {
matches!(self, Count::Implied(_))
}
pub fn param(self) -> Option<FormatParam<'tcx>> {
match self {
Count::Param(param) => Some(param),
_ => None,
}
}
pub fn span(self) -> Option<Span> {
match self {
Count::Is(_, span) => Some(span),
Count::Param(param) => Some(param.span),
Count::Implied(span) => span,
}
}
}
/// Specification for the formatting of an argument in the format string. See
/// <https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters> for the precise meanings.
#[derive(Debug)]
pub struct FormatSpec<'tcx> {
/// Optionally specified character to fill alignment with.
pub fill: Option<char>,
/// Optionally specified alignment.
pub align: Alignment,
/// Whether all flag options are set to default (no flags specified).
pub no_flags: bool,
/// Represents either the maximum width or the integer precision.
pub precision: Count<'tcx>,
/// The minimum width, will be padded according to `width`/`align`
pub width: Count<'tcx>,
/// The formatting trait used by the argument, e.g. `sym::Display` for `{}`, `sym::Debug` for
/// `{:?}`.
pub r#trait: Symbol,
pub trait_span: Option<Span>,
}
impl<'tcx> FormatSpec<'tcx> {
fn new(spec: rpf::FormatSpec<'_>, positions: ParamPosition, values: &FormatArgsValues<'tcx>) -> Option<Self> {
Some(Self {
fill: spec.fill,
align: spec.align,
no_flags: spec.sign.is_none() && !spec.alternate && !spec.zero_pad && spec.debug_hex.is_none(),
precision: Count::new(
FormatParamUsage::Precision,
spec.precision,
positions.precision,
spec.precision_span,
values,
)?,
width: Count::new(
FormatParamUsage::Width,
spec.width,
positions.width,
spec.width_span,
values,
)?,
r#trait: match spec.ty {
"" => sym::Display,
"?" => sym::Debug,
"o" => sym!(Octal),
"x" => sym!(LowerHex),
"X" => sym!(UpperHex),
"p" => sym::Pointer,
"b" => sym!(Binary),
"e" => sym!(LowerExp),
"E" => sym!(UpperExp),
_ => return None,
},
trait_span: spec
.ty_span
.map(|span| span_from_inner(values.format_string_span, span)),
})
}
/// Returns true if this format spec is unchanged from the default. e.g. returns true for `{}`,
/// `{foo}` and `{2}`, but false for `{:?}`, `{foo:5}` and `{3:.5}`
pub fn is_default(&self) -> bool {
self.r#trait == sym::Display && self.is_default_for_trait()
}
/// Has no other formatting specifiers than setting the format trait. returns true for `{}`,
/// `{foo}`, `{:?}`, but false for `{foo:5}`, `{3:.5?}`
pub fn is_default_for_trait(&self) -> bool {
self.width.is_implied() && self.precision.is_implied() && self.align == Alignment::AlignUnknown && self.no_flags
}
}
/// A format argument, such as `{}`, `{foo:?}`.
#[derive(Debug)]
pub struct FormatArg<'tcx> {
/// The parameter the argument refers to.
pub param: FormatParam<'tcx>,
/// How to format `param`.
pub format: FormatSpec<'tcx>,
/// span of the whole argument, `{..}`.
pub span: Span,
}
impl<'tcx> FormatArg<'tcx> {
/// Span of the `:` and format specifiers
///
/// ```ignore
/// format!("{:.}"), format!("{foo:.}")
/// ^^ ^^
/// ```
pub fn format_span(&self) -> Span {
let base = self.span.data();
// `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
// brace `{...|}`
Span::new(self.param.span.hi(), base.hi - BytePos(1), base.ctxt, base.parent)
}
}
/// A parsed `format_args!` expansion.
#[derive(Debug)]
pub struct FormatArgsExpn<'tcx> {
/// The format string literal.
pub format_string: FormatString,
/// The format arguments, such as `{:?}`.
pub args: Vec<FormatArg<'tcx>>,
/// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will
/// include this added newline.
pub newline: bool,
/// Spans of the commas between the format string and explicit values, excluding any trailing
/// comma
///
/// ```ignore
/// format!("..", 1, 2, 3,)
/// // ^ ^ ^
/// ```
comma_spans: Vec<Span>,
/// Explicit values passed after the format string, ignoring implicit captures. `[1, z + 2]` for
/// `format!("{x} {} {y}", 1, z + 2)`.
explicit_values: Vec<&'tcx Expr<'tcx>>,
}
impl<'tcx> FormatArgsExpn<'tcx> {
/// Gets the spans of the commas inbetween the format string and explicit args, not including
/// any trailing comma
///
/// ```ignore
/// format!("{} {}", a, b)
/// // ^ ^
/// ```
///
/// Ensures that the format string and values aren't coming from a proc macro that sets the
/// output span to that of its input
fn comma_spans(cx: &LateContext<'_>, explicit_values: &[&Expr<'_>], fmt_span: Span) -> Option<Vec<Span>> {
// `format!("{} {} {c}", "one", "two", c = "three")`
// ^^^^^ ^^^^^ ^^^^^^^
let value_spans = explicit_values
.iter()
.map(|val| hygiene::walk_chain(val.span, fmt_span.ctxt()));
// `format!("{} {} {c}", "one", "two", c = "three")`
// ^^ ^^ ^^^^^^
let between_spans = once(fmt_span)
.chain(value_spans)
.tuple_windows()
.map(|(start, end)| start.between(end));
let mut comma_spans = Vec::new();
for between_span in between_spans {
let mut offset = 0;
let mut seen_comma = false;
for token in tokenize(&snippet_opt(cx, between_span)?) {
match token.kind {
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {},
TokenKind::Comma if !seen_comma => {
seen_comma = true;
let base = between_span.data();
comma_spans.push(Span::new(
base.lo + BytePos(offset),
base.lo + BytePos(offset + 1),
base.ctxt,
base.parent,
));
},
// named arguments, `start_val, name = end_val`
// ^^^^^^^^^ between_span
TokenKind::Ident | TokenKind::Eq if seen_comma => {},
// An unexpected token usually indicates the format string or a value came from a proc macro output
// that sets the span of its output to an input, e.g. `println!(some_proc_macro!("input"), ..)` that
// emits a string literal with the span set to that of `"input"`
_ => return None,
}
offset += token.len;
}
if !seen_comma {
return None;
}
}
Some(comma_spans)
}
pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
let macro_name = macro_backtrace(expr.span)
.map(|macro_call| cx.tcx.item_name(macro_call.def_id))
.find(|&name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))?;
let newline = macro_name == sym::format_args_nl;
// ::core::fmt::Arguments::new_const(pieces)
// ::core::fmt::Arguments::new_v1(pieces, args)
// ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg)
if let ExprKind::Call(callee, [pieces, rest @ ..]) = expr.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind
&& let TyKind::Path(QPath::LangItem(LangItem::FormatArguments, _, _)) = ty.kind
&& matches!(seg.ident.as_str(), "new_const" | "new_v1" | "new_v1_formatted")
{
let format_string = FormatString::new(cx, pieces)?;
let mut parser = rpf::Parser::new(
&format_string.unescaped,
format_string.style,
Some(format_string.snippet.clone()),
// `format_string.unescaped` does not contain the appended newline
false,
rpf::ParseMode::Format,
);
let parsed_args = parser
.by_ref()
.filter_map(|piece| match piece {
rpf::Piece::NextArgument(a) => Some(a),
rpf::Piece::String(_) => None,
})
.collect_vec();
if !parser.errors.is_empty() {
return None;
}
let positions = if let Some(fmt_arg) = rest.get(1) {
// If the argument contains format specs, `new_v1_formatted(_, _, fmt, _)`, parse
// them.
Either::Left(parse_rt_fmt(fmt_arg)?)
} else {
// If no format specs are given, the positions are in the given order and there are
// no `precision`/`width`s to consider.
Either::Right((0..).map(|n| ParamPosition {
value: n,
width: None,
precision: None,
}))
};
let values = if let Some(args) = rest.first() {
FormatArgsValues::new(args, format_string.span.data())
} else {
FormatArgsValues::new_empty(format_string.span.data())
};
let args = izip!(positions, parsed_args, parser.arg_places)
.map(|(position, parsed_arg, arg_span)| {
Some(FormatArg {
param: FormatParam::new(
match parsed_arg.position {
rpf::Position::ArgumentImplicitlyIs(_) => FormatParamKind::Implicit,
rpf::Position::ArgumentIs(_) => FormatParamKind::Numbered,
// NamedInline is handled by `FormatParam::new()`
rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)),
},
FormatParamUsage::Argument,
position.value,
parsed_arg.position_span,
&values,
)?,
format: FormatSpec::new(parsed_arg.format, position, &values)?,
span: span_from_inner(values.format_string_span, arg_span),
})
})
.collect::<Option<Vec<_>>>()?;
let mut explicit_values = values.value_args;
// remove values generated for implicitly captured vars
let len = explicit_values
.iter()
.take_while(|val| !format_string.span.contains(val.span))
.count();
explicit_values.truncate(len);
let comma_spans = Self::comma_spans(cx, &explicit_values, format_string.span)?;
Some(Self {
format_string,
args,
newline,
comma_spans,
explicit_values,
})
} else {
None
}
}
pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
for_each_expr(expr, |e| {
let e_ctxt = e.span.ctxt();
if e_ctxt == expr.span.ctxt() {
ControlFlow::Continue(Descend::Yes)
} else if e_ctxt.outer_expn().is_descendant_of(expn_id) {
if let Some(args) = FormatArgsExpn::parse(cx, e) {
ControlFlow::Break(args)
} else {
ControlFlow::Continue(Descend::No)
}
} else {
ControlFlow::Continue(Descend::No)
}
})
}
/// Source callsite span of all inputs
pub fn inputs_span(&self) -> Span {
match *self.explicit_values {
[] => self.format_string.span,
[.., last] => self
.format_string
.span
.to(hygiene::walk_chain(last.span, self.format_string.span.ctxt())),
}
}
/// Get the span of a value expanded to the previous comma, e.g. for the value `10`
///
/// ```ignore
/// format("{}.{}", 10, 11)
/// // ^^^^
/// ```
pub fn value_with_prev_comma_span(&self, value_id: HirId) -> Option<Span> {
for (comma_span, value) in zip(&self.comma_spans, &self.explicit_values) {
if value.hir_id == value_id {
return Some(comma_span.to(hygiene::walk_chain(value.span, comma_span.ctxt())));
}
}
None
}
/// Iterator of all format params, both values and those referenced by `width`/`precision`s.
pub fn params(&'tcx self) -> impl Iterator<Item = FormatParam<'tcx>> {
self.args
.iter()
.flat_map(|arg| [Some(arg.param), arg.format.precision.param(), arg.format.width.param()])
.flatten()
}
}
/// A node with a `HirId` and a `Span`
pub trait HirNode {
fn hir_id(&self) -> HirId;

View File

@ -23,6 +23,7 @@
pub const CORE_ITER_CLONED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "cloned"];
pub const CORE_ITER_COPIED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "copied"];
pub const CORE_ITER_FILTER: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "filter"];
pub const CORE_RESULT_OK_METHOD: [&str; 4] = ["core", "result", "Result", "ok"];
pub const CSTRING_AS_C_STR: [&str; 5] = ["alloc", "ffi", "c_str", "CString", "as_c_str"];
pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"];
@ -113,6 +114,7 @@
pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
pub const CONVERT_IDENTITY: [&str; 3] = ["core", "convert", "identity"];
pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
pub const STD_IO_LINES: [&str; 3] = ["std", "io", "Lines"];
pub const STD_IO_SEEK: [&str; 3] = ["std", "io", "Seek"];
pub const STD_IO_SEEK_FROM_CURRENT: [&str; 4] = ["std", "io", "SeekFrom", "Current"];
pub const STD_IO_SEEKFROM_START: [&str; 4] = ["std", "io", "SeekFrom", "Start"];

View File

@ -541,9 +541,25 @@ pub fn same_type_and_consts<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
pub fn is_uninit_value_valid_for_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
cx.tcx
.check_validity_requirement((ValidityRequirement::Uninit, cx.param_env.and(ty)))
// For types containing generic parameters we cannot get a layout to check.
// Therefore, we are conservative and assume that they don't allow uninit.
.unwrap_or(false)
.unwrap_or_else(|_| is_uninit_value_valid_for_ty_fallback(cx, ty))
}
/// A fallback for polymorphic types, which are not supported by `check_validity_requirement`.
fn is_uninit_value_valid_for_ty_fallback<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
match *ty.kind() {
// The array length may be polymorphic, let's try the inner type.
ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component),
// Peek through tuples and try their fallbacks.
ty::Tuple(types) => types.iter().all(|ty| is_uninit_value_valid_for_ty(cx, ty)),
// Unions are always fine right now.
// This includes MaybeUninit, the main way people use uninitialized memory.
// For ADTs, we could look at all fields just like for tuples, but that's potentially
// exponential, so let's avoid doing that for now. Code doing that is sketchy enough to
// just use an `#[allow()]`.
ty::Adt(adt, _) => adt.is_union(),
// For the rest, conservatively assume that they cannot be uninit.
_ => false,
}
}
/// Gets an iterator over all predicates which apply to the given item.

View File

@ -35,7 +35,7 @@ relicensing are archived on GitHub. We also have saved Wayback Machine copies of
The usernames of commenters on these issues can be found in relicense_comments.txt
There are a couple people in relicense_comments.txt who are not found in contributors.txt:
There are a few people in relicense_comments.txt who are not found in contributors.txt:
- @EpocSquadron has [made minor text contributions to the
README](https://github.com/rust-lang/rust-clippy/commits?author=EpocSquadron) which have since been overwritten, and
@ -55,7 +55,7 @@ There are a couple people in relicense_comments.txt who are not found in contrib
we rewrote (see below)
Two of these contributors had nonminor contributions (#2184, #427) requiring a rewrite, carried out in #3251
Two of these contributors had non-minor contributions (#2184, #427) requiring a rewrite, carried out in #3251
([archive](http://web.archive.org/web/20181005192411/https://github.com/rust-lang-nursery/rust-clippy/pull/3251),
[screenshot](https://user-images.githubusercontent.com/1617736/46573515-5cb69580-c94b-11e8-86e5-b456452121b2.png))

View File

@ -16,7 +16,7 @@ or
cargo lintcheck
```
By default the logs will be saved into
By default, the logs will be saved into
`lintcheck-logs/lintcheck_crates_logs.txt`.
You can set a custom sources.toml by adding `--crates-toml custom.toml` or using

View File

@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2023-03-24"
channel = "nightly-2023-04-06"
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]

View File

@ -130,7 +130,7 @@ impl rustc_driver::Callbacks for ClippyCallbacks {
#[allow(rustc::bad_opt_access)]
fn config(&mut self, config: &mut interface::Config) {
let conf_path = clippy_lints::lookup_conf_file();
let conf_path_string = if let Ok(Some(path)) = &conf_path {
let conf_path_string = if let Ok((Some(path), _)) = &conf_path {
path.to_str().map(String::from)
} else {
None

View File

@ -1,2 +1,4 @@
Using config file `$SRC_DIR/.clippy.toml`
Warning: `$SRC_DIR/clippy.toml` will be ignored.
warning: using config file `$SRC_DIR/.clippy.toml`, `$SRC_DIR/clippy.toml` will be ignored
warning: 1 warning emitted

View File

@ -11,6 +11,18 @@ LL - println!("val='{}'", local_i32);
LL + println!("val='{local_i32}'");
|
error: variables can be used directly in the `format!` string
--> $DIR/uninlined_format_args.rs:10:5
|
LL | println!("Hello {} is {:.*}", "x", local_i32, local_f64);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: change this to
|
LL - println!("Hello {} is {:.*}", "x", local_i32, local_f64);
LL + println!("Hello {} is {local_f64:.local_i32$}", "x");
|
error: literal with an empty format string
--> $DIR/uninlined_format_args.rs:10:35
|
@ -24,18 +36,6 @@ LL - println!("Hello {} is {:.*}", "x", local_i32, local_f64);
LL + println!("Hello x is {:.*}", local_i32, local_f64);
|
error: variables can be used directly in the `format!` string
--> $DIR/uninlined_format_args.rs:10:5
|
LL | println!("Hello {} is {:.*}", "x", local_i32, local_f64);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: change this to
|
LL - println!("Hello {} is {:.*}", "x", local_i32, local_f64);
LL + println!("Hello {} is {local_f64:.local_i32$}", "x");
|
error: variables can be used directly in the `format!` string
--> $DIR/uninlined_format_args.rs:11:5
|

View File

@ -0,0 +1 @@
avoid-breaking-exported-api = true

View File

@ -0,0 +1,9 @@
pub struct S;
impl S {
pub fn exported_fn<T>() {
unimplemented!();
}
}
fn main() {}

View File

@ -0,0 +1 @@
future-size-threshold = 1024

View File

@ -0,0 +1,27 @@
#![warn(clippy::large_futures)]
fn main() {}
pub async fn should_warn() {
let x = [0u8; 1024];
async {}.await;
dbg!(x);
}
pub async fn should_not_warn() {
let x = [0u8; 1020];
async {}.await;
dbg!(x);
}
pub async fn bar() {
should_warn().await;
async {
let x = [0u8; 1024];
dbg!(x);
}
.await;
should_not_warn().await;
}

View File

@ -0,0 +1,10 @@
error: large future with a size of 1026 bytes
--> $DIR/large_futures.rs:18:5
|
LL | should_warn().await;
| ^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(should_warn())`
|
= note: `-D clippy::large-futures` implied by `-D warnings`
error: aborting due to previous error

View File

@ -24,6 +24,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie
enforced-import-renames
enum-variant-name-threshold
enum-variant-size-threshold
future-size-threshold
ignore-interior-mutability
large-error-threshold
literal-representation-threshold

View File

@ -425,4 +425,8 @@ pub fn integer_arithmetic() {
i ^= i;
}
pub fn issue_10583(a: u16) -> u16 {
10 / a
}
fn main() {}

View File

@ -576,6 +576,12 @@ error: arithmetic operation that can potentially result in unexpected side-effec
LL | i * 2;
| ^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> $DIR/arithmetic_side_effects.rs:394:5
|
LL | 1 % i / 2;
| ^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> $DIR/arithmetic_side_effects.rs:395:5
|
@ -642,5 +648,11 @@ error: arithmetic operation that can potentially result in unexpected side-effec
LL | i %= var2;
| ^^^^^^^^^
error: aborting due to 107 previous errors
error: arithmetic operation that can potentially result in unexpected side-effects
--> $DIR/arithmetic_side_effects.rs:429:5
|
LL | 10 / a
| ^^^^^^
error: aborting due to 109 previous errors

View File

@ -63,7 +63,7 @@ fn group_with_span(delimiter: Delimiter, stream: TokenStream, span: Span) -> Gro
/// Token used to escape the following token from the macro's span rules.
const ESCAPE_CHAR: char = '$';
/// Takes a single token followed by a sequence tokens. Returns the sequence of tokens with their
/// Takes a single token followed by a sequence of tokens. Returns the sequence of tokens with their
/// span set to that of the first token. Tokens may be escaped with either `#ident` or `#(tokens)`.
#[proc_macro]
pub fn with_span(input: TokenStream) -> TokenStream {

View File

@ -29,6 +29,12 @@ fn main() {
1f64 as isize;
1f64 as usize;
1f32 as u32 as u16;
{
let _x: i8 = 1i32 as _;
1f32 as i32;
1f64 as i32;
1f32 as u8;
}
// Test clippy::cast_possible_wrap
1u8 as i8;
1u16 as i16;

View File

@ -44,10 +44,6 @@ LL | 1f32 as i32;
|
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
= note: `-D clippy::cast-possible-truncation` implied by `-D warnings`
help: ... or use `try_from` and handle the error accordingly
|
LL | i32::try_from(1f32);
| ~~~~~~~~~~~~~~~~~~~
error: casting `f32` to `u32` may truncate the value
--> $DIR/cast.rs:25:5
@ -56,10 +52,6 @@ LL | 1f32 as u32;
| ^^^^^^^^^^^
|
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
help: ... or use `try_from` and handle the error accordingly
|
LL | u32::try_from(1f32);
| ~~~~~~~~~~~~~~~~~~~
error: casting `f32` to `u32` may lose the sign of the value
--> $DIR/cast.rs:25:5
@ -76,10 +68,6 @@ LL | 1f64 as f32;
| ^^^^^^^^^^^
|
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
help: ... or use `try_from` and handle the error accordingly
|
LL | f32::try_from(1f64);
| ~~~~~~~~~~~~~~~~~~~
error: casting `i32` to `i8` may truncate the value
--> $DIR/cast.rs:27:5
@ -112,10 +100,6 @@ LL | 1f64 as isize;
| ^^^^^^^^^^^^^
|
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
help: ... or use `try_from` and handle the error accordingly
|
LL | isize::try_from(1f64);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `f64` to `usize` may truncate the value
--> $DIR/cast.rs:30:5
@ -124,10 +108,6 @@ LL | 1f64 as usize;
| ^^^^^^^^^^^^^
|
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
help: ... or use `try_from` and handle the error accordingly
|
LL | usize::try_from(1f64);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `f64` to `usize` may lose the sign of the value
--> $DIR/cast.rs:30:5
@ -154,10 +134,6 @@ LL | 1f32 as u32 as u16;
| ^^^^^^^^^^^
|
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
help: ... or use `try_from` and handle the error accordingly
|
LL | u32::try_from(1f32) as u16;
| ~~~~~~~~~~~~~~~~~~~
error: casting `f32` to `u32` may lose the sign of the value
--> $DIR/cast.rs:31:5
@ -165,8 +141,50 @@ error: casting `f32` to `u32` may lose the sign of the value
LL | 1f32 as u32 as u16;
| ^^^^^^^^^^^
error: casting `i32` to `i8` may truncate the value
--> $DIR/cast.rs:33:22
|
LL | let _x: i8 = 1i32 as _;
| ^^^^^^^^^
|
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
help: ... or use `try_from` and handle the error accordingly
|
LL | let _x: i8 = 1i32.try_into();
| ~~~~~~~~~~~~~~~
error: casting `f32` to `i32` may truncate the value
--> $DIR/cast.rs:34:9
|
LL | 1f32 as i32;
| ^^^^^^^^^^^
|
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
error: casting `f64` to `i32` may truncate the value
--> $DIR/cast.rs:35:9
|
LL | 1f64 as i32;
| ^^^^^^^^^^^
|
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
error: casting `f32` to `u8` may truncate the value
--> $DIR/cast.rs:36:9
|
LL | 1f32 as u8;
| ^^^^^^^^^^
|
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
error: casting `f32` to `u8` may lose the sign of the value
--> $DIR/cast.rs:36:9
|
LL | 1f32 as u8;
| ^^^^^^^^^^
error: casting `u8` to `i8` may wrap around the value
--> $DIR/cast.rs:33:5
--> $DIR/cast.rs:39:5
|
LL | 1u8 as i8;
| ^^^^^^^^^
@ -174,43 +192,43 @@ LL | 1u8 as i8;
= note: `-D clippy::cast-possible-wrap` implied by `-D warnings`
error: casting `u16` to `i16` may wrap around the value
--> $DIR/cast.rs:34:5
--> $DIR/cast.rs:40:5
|
LL | 1u16 as i16;
| ^^^^^^^^^^^
error: casting `u32` to `i32` may wrap around the value
--> $DIR/cast.rs:35:5
--> $DIR/cast.rs:41:5
|
LL | 1u32 as i32;
| ^^^^^^^^^^^
error: casting `u64` to `i64` may wrap around the value
--> $DIR/cast.rs:36:5
--> $DIR/cast.rs:42:5
|
LL | 1u64 as i64;
| ^^^^^^^^^^^
error: casting `usize` to `isize` may wrap around the value
--> $DIR/cast.rs:37:5
--> $DIR/cast.rs:43:5
|
LL | 1usize as isize;
| ^^^^^^^^^^^^^^^
error: casting `i32` to `u32` may lose the sign of the value
--> $DIR/cast.rs:40:5
--> $DIR/cast.rs:46:5
|
LL | -1i32 as u32;
| ^^^^^^^^^^^^
error: casting `isize` to `usize` may lose the sign of the value
--> $DIR/cast.rs:42:5
--> $DIR/cast.rs:48:5
|
LL | -1isize as usize;
| ^^^^^^^^^^^^^^^^
error: casting `i64` to `i8` may truncate the value
--> $DIR/cast.rs:109:5
--> $DIR/cast.rs:115:5
|
LL | (-99999999999i64).min(1) as i8; // should be linted because signed
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -222,7 +240,7 @@ LL | i8::try_from((-99999999999i64).min(1)); // should be linted because sig
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: casting `u64` to `u8` may truncate the value
--> $DIR/cast.rs:121:5
--> $DIR/cast.rs:127:5
|
LL | 999999u64.clamp(0, 256) as u8; // should still be linted
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -234,7 +252,7 @@ LL | u8::try_from(999999u64.clamp(0, 256)); // should still be linted
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: casting `main::E2` to `u8` may truncate the value
--> $DIR/cast.rs:142:21
--> $DIR/cast.rs:148:21
|
LL | let _ = self as u8;
| ^^^^^^^^^^
@ -246,7 +264,7 @@ LL | let _ = u8::try_from(self);
| ~~~~~~~~~~~~~~~~~~
error: casting `main::E2::B` to `u8` will truncate the value
--> $DIR/cast.rs:143:21
--> $DIR/cast.rs:149:21
|
LL | let _ = Self::B as u8;
| ^^^^^^^^^^^^^
@ -254,7 +272,7 @@ LL | let _ = Self::B as u8;
= note: `-D clippy::cast-enum-truncation` implied by `-D warnings`
error: casting `main::E5` to `i8` may truncate the value
--> $DIR/cast.rs:179:21
--> $DIR/cast.rs:185:21
|
LL | let _ = self as i8;
| ^^^^^^^^^^
@ -266,13 +284,13 @@ LL | let _ = i8::try_from(self);
| ~~~~~~~~~~~~~~~~~~
error: casting `main::E5::A` to `i8` will truncate the value
--> $DIR/cast.rs:180:21
--> $DIR/cast.rs:186:21
|
LL | let _ = Self::A as i8;
| ^^^^^^^^^^^^^
error: casting `main::E6` to `i16` may truncate the value
--> $DIR/cast.rs:194:21
--> $DIR/cast.rs:200:21
|
LL | let _ = self as i16;
| ^^^^^^^^^^^
@ -284,7 +302,7 @@ LL | let _ = i16::try_from(self);
| ~~~~~~~~~~~~~~~~~~~
error: casting `main::E7` to `usize` may truncate the value on targets with 32-bit wide pointers
--> $DIR/cast.rs:209:21
--> $DIR/cast.rs:215:21
|
LL | let _ = self as usize;
| ^^^^^^^^^^^^^
@ -296,7 +314,7 @@ LL | let _ = usize::try_from(self);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `main::E10` to `u16` may truncate the value
--> $DIR/cast.rs:250:21
--> $DIR/cast.rs:256:21
|
LL | let _ = self as u16;
| ^^^^^^^^^^^
@ -308,7 +326,7 @@ LL | let _ = u16::try_from(self);
| ~~~~~~~~~~~~~~~~~~~
error: casting `u32` to `u8` may truncate the value
--> $DIR/cast.rs:258:13
--> $DIR/cast.rs:264:13
|
LL | let c = (q >> 16) as u8;
| ^^^^^^^^^^^^^^^
@ -316,11 +334,11 @@ LL | let c = (q >> 16) as u8;
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
help: ... or use `try_from` and handle the error accordingly
|
LL | let c = u8::try_from((q >> 16));
| ~~~~~~~~~~~~~~~~~~~~~~~
LL | let c = u8::try_from(q >> 16);
| ~~~~~~~~~~~~~~~~~~~~~
error: casting `u32` to `u8` may truncate the value
--> $DIR/cast.rs:261:13
--> $DIR/cast.rs:267:13
|
LL | let c = (q / 1000) as u8;
| ^^^^^^^^^^^^^^^^
@ -328,8 +346,8 @@ LL | let c = (q / 1000) as u8;
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
help: ... or use `try_from` and handle the error accordingly
|
LL | let c = u8::try_from((q / 1000));
| ~~~~~~~~~~~~~~~~~~~~~~~~
LL | let c = u8::try_from(q / 1000);
| ~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to 36 previous errors
error: aborting due to 41 previous errors

View File

@ -0,0 +1,358 @@
// run-rustfix
#![allow(unused)]
#![warn(clippy::clear_with_drain)]
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
fn vec_range() {
// Do not lint because iterator is assigned
let mut v = vec![1, 2, 3];
let iter = v.drain(0..v.len());
// Do not lint because iterator is used
let mut v = vec![1, 2, 3];
let n = v.drain(0..v.len()).count();
// Do not lint because iterator is assigned and used
let mut v = vec![1, 2, 3];
let iter = v.drain(usize::MIN..v.len());
let n = iter.count();
// Do lint
let mut v = vec![1, 2, 3];
v.clear();
// Do lint
let mut v = vec![1, 2, 3];
v.clear();
}
fn vec_range_from() {
// Do not lint because iterator is assigned
let mut v = vec![1, 2, 3];
let iter = v.drain(0..);
// Do not lint because iterator is assigned and used
let mut v = vec![1, 2, 3];
let mut iter = v.drain(0..);
let next = iter.next();
// Do not lint because iterator is used
let mut v = vec![1, 2, 3];
let next = v.drain(usize::MIN..).next();
// Do lint
let mut v = vec![1, 2, 3];
v.clear();
// Do lint
let mut v = vec![1, 2, 3];
v.clear();
}
fn vec_range_full() {
// Do not lint because iterator is assigned
let mut v = vec![1, 2, 3];
let iter = v.drain(..);
// Do not lint because iterator is used
let mut v = vec![1, 2, 3];
for x in v.drain(..) {
let y = format!("x = {x}");
}
// Do lint
let mut v = vec![1, 2, 3];
v.clear();
}
fn vec_range_to() {
// Do not lint because iterator is assigned
let mut v = vec![1, 2, 3];
let iter = v.drain(..v.len());
// Do not lint because iterator is assigned and used
let mut v = vec![1, 2, 3];
let iter = v.drain(..v.len());
for x in iter {
let y = format!("x = {x}");
}
// Do lint
let mut v = vec![1, 2, 3];
v.clear();
}
fn vec_partial_drains() {
// Do not lint any of these because the ranges are not full
let mut v = vec![1, 2, 3];
v.drain(1..);
let mut v = vec![1, 2, 3];
v.drain(1..).max();
let mut v = vec![1, 2, 3];
v.drain(..v.len() - 1);
let mut v = vec![1, 2, 3];
v.drain(..v.len() - 1).min();
let mut v = vec![1, 2, 3];
v.drain(1..v.len() - 1);
let mut v = vec![1, 2, 3];
let w: Vec<i8> = v.drain(1..v.len() - 1).collect();
}
fn vec_deque_range() {
// Do not lint because iterator is assigned
let mut deque = VecDeque::from([1, 2, 3]);
let iter = deque.drain(0..deque.len());
// Do not lint because iterator is used
let mut deque = VecDeque::from([1, 2, 3]);
let n = deque.drain(0..deque.len()).count();
// Do not lint because iterator is assigned and used
let mut deque = VecDeque::from([1, 2, 3]);
let iter = deque.drain(usize::MIN..deque.len());
let n = iter.count();
// Do lint
let mut deque = VecDeque::from([1, 2, 3]);
deque.clear();
// Do lint
let mut deque = VecDeque::from([1, 2, 3]);
deque.clear();
}
fn vec_deque_range_from() {
// Do not lint because iterator is assigned
let mut deque = VecDeque::from([1, 2, 3]);
let iter = deque.drain(0..);
// Do not lint because iterator is assigned and used
let mut deque = VecDeque::from([1, 2, 3]);
let mut iter = deque.drain(0..);
let next = iter.next();
// Do not lint because iterator is used
let mut deque = VecDeque::from([1, 2, 3]);
let next = deque.drain(usize::MIN..).next();
// Do lint
let mut deque = VecDeque::from([1, 2, 3]);
deque.clear();
// Do lint
let mut deque = VecDeque::from([1, 2, 3]);
deque.clear();
}
fn vec_deque_range_full() {
// Do not lint because iterator is assigned
let mut deque = VecDeque::from([1, 2, 3]);
let iter = deque.drain(..);
// Do not lint because iterator is used
let mut deque = VecDeque::from([1, 2, 3]);
for x in deque.drain(..) {
let y = format!("x = {x}");
}
// Do lint
let mut deque = VecDeque::from([1, 2, 3]);
deque.clear();
}
fn vec_deque_range_to() {
// Do not lint because iterator is assigned
let mut deque = VecDeque::from([1, 2, 3]);
let iter = deque.drain(..deque.len());
// Do not lint because iterator is assigned and used
let mut deque = VecDeque::from([1, 2, 3]);
let iter = deque.drain(..deque.len());
for x in iter {
let y = format!("x = {x}");
}
// Do lint
let mut deque = VecDeque::from([1, 2, 3]);
deque.clear();
}
fn vec_deque_partial_drains() {
// Do not lint any of these because the ranges are not full
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(1..);
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(1..).max();
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(..deque.len() - 1);
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(..deque.len() - 1).min();
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(1..deque.len() - 1);
let mut deque = VecDeque::from([1, 2, 3]);
let w: Vec<i8> = deque.drain(1..deque.len() - 1).collect();
}
fn string_range() {
// Do not lint because iterator is assigned
let mut s = String::from("Hello, world!");
let iter = s.drain(0..s.len());
// Do not lint because iterator is used
let mut s = String::from("Hello, world!");
let n = s.drain(0..s.len()).count();
// Do not lint because iterator is assigned and used
let mut s = String::from("Hello, world!");
let iter = s.drain(usize::MIN..s.len());
let n = iter.count();
// Do lint
let mut s = String::from("Hello, world!");
s.clear();
// Do lint
let mut s = String::from("Hello, world!");
s.clear();
}
fn string_range_from() {
// Do not lint because iterator is assigned
let mut s = String::from("Hello, world!");
let iter = s.drain(0..);
// Do not lint because iterator is assigned and used
let mut s = String::from("Hello, world!");
let mut iter = s.drain(0..);
let next = iter.next();
// Do not lint because iterator is used
let mut s = String::from("Hello, world!");
let next = s.drain(usize::MIN..).next();
// Do lint
let mut s = String::from("Hello, world!");
s.clear();
// Do lint
let mut s = String::from("Hello, world!");
s.clear();
}
fn string_range_full() {
// Do not lint because iterator is assigned
let mut s = String::from("Hello, world!");
let iter = s.drain(..);
// Do not lint because iterator is used
let mut s = String::from("Hello, world!");
for x in s.drain(..) {
let y = format!("x = {x}");
}
// Do lint
let mut s = String::from("Hello, world!");
s.clear();
}
fn string_range_to() {
// Do not lint because iterator is assigned
let mut s = String::from("Hello, world!");
let iter = s.drain(..s.len());
// Do not lint because iterator is assigned and used
let mut s = String::from("Hello, world!");
let iter = s.drain(..s.len());
for x in iter {
let y = format!("x = {x}");
}
// Do lint
let mut s = String::from("Hello, world!");
s.clear();
}
fn string_partial_drains() {
// Do not lint any of these because the ranges are not full
let mut s = String::from("Hello, world!");
s.drain(1..);
let mut s = String::from("Hello, world!");
s.drain(1..).max();
let mut s = String::from("Hello, world!");
s.drain(..s.len() - 1);
let mut s = String::from("Hello, world!");
s.drain(..s.len() - 1).min();
let mut s = String::from("Hello, world!");
s.drain(1..s.len() - 1);
let mut s = String::from("Hello, world!");
let w: String = s.drain(1..s.len() - 1).collect();
}
fn hash_set() {
// Do not lint because iterator is assigned
let mut set = HashSet::from([1, 2, 3]);
let iter = set.drain();
// Do not lint because iterator is assigned and used
let mut set = HashSet::from([1, 2, 3]);
let mut iter = set.drain();
let next = iter.next();
// Do not lint because iterator is used
let mut set = HashSet::from([1, 2, 3]);
let next = set.drain().next();
// Do lint
let mut set = HashSet::from([1, 2, 3]);
set.clear();
}
fn hash_map() {
// Do not lint because iterator is assigned
let mut map = HashMap::from([(1, "a"), (2, "b")]);
let iter = map.drain();
// Do not lint because iterator is assigned and used
let mut map = HashMap::from([(1, "a"), (2, "b")]);
let mut iter = map.drain();
let next = iter.next();
// Do not lint because iterator is used
let mut map = HashMap::from([(1, "a"), (2, "b")]);
let next = map.drain().next();
// Do lint
let mut map = HashMap::from([(1, "a"), (2, "b")]);
map.clear();
}
fn binary_heap() {
// Do not lint because iterator is assigned
let mut heap = BinaryHeap::from([1, 2]);
let iter = heap.drain();
// Do not lint because iterator is assigned and used
let mut heap = BinaryHeap::from([1, 2]);
let mut iter = heap.drain();
let next = iter.next();
// Do not lint because iterator is used
let mut heap = BinaryHeap::from([1, 2]);
let next = heap.drain().next();
// Do lint
let mut heap = BinaryHeap::from([1, 2]);
heap.clear();
}
fn main() {}

View File

@ -0,0 +1,358 @@
// run-rustfix
#![allow(unused)]
#![warn(clippy::clear_with_drain)]
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
fn vec_range() {
// Do not lint because iterator is assigned
let mut v = vec![1, 2, 3];
let iter = v.drain(0..v.len());
// Do not lint because iterator is used
let mut v = vec![1, 2, 3];
let n = v.drain(0..v.len()).count();
// Do not lint because iterator is assigned and used
let mut v = vec![1, 2, 3];
let iter = v.drain(usize::MIN..v.len());
let n = iter.count();
// Do lint
let mut v = vec![1, 2, 3];
v.drain(0..v.len());
// Do lint
let mut v = vec![1, 2, 3];
v.drain(usize::MIN..v.len());
}
fn vec_range_from() {
// Do not lint because iterator is assigned
let mut v = vec![1, 2, 3];
let iter = v.drain(0..);
// Do not lint because iterator is assigned and used
let mut v = vec![1, 2, 3];
let mut iter = v.drain(0..);
let next = iter.next();
// Do not lint because iterator is used
let mut v = vec![1, 2, 3];
let next = v.drain(usize::MIN..).next();
// Do lint
let mut v = vec![1, 2, 3];
v.drain(0..);
// Do lint
let mut v = vec![1, 2, 3];
v.drain(usize::MIN..);
}
fn vec_range_full() {
// Do not lint because iterator is assigned
let mut v = vec![1, 2, 3];
let iter = v.drain(..);
// Do not lint because iterator is used
let mut v = vec![1, 2, 3];
for x in v.drain(..) {
let y = format!("x = {x}");
}
// Do lint
let mut v = vec![1, 2, 3];
v.drain(..);
}
fn vec_range_to() {
// Do not lint because iterator is assigned
let mut v = vec![1, 2, 3];
let iter = v.drain(..v.len());
// Do not lint because iterator is assigned and used
let mut v = vec![1, 2, 3];
let iter = v.drain(..v.len());
for x in iter {
let y = format!("x = {x}");
}
// Do lint
let mut v = vec![1, 2, 3];
v.drain(..v.len());
}
fn vec_partial_drains() {
// Do not lint any of these because the ranges are not full
let mut v = vec![1, 2, 3];
v.drain(1..);
let mut v = vec![1, 2, 3];
v.drain(1..).max();
let mut v = vec![1, 2, 3];
v.drain(..v.len() - 1);
let mut v = vec![1, 2, 3];
v.drain(..v.len() - 1).min();
let mut v = vec![1, 2, 3];
v.drain(1..v.len() - 1);
let mut v = vec![1, 2, 3];
let w: Vec<i8> = v.drain(1..v.len() - 1).collect();
}
fn vec_deque_range() {
// Do not lint because iterator is assigned
let mut deque = VecDeque::from([1, 2, 3]);
let iter = deque.drain(0..deque.len());
// Do not lint because iterator is used
let mut deque = VecDeque::from([1, 2, 3]);
let n = deque.drain(0..deque.len()).count();
// Do not lint because iterator is assigned and used
let mut deque = VecDeque::from([1, 2, 3]);
let iter = deque.drain(usize::MIN..deque.len());
let n = iter.count();
// Do lint
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(0..deque.len());
// Do lint
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(usize::MIN..deque.len());
}
fn vec_deque_range_from() {
// Do not lint because iterator is assigned
let mut deque = VecDeque::from([1, 2, 3]);
let iter = deque.drain(0..);
// Do not lint because iterator is assigned and used
let mut deque = VecDeque::from([1, 2, 3]);
let mut iter = deque.drain(0..);
let next = iter.next();
// Do not lint because iterator is used
let mut deque = VecDeque::from([1, 2, 3]);
let next = deque.drain(usize::MIN..).next();
// Do lint
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(0..);
// Do lint
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(usize::MIN..);
}
fn vec_deque_range_full() {
// Do not lint because iterator is assigned
let mut deque = VecDeque::from([1, 2, 3]);
let iter = deque.drain(..);
// Do not lint because iterator is used
let mut deque = VecDeque::from([1, 2, 3]);
for x in deque.drain(..) {
let y = format!("x = {x}");
}
// Do lint
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(..);
}
fn vec_deque_range_to() {
// Do not lint because iterator is assigned
let mut deque = VecDeque::from([1, 2, 3]);
let iter = deque.drain(..deque.len());
// Do not lint because iterator is assigned and used
let mut deque = VecDeque::from([1, 2, 3]);
let iter = deque.drain(..deque.len());
for x in iter {
let y = format!("x = {x}");
}
// Do lint
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(..deque.len());
}
fn vec_deque_partial_drains() {
// Do not lint any of these because the ranges are not full
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(1..);
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(1..).max();
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(..deque.len() - 1);
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(..deque.len() - 1).min();
let mut deque = VecDeque::from([1, 2, 3]);
deque.drain(1..deque.len() - 1);
let mut deque = VecDeque::from([1, 2, 3]);
let w: Vec<i8> = deque.drain(1..deque.len() - 1).collect();
}
fn string_range() {
// Do not lint because iterator is assigned
let mut s = String::from("Hello, world!");
let iter = s.drain(0..s.len());
// Do not lint because iterator is used
let mut s = String::from("Hello, world!");
let n = s.drain(0..s.len()).count();
// Do not lint because iterator is assigned and used
let mut s = String::from("Hello, world!");
let iter = s.drain(usize::MIN..s.len());
let n = iter.count();
// Do lint
let mut s = String::from("Hello, world!");
s.drain(0..s.len());
// Do lint
let mut s = String::from("Hello, world!");
s.drain(usize::MIN..s.len());
}
fn string_range_from() {
// Do not lint because iterator is assigned
let mut s = String::from("Hello, world!");
let iter = s.drain(0..);
// Do not lint because iterator is assigned and used
let mut s = String::from("Hello, world!");
let mut iter = s.drain(0..);
let next = iter.next();
// Do not lint because iterator is used
let mut s = String::from("Hello, world!");
let next = s.drain(usize::MIN..).next();
// Do lint
let mut s = String::from("Hello, world!");
s.drain(0..);
// Do lint
let mut s = String::from("Hello, world!");
s.drain(usize::MIN..);
}
fn string_range_full() {
// Do not lint because iterator is assigned
let mut s = String::from("Hello, world!");
let iter = s.drain(..);
// Do not lint because iterator is used
let mut s = String::from("Hello, world!");
for x in s.drain(..) {
let y = format!("x = {x}");
}
// Do lint
let mut s = String::from("Hello, world!");
s.drain(..);
}
fn string_range_to() {
// Do not lint because iterator is assigned
let mut s = String::from("Hello, world!");
let iter = s.drain(..s.len());
// Do not lint because iterator is assigned and used
let mut s = String::from("Hello, world!");
let iter = s.drain(..s.len());
for x in iter {
let y = format!("x = {x}");
}
// Do lint
let mut s = String::from("Hello, world!");
s.drain(..s.len());
}
fn string_partial_drains() {
// Do not lint any of these because the ranges are not full
let mut s = String::from("Hello, world!");
s.drain(1..);
let mut s = String::from("Hello, world!");
s.drain(1..).max();
let mut s = String::from("Hello, world!");
s.drain(..s.len() - 1);
let mut s = String::from("Hello, world!");
s.drain(..s.len() - 1).min();
let mut s = String::from("Hello, world!");
s.drain(1..s.len() - 1);
let mut s = String::from("Hello, world!");
let w: String = s.drain(1..s.len() - 1).collect();
}
fn hash_set() {
// Do not lint because iterator is assigned
let mut set = HashSet::from([1, 2, 3]);
let iter = set.drain();
// Do not lint because iterator is assigned and used
let mut set = HashSet::from([1, 2, 3]);
let mut iter = set.drain();
let next = iter.next();
// Do not lint because iterator is used
let mut set = HashSet::from([1, 2, 3]);
let next = set.drain().next();
// Do lint
let mut set = HashSet::from([1, 2, 3]);
set.drain();
}
fn hash_map() {
// Do not lint because iterator is assigned
let mut map = HashMap::from([(1, "a"), (2, "b")]);
let iter = map.drain();
// Do not lint because iterator is assigned and used
let mut map = HashMap::from([(1, "a"), (2, "b")]);
let mut iter = map.drain();
let next = iter.next();
// Do not lint because iterator is used
let mut map = HashMap::from([(1, "a"), (2, "b")]);
let next = map.drain().next();
// Do lint
let mut map = HashMap::from([(1, "a"), (2, "b")]);
map.drain();
}
fn binary_heap() {
// Do not lint because iterator is assigned
let mut heap = BinaryHeap::from([1, 2]);
let iter = heap.drain();
// Do not lint because iterator is assigned and used
let mut heap = BinaryHeap::from([1, 2]);
let mut iter = heap.drain();
let next = iter.next();
// Do not lint because iterator is used
let mut heap = BinaryHeap::from([1, 2]);
let next = heap.drain().next();
// Do lint
let mut heap = BinaryHeap::from([1, 2]);
heap.drain();
}
fn main() {}

View File

@ -0,0 +1,130 @@
error: `drain` used to clear a `Vec`
--> $DIR/clear_with_drain.rs:23:7
|
LL | v.drain(0..v.len());
| ^^^^^^^^^^^^^^^^^ help: try: `clear()`
|
= note: `-D clippy::clear-with-drain` implied by `-D warnings`
error: `drain` used to clear a `Vec`
--> $DIR/clear_with_drain.rs:27:7
|
LL | v.drain(usize::MIN..v.len());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `Vec`
--> $DIR/clear_with_drain.rs:46:7
|
LL | v.drain(0..);
| ^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `Vec`
--> $DIR/clear_with_drain.rs:50:7
|
LL | v.drain(usize::MIN..);
| ^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `Vec`
--> $DIR/clear_with_drain.rs:66:7
|
LL | v.drain(..);
| ^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `Vec`
--> $DIR/clear_with_drain.rs:83:7
|
LL | v.drain(..v.len());
| ^^^^^^^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `VecDeque`
--> $DIR/clear_with_drain.rs:121:11
|
LL | deque.drain(0..deque.len());
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `VecDeque`
--> $DIR/clear_with_drain.rs:125:11
|
LL | deque.drain(usize::MIN..deque.len());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `VecDeque`
--> $DIR/clear_with_drain.rs:144:11
|
LL | deque.drain(0..);
| ^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `VecDeque`
--> $DIR/clear_with_drain.rs:148:11
|
LL | deque.drain(usize::MIN..);
| ^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `VecDeque`
--> $DIR/clear_with_drain.rs:164:11
|
LL | deque.drain(..);
| ^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `VecDeque`
--> $DIR/clear_with_drain.rs:181:11
|
LL | deque.drain(..deque.len());
| ^^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `String`
--> $DIR/clear_with_drain.rs:219:7
|
LL | s.drain(0..s.len());
| ^^^^^^^^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `String`
--> $DIR/clear_with_drain.rs:223:7
|
LL | s.drain(usize::MIN..s.len());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `String`
--> $DIR/clear_with_drain.rs:242:7
|
LL | s.drain(0..);
| ^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `String`
--> $DIR/clear_with_drain.rs:246:7
|
LL | s.drain(usize::MIN..);
| ^^^^^^^^^^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `String`
--> $DIR/clear_with_drain.rs:262:7
|
LL | s.drain(..);
| ^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `String`
--> $DIR/clear_with_drain.rs:279:7
|
LL | s.drain(..s.len());
| ^^^^^^^^^^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `HashSet`
--> $DIR/clear_with_drain.rs:317:9
|
LL | set.drain();
| ^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `HashMap`
--> $DIR/clear_with_drain.rs:336:9
|
LL | map.drain();
| ^^^^^^^ help: try: `clear()`
error: `drain` used to clear a `BinaryHeap`
--> $DIR/clear_with_drain.rs:355:10
|
LL | heap.drain();
| ^^^^^^^ help: try: `clear()`
error: aborting due to 21 previous errors

View File

@ -84,13 +84,18 @@ fn shadowing_2() {
}
#[allow(clippy::let_unit_value)]
fn fake_read() {
let mut x = vec![1, 2, 3]; // Ok
fn fake_read_1() {
let mut x = vec![1, 2, 3]; // WARNING
x.reverse();
// `collection_is_never_read` gets fooled, but other lints should catch this.
let _: () = x.clear();
}
fn fake_read_2() {
let mut x = vec![1, 2, 3]; // WARNING
x.reverse();
println!("{:?}", x.push(5));
}
fn assignment() {
let mut x = vec![1, 2, 3]; // WARNING
let y = vec![4, 5, 6]; // Ok
@ -163,3 +168,23 @@ fn foo<T>(v: &Vec<T>) -> usize {
let x = vec![1, 2, 3]; // Ok
foo(&x);
}
fn string() {
// Do lint (write without read)
let mut s = String::new();
s.push_str("Hello, World!");
// Do not lint (read without write)
let mut s = String::from("Hello, World!");
let _ = s.len();
// Do not lint (write and read)
let mut s = String::from("Hello, World!");
s.push_str("foo, bar");
let _ = s.len();
// Do lint the first line, but not the second
let mut s = String::from("Hello, World!");
let t = String::from("foo, bar");
s = t;
}

View File

@ -25,28 +25,52 @@ LL | let mut x = HashMap::new(); // WARNING
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: collection is never read
--> $DIR/collection_is_never_read.rs:95:5
--> $DIR/collection_is_never_read.rs:88:5
|
LL | let mut x = vec![1, 2, 3]; // WARNING
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: collection is never read
--> $DIR/collection_is_never_read.rs:102:5
--> $DIR/collection_is_never_read.rs:94:5
|
LL | let mut x = vec![1, 2, 3]; // WARNING
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: collection is never read
--> $DIR/collection_is_never_read.rs:119:5
--> $DIR/collection_is_never_read.rs:100:5
|
LL | let mut x = vec![1, 2, 3]; // WARNING
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: collection is never read
--> $DIR/collection_is_never_read.rs:107:5
|
LL | let mut x = vec![1, 2, 3]; // WARNING
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: collection is never read
--> $DIR/collection_is_never_read.rs:124:5
|
LL | let mut x = HashSet::new(); // WARNING
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: collection is never read
--> $DIR/collection_is_never_read.rs:133:5
--> $DIR/collection_is_never_read.rs:138:5
|
LL | let x = vec![1, 2, 3]; // WARNING
| ^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 8 previous errors
error: collection is never read
--> $DIR/collection_is_never_read.rs:174:5
|
LL | let mut s = String::new();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: collection is never read
--> $DIR/collection_is_never_read.rs:187:5
|
LL | let mut s = String::from("Hello, World!");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 12 previous errors

View File

@ -21,6 +21,17 @@ pub fn must_use_with_note() -> Result<(), ()> {
unimplemented!();
}
// vvvv Should not lint (#10486)
#[must_use]
async fn async_must_use() -> usize {
unimplemented!();
}
#[must_use]
async fn async_must_use_result() -> Result<(), ()> {
Ok(())
}
fn main() {
must_use_result();
must_use_tuple();

View File

@ -23,5 +23,13 @@ LL | pub fn must_use_array() -> [Result<(), ()>; 1] {
|
= help: either add some descriptive text or remove the attribute
error: aborting due to 3 previous errors
error: this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`
--> $DIR/double_must_use.rs:31:1
|
LL | async fn async_must_use_result() -> Result<(), ()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: either add some descriptive text or remove the attribute
error: aborting due to 4 previous errors

View File

@ -0,0 +1,105 @@
// run-rustfix
#![allow(unused, clippy::needless_lifetimes)]
#![warn(clippy::extra_unused_type_parameters)]
fn unused_ty(x: u8) {
unimplemented!()
}
fn unused_multi(x: u8) {
unimplemented!()
}
fn unused_with_lt<'a>(x: &'a u8) {
unimplemented!()
}
fn used_ty<T>(x: T, y: u8) {}
fn used_ref<'a, T>(x: &'a T) {}
fn used_ret<T: Default>(x: u8) -> T {
T::default()
}
fn unused_bounded<U>(x: U) {
unimplemented!();
}
fn some_unused<B, C>(b: B, c: C) {
unimplemented!();
}
fn used_opaque<A>(iter: impl Iterator<Item = A>) -> usize {
iter.count()
}
fn used_ret_opaque<A>() -> impl Iterator<Item = A> {
std::iter::empty()
}
fn used_vec_box<T>(x: Vec<Box<T>>) {}
fn used_body<T: Default + ToString>() -> String {
T::default().to_string()
}
fn used_closure<T: Default + ToString>() -> impl Fn() {
|| println!("{}", T::default().to_string())
}
struct S;
impl S {
fn unused_ty_impl(&self) {
unimplemented!()
}
}
// Don't lint on trait methods
trait Foo {
fn bar<T>(&self);
}
impl Foo for S {
fn bar<T>(&self) {}
}
fn skip_index<A, Iter>(iter: Iter, index: usize) -> impl Iterator<Item = A>
where
Iter: Iterator<Item = A>,
{
iter.enumerate()
.filter_map(move |(i, a)| if i == index { None } else { Some(a) })
}
fn unused_opaque(dummy: impl Default) {
unimplemented!()
}
mod unexported_trait_bounds {
mod private {
pub trait Private {}
}
fn priv_trait_bound<T: private::Private>() {
unimplemented!();
}
fn unused_with_priv_trait_bound<T: private::Private>() {
unimplemented!();
}
}
mod issue10319 {
fn assert_send<T: Send>() {}
fn assert_send_where<T>()
where
T: Send,
{
}
}
fn main() {}

View File

@ -1,3 +1,5 @@
// run-rustfix
#![allow(unused, clippy::needless_lifetimes)]
#![warn(clippy::extra_unused_type_parameters)]
@ -21,14 +23,7 @@ fn used_ret<T: Default>(x: u8) -> T {
T::default()
}
fn unused_bounded<T: Default, U>(x: U) {
unimplemented!();
}
fn unused_where_clause<T, U>(x: U)
where
T: Default,
{
fn unused_bounded<T: Default, U, V: Default>(x: U) {
unimplemented!();
}

View File

@ -1,75 +1,64 @@
error: type parameter goes unused in function definition
--> $DIR/extra_unused_type_parameters.rs:4:13
error: type parameter `T` goes unused in function definition
--> $DIR/extra_unused_type_parameters.rs:6:13
|
LL | fn unused_ty<T>(x: u8) {
| ^^^
| ^^^ help: consider removing the parameter
|
= help: consider removing the parameter
= note: `-D clippy::extra-unused-type-parameters` implied by `-D warnings`
error: type parameters go unused in function definition
--> $DIR/extra_unused_type_parameters.rs:8:16
error: type parameters go unused in function definition: T, U
--> $DIR/extra_unused_type_parameters.rs:10:16
|
LL | fn unused_multi<T, U>(x: u8) {
| ^^^^^^
|
= help: consider removing the parameters
| ^^^^^^ help: consider removing the parameters
error: type parameter goes unused in function definition
--> $DIR/extra_unused_type_parameters.rs:12:23
error: type parameter `T` goes unused in function definition
--> $DIR/extra_unused_type_parameters.rs:14:21
|
LL | fn unused_with_lt<'a, T>(x: &'a u8) {
| ^
|
= help: consider removing the parameter
| ^^^ help: consider removing the parameter
error: type parameter goes unused in function definition
--> $DIR/extra_unused_type_parameters.rs:24:19
error: type parameters go unused in function definition: T, V
--> $DIR/extra_unused_type_parameters.rs:26:19
|
LL | fn unused_bounded<T: Default, U>(x: U) {
| ^^^^^^^^^^^
LL | fn unused_bounded<T: Default, U, V: Default>(x: U) {
| ^^^^^^^^^^^^ ^^^^^^^^^^^^
|
help: consider removing the parameters
|
LL - fn unused_bounded<T: Default, U, V: Default>(x: U) {
LL + fn unused_bounded<U>(x: U) {
|
= help: consider removing the parameter
error: type parameter goes unused in function definition
--> $DIR/extra_unused_type_parameters.rs:28:24
|
LL | fn unused_where_clause<T, U>(x: U)
| ^^
|
= help: consider removing the parameter
error: type parameters go unused in function definition
--> $DIR/extra_unused_type_parameters.rs:35:16
error: type parameters go unused in function definition: A, D, E
--> $DIR/extra_unused_type_parameters.rs:30:16
|
LL | fn some_unused<A, B, C, D: Iterator<Item = (B, C)>, E>(b: B, c: C) {
| ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^
| ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider removing the parameters
|
LL - fn some_unused<A, B, C, D: Iterator<Item = (B, C)>, E>(b: B, c: C) {
LL + fn some_unused<B, C>(b: B, c: C) {
|
= help: consider removing the parameters
error: type parameter goes unused in function definition
--> $DIR/extra_unused_type_parameters.rs:60:22
error: type parameter `T` goes unused in function definition
--> $DIR/extra_unused_type_parameters.rs:55:22
|
LL | fn unused_ty_impl<T>(&self) {
| ^^^
|
= help: consider removing the parameter
| ^^^ help: consider removing the parameter
error: type parameters go unused in function definition
--> $DIR/extra_unused_type_parameters.rs:82:17
error: type parameters go unused in function definition: A, B
--> $DIR/extra_unused_type_parameters.rs:77:17
|
LL | fn unused_opaque<A, B>(dummy: impl Default) {
| ^^^^^^
|
= help: consider removing the parameters
| ^^^^^^ help: consider removing the parameters
error: type parameter goes unused in function definition
--> $DIR/extra_unused_type_parameters.rs:95:58
error: type parameter `U` goes unused in function definition
--> $DIR/extra_unused_type_parameters.rs:90:56
|
LL | fn unused_with_priv_trait_bound<T: private::Private, U>() {
| ^
|
= help: consider removing the parameter
| ^^^ help: consider removing the parameter
error: aborting due to 9 previous errors
error: aborting due to 8 previous errors

View File

@ -0,0 +1,24 @@
#![warn(clippy::extra_unused_type_parameters)]
fn unused_where_clause<T, U>(x: U)
where
T: Default,
{
unimplemented!();
}
fn unused_multi_where_clause<T, U, V: Default>(x: U)
where
T: Default,
{
unimplemented!();
}
fn unused_all_where_clause<T, U: Default, V: Default>()
where
T: Default,
{
unimplemented!();
}
fn main() {}

View File

@ -0,0 +1,27 @@
error: type parameter `T` goes unused in function definition
--> $DIR/extra_unused_type_parameters_unfixable.rs:3:24
|
LL | fn unused_where_clause<T, U>(x: U)
| ^
|
= help: consider removing the parameter
= note: `-D clippy::extra-unused-type-parameters` implied by `-D warnings`
error: type parameters go unused in function definition: T, V
--> $DIR/extra_unused_type_parameters_unfixable.rs:10:30
|
LL | fn unused_multi_where_clause<T, U, V: Default>(x: U)
| ^ ^^^^^^^^^^
|
= help: consider removing the parameters
error: type parameters go unused in function definition: T, U, V
--> $DIR/extra_unused_type_parameters_unfixable.rs:17:28
|
LL | fn unused_all_where_clause<T, U: Default, V: Default>()
| ^ ^^^^^^^^^^ ^^^^^^^^^^
|
= help: consider removing the parameters
error: aborting due to 3 previous errors

View File

@ -1,4 +1,5 @@
#![warn(clippy::format_in_format_args, clippy::to_string_in_format_args)]
#![allow(unused)]
#![allow(clippy::assertions_on_constants, clippy::eq_op, clippy::uninlined_format_args)]
use std::io::{stdout, Error, ErrorKind, Write};
@ -57,3 +58,46 @@ fn main() {
my_macro!();
println!("error: {}", my_other_macro!());
}
macro_rules! _internal {
($($args:tt)*) => {
println!("{}", format_args!($($args)*))
};
}
macro_rules! my_println2 {
($target:expr, $($args:tt)+) => {{
if $target {
_internal!($($args)+)
}
}};
}
macro_rules! my_println2_args {
($target:expr, $($args:tt)+) => {{
if $target {
_internal!("foo: {}", format_args!($($args)+))
}
}};
}
fn test2() {
let error = Error::new(ErrorKind::Other, "bad thing");
// None of these should be linted without the config change
my_println2!(true, "error: {}", format!("something failed at {}", Location::caller()));
my_println2!(
true,
"{}: {}",
error,
format!("something failed at {}", Location::caller())
);
my_println2_args!(true, "error: {}", format!("something failed at {}", Location::caller()));
my_println2_args!(
true,
"{}: {}",
error,
format!("something failed at {}", Location::caller())
);
}

Some files were not shown because too many files have changed in this diff Show More